From 059bbf00d71aa5603d0cc7f2f0f80bc96b305fc3 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 2 Jul 2019 13:12:15 -0400 Subject: [PATCH 001/134] Address Codacy errors and netbeans suggestions in JLNK.java array now copied --- .../org/sleuthkit/autopsy/coreutils/JLNK.java | 85 ++++++++++--------- 1 file changed, 43 insertions(+), 42 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/JLNK.java b/Core/src/org/sleuthkit/autopsy/coreutils/JLNK.java index 073b9c3107..5e8c680b9f 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/JLNK.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/JLNK.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2012 Basis Technology Corp. + * Copyright 2012-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.coreutils; import java.io.File; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import org.openide.util.NbBundle; @@ -35,43 +36,43 @@ import org.sleuthkit.autopsy.coreutils.LnkEnums.NetworkProviderType; */ public class JLNK { - private int header; - private byte[] linkClassIdentifier; - private List linkFlags; - private List fileAttributesFlags; - private long crtime; - private long atime; - private long mtime; - private int fileSize; - private int iconIndex; - private int showCommand; - private short hotKey; + private final int header; + private final byte[] linkClassIdentifier; + private final List linkFlags; + private final List fileAttributesFlags; + private final long crtime; + private final long atime; + private final long mtime; + private final int fileSize; + private final int iconIndex; + private final int showCommand; + private final short hotKey; - private List linkTargetIdList; + private final List linkTargetIdList; - private boolean hasUnicodeLocalBaseAndCommonSuffixOffset; - private String localBasePath; - private String commonPathSuffix; - private String localBasePathUnicode; - private String commonPathSuffixUnicode; + private final boolean hasUnicodeLocalBaseAndCommonSuffixOffset; + private final String localBasePath; + private final String commonPathSuffix; + private final String localBasePathUnicode; + private final String commonPathSuffixUnicode; - private String name; - private String relativePath; - private String workingDir; - private String arguments; - private String iconLocation; + private final String name; + private final String relativePath; + private final String workingDir; + private final String arguments; + private final String iconLocation; - private int driveSerialNumber; - private DriveType driveType; - private String volumeLabel; + private final int driveSerialNumber; + private final DriveType driveType; + private final String volumeLabel; - private List commonNetworkRelativeListFlags; - private NetworkProviderType networkProviderType; - private boolean unicodeNetAndDeviceName; - private String netName; - private String netNameUnicode; - private String deviceName; - private String deviceNameUnicode; + private final List commonNetworkRelativeListFlags; + private final NetworkProviderType networkProviderType; + private final boolean unicodeNetAndDeviceName; + private final String netName; + private final String netNameUnicode; + private final String deviceName; + private final String deviceNameUnicode; public JLNK(int header, byte[] linkClassIdentifier, int linkFlags, int fileAttributesFlags, long crtime, long atime, @@ -87,14 +88,14 @@ public class JLNK { String netName, String netNameUnicode, String deviceName, String deviceNameUnicode) { this.header = header; - this.linkClassIdentifier = linkClassIdentifier; - this.linkFlags = new ArrayList(); + this.linkClassIdentifier = linkClassIdentifier.clone(); + this.linkFlags = new ArrayList<>(); for (LnkEnums.LinkFlags enumVal : LnkEnums.LinkFlags.values()) { if ((linkFlags & enumVal.getFlag()) == enumVal.getFlag()) { this.linkFlags.add(enumVal); } } - this.fileAttributesFlags = new ArrayList(); + this.fileAttributesFlags = new ArrayList<>(); for (LnkEnums.FileAttributesFlags enumVal : LnkEnums.FileAttributesFlags.values()) { if ((fileAttributesFlags & enumVal.getFlag()) == enumVal.getFlag()) { this.fileAttributesFlags.add(enumVal); @@ -121,7 +122,7 @@ public class JLNK { this.driveSerialNumber = driveSerialNumber; this.driveType = driveType; this.volumeLabel = volumeLabel; - this.commonNetworkRelativeListFlags = new ArrayList(); + this.commonNetworkRelativeListFlags = new ArrayList<>(); for (LnkEnums.CommonNetworkRelativeLinkFlags enumVal : LnkEnums.CommonNetworkRelativeLinkFlags.values()) { if ((commonNetworkRelativeListFlags & enumVal.getFlag()) == enumVal.getFlag()) { this.commonNetworkRelativeListFlags.add(enumVal); @@ -140,7 +141,7 @@ public class JLNK { } public List getCommonNetworkRelativeListFlags() { - return commonNetworkRelativeListFlags; + return Collections.unmodifiableList(commonNetworkRelativeListFlags); } public String getCommonPathSuffix() { @@ -176,7 +177,7 @@ public class JLNK { } public List getFileAttributesFlags() { - return fileAttributesFlags; + return Collections.unmodifiableList(fileAttributesFlags); } public int getFileSize() { @@ -196,7 +197,7 @@ public class JLNK { } public List getLinkTargetIdList() { - return linkTargetIdList; + return Collections.unmodifiableList(linkTargetIdList); } public int getIconIndex() { @@ -208,11 +209,11 @@ public class JLNK { } public byte[] getLinkClassIdentifier() { - return linkClassIdentifier; + return linkClassIdentifier.clone(); } public List getLinkFlags() { - return linkFlags; + return Collections.unmodifiableList(linkFlags); } public String getLocalBasePath() { From 13344e316d844dbf158183c7f1712650e70a241d Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 2 Jul 2019 13:20:04 -0400 Subject: [PATCH 002/134] Address Codacy errors and netbeans suggestions in LnkEnums.java, array now copied --- .../sleuthkit/autopsy/coreutils/LnkEnums.java | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/LnkEnums.java b/Core/src/org/sleuthkit/autopsy/coreutils/LnkEnums.java index dcad3b318c..c154f5ac8a 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/LnkEnums.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/LnkEnums.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2012 Basis Technology Corp. + * Copyright 2012-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -31,6 +31,10 @@ class LnkEnums { private static final byte[] IEFRAME = new byte[]{(byte) 0x80, 0x53, 0x1c, (byte) 0x87, (byte) 0xa0, 0x42, 0x69, 0x10, (byte) 0xa2, (byte) 0xea, 0x08, 0x00, 0x2b, 0x30, 0x30, (byte) 0x9d}; + private LnkEnums() { + //private constructor for utility class + } + public enum CommonCLSIDS { CDrivesFolder(CDRIVES), @@ -38,20 +42,24 @@ class LnkEnums { IEFrameDLL(IEFRAME), Unknown(new byte[16]); - private byte[] flag; + private final byte[] flag; private CommonCLSIDS(byte[] flag) { - this.flag = flag; + this.flag = flag.clone(); } static CommonCLSIDS valueOf(byte[] type) { for (CommonCLSIDS value : CommonCLSIDS.values()) { - if (java.util.Arrays.equals(value.flag, type)) { + if (java.util.Arrays.equals(value.getFlag(), type)) { return value; } } return Unknown; } + + byte[] getFlag() { + return flag.clone(); + } } public enum LinkFlags { @@ -84,7 +92,7 @@ class LnkEnums { PreferEnvironmentPath(0x02000000), KeepLocalIDListForUNCTarget(0x04000000); - private int flag; + private final int flag; private LinkFlags(int flag) { this.flag = flag; @@ -105,7 +113,7 @@ class LnkEnums { DRIVE_CDROM(0x00000005), DRIVE_RAMDISK(0x00000006); - private int flag; + private final int flag; private DriveType(int flag) { this.flag = flag; @@ -117,7 +125,7 @@ class LnkEnums { static DriveType valueOf(int type) { for (DriveType value : DriveType.values()) { - if (value.flag == type) { + if (value.getFlag() == type) { return value; } } @@ -143,7 +151,7 @@ class LnkEnums { NOT_CONTENT_INDEXED(0x00002000), ENCRYPTED(0x00004000); - private int flag; + private final int flag; private FileAttributesFlags(int flag) { this.flag = flag; @@ -159,7 +167,7 @@ class LnkEnums { VolumeIDAndLocalBasePath(0x00000001), CommonNetworkRelativeLinkAndPathSuffix(0x00000002); - private int flag; + private final int flag; private LinkInfoFlags(int flag) { this.flag = flag; @@ -175,7 +183,7 @@ class LnkEnums { ValidDevice(0x00000001), ValidNetType(0x00000002); - private int flag; + private final int flag; private CommonNetworkRelativeLinkFlags(int flag) { this.flag = flag; @@ -231,7 +239,7 @@ class LnkEnums { WNNC_NET_GOOGLE(0x00430000), WNNC_NET_UNKNOWN(0x00000000); - private int flag; + private final int flag; private NetworkProviderType(int flag) { this.flag = flag; @@ -239,7 +247,7 @@ class LnkEnums { static NetworkProviderType valueOf(int type) { for (NetworkProviderType value : NetworkProviderType.values()) { - if (value.flag == type) { + if (value.getFlag() == type) { return value; } } @@ -250,4 +258,5 @@ class LnkEnums { return flag; } } + } From db1f6b84400ecb5bab801320ee4fe308340e5f4c Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 2 Jul 2019 13:22:46 -0400 Subject: [PATCH 003/134] Address Codacy Error in ModalDialogProgressIndicator.java, array now copied --- .../autopsy/progress/ModalDialogProgressIndicator.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/progress/ModalDialogProgressIndicator.java b/Core/src/org/sleuthkit/autopsy/progress/ModalDialogProgressIndicator.java index 8d92e40d70..b6b4c8f142 100644 --- a/Core/src/org/sleuthkit/autopsy/progress/ModalDialogProgressIndicator.java +++ b/Core/src/org/sleuthkit/autopsy/progress/ModalDialogProgressIndicator.java @@ -25,6 +25,7 @@ import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.ThreadSafe; import javax.swing.JDialog; import javax.swing.SwingUtilities; +import org.apache.commons.lang3.SerializationUtils; import org.openide.DialogDescriptor; import org.openide.DialogDisplayer; import org.openide.util.HelpCtx; @@ -65,7 +66,7 @@ public final class ModalDialogProgressIndicator implements ProgressIndicator { this.title = title; progressPanel = new ProgressPanel(); progressPanel.setIndeterminate(true); - this.buttonLabels = buttonLabels; + this.buttonLabels = SerializationUtils.clone(buttonLabels); this.focusedButtonLabel = focusedButtonLabel; this.buttonListener = buttonListener; } From 9a5b5c34afce6bbd7cff6aaf4148931d4cd0eb89 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 2 Jul 2019 13:31:53 -0400 Subject: [PATCH 004/134] Address codacy errors and netbeans suggestions in SqliteTextExtractor.java, array now copied --- .../progress/ModalDialogProgressIndicator.java | 2 +- .../textextractors/SqliteTextExtractor.java | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/progress/ModalDialogProgressIndicator.java b/Core/src/org/sleuthkit/autopsy/progress/ModalDialogProgressIndicator.java index b6b4c8f142..31fb900c56 100644 --- a/Core/src/org/sleuthkit/autopsy/progress/ModalDialogProgressIndicator.java +++ b/Core/src/org/sleuthkit/autopsy/progress/ModalDialogProgressIndicator.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/Core/src/org/sleuthkit/autopsy/textextractors/SqliteTextExtractor.java b/Core/src/org/sleuthkit/autopsy/textextractors/SqliteTextExtractor.java index 889062189c..aa95594be0 100755 --- a/Core/src/org/sleuthkit/autopsy/textextractors/SqliteTextExtractor.java +++ b/Core/src/org/sleuthkit/autopsy/textextractors/SqliteTextExtractor.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2018-2018 Basis Technology Corp. + * Copyright 2018-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -44,7 +44,7 @@ final class SqliteTextExtractor implements TextExtractor { private static final Logger logger = Logger.getLogger(SqliteTextExtractor.class.getName()); private final AbstractFile file; - public SqliteTextExtractor(AbstractFile file) { + SqliteTextExtractor(AbstractFile file) { this.file = file; } /** @@ -101,7 +101,7 @@ final class SqliteTextExtractor implements TextExtractor { * * @param file Sqlite file */ - public SQLiteStreamReader(AbstractFile file) { + SQLiteStreamReader(AbstractFile file) { this.file = file; reader = new SQLiteTableReader.Builder(file) .forAllColumnNames(getColumnNameStrategy()) @@ -140,7 +140,7 @@ final class SqliteTextExtractor implements TextExtractor { } fillBuffer(objectStr); - columnIndex = columnIndex % totalColumns; + columnIndex %= totalColumns; } }; } @@ -171,7 +171,7 @@ final class SqliteTextExtractor implements TextExtractor { fillBuffer(columnName + ((columnIndex == totalColumns) ? "\n" : " ")); //Reset the columnCount to 0 for next table read - columnIndex = columnIndex % totalColumns; + columnIndex %= totalColumns; } }; } @@ -204,7 +204,7 @@ final class SqliteTextExtractor implements TextExtractor { */ @Override public int read(char[] cbuf, int off, int len) throws IOException { - buf = cbuf; + buf = cbuf.clone(); bufIndex = off; @@ -277,12 +277,12 @@ final class SqliteTextExtractor implements TextExtractor { private final String entity; private Integer pointer; - public ExcessBytes(String entity, Integer pointer) { + ExcessBytes(String entity, Integer pointer) { this.entity = entity; this.pointer = pointer; } - public boolean isFinished() { + boolean isFinished() { return entity.length() == pointer; } @@ -296,7 +296,7 @@ final class SqliteTextExtractor implements TextExtractor { * * @return number of characters read into the buffer */ - public int read(char[] buf, int off, int len) { + int read(char[] buf, int off, int len) { for (int i = off; i < len; i++) { if (isFinished()) { return i - off; From daef5d4355c78a7b9bdbfeab4d5f54d45c7ec5c6 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 2 Jul 2019 13:40:25 -0400 Subject: [PATCH 005/134] Address codacy errors and netbeans suggestions in FileExportRuleSet.java, array now coppied --- .../autoingest/FileExportRuleSet.java | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/FileExportRuleSet.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/FileExportRuleSet.java index 399ae574ba..4a098762ff 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/FileExportRuleSet.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/FileExportRuleSet.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2015-2018 Basis Technology Corp. + * Copyright 2015-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -178,6 +178,20 @@ final class FileExportRuleSet implements Serializable, Comparable(); } + /** + * Sort the file size conditions of this rule. + */ + void sortFileSizeConditions() { + this.fileSizeConditions.sort(null); + } + + /** + * Sort the artifact conditions of this rule. + */ + void sortArtifactConditions() { + this.artifactConditions.sort(null); + } + /** * Gets the name of the rule. * @@ -337,12 +351,12 @@ final class FileExportRuleSet implements Serializable, Comparable Date: Tue, 2 Jul 2019 13:53:29 -0400 Subject: [PATCH 006/134] Address codacy errors and netbeans suggestions in FileExportRuleSet.java, array now coppied --- .../autoingest/FileExportRuleSet.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/FileExportRuleSet.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/FileExportRuleSet.java index 4a098762ff..1f5822c973 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/FileExportRuleSet.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/FileExportRuleSet.java @@ -48,7 +48,7 @@ final class FileExportRuleSet implements Serializable, Comparable rules; + private final Map rules; /** * Constructs an empty named set of uniquely named rules. @@ -390,13 +390,18 @@ final class FileExportRuleSet implements Serializable, Comparable evaluate(long dataSourceId) throws ExportRulesException { try { SleuthkitCase db = Case.getCurrentCaseThrows().getSleuthkitCase(); + ResultSet resultSet = null; try (SleuthkitCase.CaseDbQuery queryResult = db.executeQuery(getQuery(dataSourceId))) { - ResultSet resultSet = queryResult.getResultSet(); + resultSet = queryResult.getResultSet(); List fileIds = new ArrayList<>(); while (resultSet.next()) { fileIds.add(resultSet.getLong("obj_id")); } return fileIds; + } finally { + if (resultSet != null) { + resultSet.close(); + } } } catch (NoCurrentCaseException ex) { throw new ExportRulesException("No current case", ex); @@ -922,7 +927,7 @@ final class FileExportRuleSet implements Serializable, Comparable Date: Tue, 2 Jul 2019 13:56:39 -0400 Subject: [PATCH 007/134] Address Codacy error and and netbeans suggestion in BinaryCookieReader.java, array now copied --- .../autopsy/recentactivity/BinaryCookieReader.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/BinaryCookieReader.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/BinaryCookieReader.java index ce92c2e084..1937377804 100755 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/BinaryCookieReader.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/BinaryCookieReader.java @@ -42,6 +42,7 @@ import org.sleuthkit.autopsy.recentactivity.BinaryCookieReader.Cookie; */ final class BinaryCookieReader implements Iterable { + private static final Logger LOG = Logger.getLogger(BinaryCookieReader.class.getName()); private static final int MAGIC_SIZE = 4; private static final int SIZEOF_INT_BYTES = 4; private static final int PAGE_HEADER_VALUE = 256; @@ -53,8 +54,6 @@ final class BinaryCookieReader implements Iterable { private final int[] pageSizeArray; private final File cookieFile; - private static final Logger LOG = Logger.getLogger(BinaryCookieReader.class.getName()); - /** * The binary cookie reader encapsulates all the knowledge of how to read * the mac .binarycookie files into one class. @@ -62,7 +61,7 @@ final class BinaryCookieReader implements Iterable { */ private BinaryCookieReader(File cookieFile, int[] pageSizeArray) { this.cookieFile = cookieFile; - this.pageSizeArray = pageSizeArray; + this.pageSizeArray = pageSizeArray.clone(); } /** @@ -71,7 +70,9 @@ final class BinaryCookieReader implements Iterable { * open. * * @param cookieFile binarycookie file + * * @return An instance of the reader + * * @throws FileNotFoundException * @throws IOException */ @@ -229,6 +230,7 @@ final class BinaryCookieReader implements Iterable { * correct format by checking for the header value of 0x0100. * * @param page byte array representing a cookie page + * * @throws IOException */ CookiePage(byte[] page) throws IOException { @@ -414,7 +416,8 @@ final class BinaryCookieReader implements Iterable { * offset ending at the first null terminator found. * * @param byteArray Array of bytes - * @param offset starting offset in the array + * @param offset starting offset in the array + * * @return String with bytes converted to ascii */ private String decodeString(byte[] byteArray, int offset) { From 41e4b3937b7ea631e310c67878c18c058898993a Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 2 Jul 2019 14:14:46 -0400 Subject: [PATCH 008/134] Fix broken code in FileExportRuleSet by removing type --- .../autopsy/experimental/autoingest/FileExportRuleSet.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/FileExportRuleSet.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/FileExportRuleSet.java index 1f5822c973..7cc730f532 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/FileExportRuleSet.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/FileExportRuleSet.java @@ -48,7 +48,7 @@ final class FileExportRuleSet implements Serializable, Comparable rules; + private final TreeMap rules; /** * Constructs an empty named set of uniquely named rules. From cbbafb2eecfebf80a94130b0a919dc35b626dbe9 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Fri, 2 Aug 2019 13:20:21 -0400 Subject: [PATCH 009/134] 5373 add exception class with user friendly exception --- .../autopsy/exceptions/AutopsyException.java | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 Core/src/org/sleuthkit/autopsy/exceptions/AutopsyException.java diff --git a/Core/src/org/sleuthkit/autopsy/exceptions/AutopsyException.java b/Core/src/org/sleuthkit/autopsy/exceptions/AutopsyException.java new file mode 100644 index 0000000000..739e398735 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/exceptions/AutopsyException.java @@ -0,0 +1,65 @@ +/* + * 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.exceptions; + +public abstract class AutopsyException extends Exception { + + private static final long serialVersionUID = 1L; + + private String userMessage = ""; + + /** + * Constructs an exception to be thrown by a autopsy without a user message. + * + * @param message Exception message. + */ + public AutopsyException(String message) { + super(message); + } + + /** + * Constructs an exception to be thrown by a autopsy with a user exception. + * + * @param message Exception message. + * @param userMessage the user friendly message to include in this exception + */ + public AutopsyException(String message, String userMessage) { + super(message); + this.userMessage = userMessage; + } + + /** + * Constructs an exception to be thrown by a autopsy without a user message. + * + * @param message Exception message. + * @param cause Exception cause. + */ + public AutopsyException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Constructs an exception to be thrown by a autopsy with a user exception. + * + * @param message Exception message. + * @param userMessage the user friendly message to include in this exception + * @param cause Exception cause. + */ + public AutopsyException(String message, String userMessage, Throwable cause) { + super(message, cause); + this.userMessage = userMessage; + } + + /** + * Get the user friendly message if one exists. + * + * @return the user friendly message if one exists, otherwise an empty String + */ + public String getUserMessage() { + return userMessage; + } + +} From 0966948b5ec7b66aa791aa20fa28cf32bd841c96 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Fri, 2 Aug 2019 14:03:14 -0400 Subject: [PATCH 010/134] 5373 resolve merge conflict change exten AutopsyException some --- .../CorrelationAttributeInstance.java | 6 ++-- .../datamodel/EamDbException.java | 28 +++++++++++++++++-- .../datamodel/EamDbUtil.java | 2 +- .../datamodel/PostgresEamDb.java | 9 +++--- .../datamodel/SqliteEamDb.java | 6 ++-- .../eventlisteners/Installer.java | 2 +- .../autopsy/exceptions/AutopsyException.java | 27 ++++++++++++++---- 7 files changed, 60 insertions(+), 20 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeInstance.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeInstance.java index 3cbc510e4c..727b5ecb0b 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeInstance.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeInstance.java @@ -277,10 +277,10 @@ public class CorrelationAttributeInstance implements Serializable { * @param enabled Is this Type currently enabled. */ @Messages({"CorrelationAttributeInstance.nullName.message=Database name is null.", - "CorrelationAttributeInstance.invalidName.message=Invalid database table name. Name must start with a lowercase letter and can only contain lowercase letters, numbers, and '_'."}) + "CorrelationAttributeInstance.invalidName.message=Invalid database table name. Name must start with a lowercase letter and can only contain lowercase letters, numbers, and '_'."}) public Type(int typeId, String displayName, String dbTableName, Boolean supported, Boolean enabled) throws EamDbException { if (dbTableName == null) { - throw new EamDbException(Bundle.CorrelationAttributeInstance_nullName_message()); + throw new EamDbException("dbTableName is null", Bundle.CorrelationAttributeInstance_nullName_message()); } this.typeId = typeId; this.displayName = displayName; @@ -288,7 +288,7 @@ public class CorrelationAttributeInstance implements Serializable { this.supported = supported; this.enabled = enabled; if (!Pattern.matches(DB_NAMES_REGEX, dbTableName)) { - throw new EamDbException(Bundle.CorrelationAttributeInstance_invalidName_message()); // NON-NLS + throw new EamDbException("Invalid database table name. Name must start with a lowercase letter and can only contain lowercase letters, numbers, and '_'.", Bundle.CorrelationAttributeInstance_invalidName_message()); // NON-NLS } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbException.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbException.java index a410f7f182..99dd72483e 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbException.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbException.java @@ -1,7 +1,7 @@ /* * Central Repository * - * Copyright 2015-2017 Basis Technology Corp. + * Copyright 2015-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,10 +18,12 @@ */ package org.sleuthkit.autopsy.centralrepository.datamodel; +import org.sleuthkit.autopsy.exceptions.AutopsyException; + /* * An exception to be thrown by an artifact manager. */ -public class EamDbException extends Exception { +public class EamDbException extends AutopsyException { private static final long serialVersionUID = 1L; @@ -34,6 +36,28 @@ public class EamDbException extends Exception { super(message); } + /** + * Constructs an exception to be thrown by an artifact manager with a user exception. + * + * @param message Exception message. + * @param userMessage the user friendly message to include in this exception + */ + public EamDbException(String message, String userMessage) { + super(message, userMessage); + } + + /** + * Constructs an exception to be thrown by an artifact manager with a user + * exception. + * + * @param message Exception message. + * @param userMessage the user friendly message to include in this exception + * @param cause Exception cause. + */ + public EamDbException(String message, String userMessage, Throwable cause) { + super(message, userMessage, cause); + } + /** * Constructs an exception to be thrown by an artifact manager. * diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbUtil.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbUtil.java index ad198b1744..4fbc566f06 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbUtil.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbUtil.java @@ -236,7 +236,7 @@ public class EamDbUtil { EamDbPlatformEnum.setSelectedPlatform(EamDbPlatformEnum.DISABLED.name()); EamDbPlatformEnum.saveSelectedPlatform(); - throw new EamDbException(messageForDialog); + throw new EamDbException(messageForDialog, messageForDialog); } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDb.java index c6b0d15669..dabfeba7a1 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PostgresEamDb.java @@ -190,7 +190,7 @@ final class PostgresEamDb extends AbstractSqlEamDb { protected Connection connect() throws EamDbException { synchronized (this) { if (!EamDb.isEnabled()) { - throw new EamDbException(Bundle.PostgresEamDb_centralRepoDisabled_message()); // NON-NLS + throw new EamDbException("Central Repository module is not enabled", Bundle.PostgresEamDb_centralRepoDisabled_message()); // NON-NLS } if (connectionPool == null) { @@ -200,7 +200,7 @@ final class PostgresEamDb extends AbstractSqlEamDb { try { return connectionPool.getConnection(); } catch (SQLException ex) { - throw new EamDbException(Bundle.PostgresEamDb_connectionFailed_message(), ex); // NON-NLS + throw new EamDbException("Error getting connection from connection pool.", Bundle.PostgresEamDb_connectionFailed_message(), ex); // NON-NLS } } @@ -221,6 +221,7 @@ final class PostgresEamDb extends AbstractSqlEamDb { * to get the lock */ @Override + @Messages({"PostgresEamDb.multiUserLockError.message=Error acquiring database lock"}) public CoordinationService.Lock getExclusiveMultiUserDbLock() throws EamDbException { try { // First check if multi user mode is enabled - if not there's no point trying to get a lock @@ -234,9 +235,9 @@ final class PostgresEamDb extends AbstractSqlEamDb { if (lock != null) { return lock; } - throw new EamDbException("Error acquiring database lock"); + throw new EamDbException("Error acquiring database lock", Bundle.PostgresEamDb_multiUserLockError_message()); } catch (InterruptedException ex) { - throw new EamDbException("Error acquiring database lock"); + throw new EamDbException("Error acquiring database lock", Bundle.PostgresEamDb_multiUserLockError_message(), ex); } catch (CoordinationService.CoordinationServiceException ex) { // This likely just means the coordination service isn't running, which is ok return null; diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java index b2df5f0fd9..7c36a2fd9e 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/SqliteEamDb.java @@ -158,7 +158,7 @@ final class SqliteEamDb extends AbstractSqlEamDb { private void setupConnectionPool(boolean foreignKeysEnabled) throws EamDbException { if (dbSettings.dbFileExists() == false) { - throw new EamDbException(Bundle.SqliteEamDb_databaseMissing_message()); + throw new EamDbException("Central repository database missing", Bundle.SqliteEamDb_databaseMissing_message()); } connectionPool = new BasicDataSource(); @@ -194,7 +194,7 @@ final class SqliteEamDb extends AbstractSqlEamDb { protected Connection connect(boolean foreignKeys) throws EamDbException { synchronized (this) { if (!EamDb.isEnabled()) { - throw new EamDbException(Bundle.SqliteEamDb_centralRepositoryDisabled_message()); // NON-NLS + throw new EamDbException("Central repository database missing", Bundle.SqliteEamDb_centralRepositoryDisabled_message()); // NON-NLS } if (connectionPool == null) { setupConnectionPool(foreignKeys); @@ -202,7 +202,7 @@ final class SqliteEamDb extends AbstractSqlEamDb { try { return connectionPool.getConnection(); } catch (SQLException ex) { - throw new EamDbException(Bundle.SqliteEamDb_connectionFailedMessage_message(), ex); // NON-NLS + throw new EamDbException("Error getting connection from connection pool.", Bundle.SqliteEamDb_connectionFailedMessage_message(), ex); // NON-NLS } } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java index a96f09fa78..690fb9189d 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java @@ -64,7 +64,7 @@ public class Installer extends ModuleInstall { if (RuntimeProperties.runningWithGUI()) { WindowManager.getDefault().invokeWhenUIReady(() -> { JOptionPane.showMessageDialog(null, - ex.getMessage(), + ex.getUserMessage(), NbBundle.getMessage(this.getClass(), "Installer.centralRepoUpgradeFailed.title"), JOptionPane.ERROR_MESSAGE); diff --git a/Core/src/org/sleuthkit/autopsy/exceptions/AutopsyException.java b/Core/src/org/sleuthkit/autopsy/exceptions/AutopsyException.java index 739e398735..afccff0105 100644 --- a/Core/src/org/sleuthkit/autopsy/exceptions/AutopsyException.java +++ b/Core/src/org/sleuthkit/autopsy/exceptions/AutopsyException.java @@ -1,15 +1,28 @@ /* - * 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 + * + * 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.exceptions; -public abstract class AutopsyException extends Exception { +public class AutopsyException extends Exception { private static final long serialVersionUID = 1L; - private String userMessage = ""; + private final String userMessage; /** * Constructs an exception to be thrown by a autopsy without a user message. @@ -18,6 +31,7 @@ public abstract class AutopsyException extends Exception { */ public AutopsyException(String message) { super(message); + this.userMessage = message; } /** @@ -39,6 +53,7 @@ public abstract class AutopsyException extends Exception { */ public AutopsyException(String message, Throwable cause) { super(message, cause); + this.userMessage = message; } /** @@ -56,7 +71,7 @@ public abstract class AutopsyException extends Exception { /** * Get the user friendly message if one exists. * - * @return the user friendly message if one exists, otherwise an empty String + * @return the user friendly message if one exists, otherwise returns the exceptions normal message */ public String getUserMessage() { return userMessage; From f7e1b845ba240dfed1ea2a106faa0ec9857b8f18 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Fri, 2 Aug 2019 14:41:59 -0400 Subject: [PATCH 011/134] 5373 finish using AutopsyException for purposes of 4595 --- .../datamodel/AbstractSqlEamDb.java | 12 ++-- .../datamodel/EamDbUtil.java | 64 ++++++++++--------- .../optionspanel/GlobalSettingsPanel.java | 4 +- .../autopsy/exceptions/AutopsyException.java | 2 +- 4 files changed, 42 insertions(+), 40 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java index f1be6fab1f..fd2114422b 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/AbstractSqlEamDb.java @@ -3246,10 +3246,10 @@ abstract class AbstractSqlEamDb implements EamDb { try { minorVersion = Integer.parseInt(minorVersionStr); } catch (NumberFormatException ex) { - throw new EamDbException(Bundle.AbstractSqlEamDb_badMinorSchema_message(minorVersionStr), ex); + throw new EamDbException("Bad value for schema minor version (" + minorVersionStr + ") - database is corrupt", Bundle.AbstractSqlEamDb_badMinorSchema_message(minorVersionStr), ex); } } else { - throw new EamDbException(Bundle.AbstractSqlEamDb_failedToReadMinorVersion_message()); + throw new EamDbException("Failed to read schema minor version from db_info table", Bundle.AbstractSqlEamDb_failedToReadMinorVersion_message()); } int majorVersion = 0; @@ -3260,10 +3260,10 @@ abstract class AbstractSqlEamDb implements EamDb { try { majorVersion = Integer.parseInt(majorVersionStr); } catch (NumberFormatException ex) { - throw new EamDbException(Bundle.AbstractSqlEamDb_badMajorSchema_message(majorVersionStr), ex); + throw new EamDbException("Bad value for schema version (" + majorVersionStr + ") - database is corrupt", Bundle.AbstractSqlEamDb_badMajorSchema_message(majorVersionStr), ex); } } else { - throw new EamDbException(Bundle.AbstractSqlEamDb_failedToReadMajorVersion_message()); + throw new EamDbException("Failed to read schema major version from db_info table", Bundle.AbstractSqlEamDb_failedToReadMajorVersion_message()); } /* @@ -3341,7 +3341,7 @@ abstract class AbstractSqlEamDb implements EamDb { addObjectIdIndexTemplate = SqliteEamDbSettings.getAddObjectIdIndexTemplate(); break; default: - throw new EamDbException(Bundle.AbstractSqlEamDb_cannotUpgrage_message(selectedPlatform.name())); + throw new EamDbException("Currently selected database platform \"" + selectedPlatform.name() + "\" can not be upgraded.", Bundle.AbstractSqlEamDb_cannotUpgrage_message(selectedPlatform.name())); } final String dataSourcesTableName = "data_sources"; final String dataSourceObjectIdColumnName = "datasource_obj_id"; @@ -3501,7 +3501,7 @@ abstract class AbstractSqlEamDb implements EamDb { statement.execute("DROP TABLE old_data_sources"); break; default: - throw new EamDbException(Bundle.AbstractSqlEamDb_cannotUpgrage_message(selectedPlatform.name())); + throw new EamDbException("Currently selected database platform \"" + selectedPlatform.name() + "\" can not be upgraded.", Bundle.AbstractSqlEamDb_cannotUpgrage_message(selectedPlatform.name())); } } updateSchemaVersion(conn); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbUtil.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbUtil.java index 4fbc566f06..94723d29db 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbUtil.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbUtil.java @@ -31,6 +31,7 @@ import org.sleuthkit.autopsy.coordinationservice.CoordinationService.Coordinatio import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ModuleSettings; import static org.sleuthkit.autopsy.centralrepository.datamodel.AbstractSqlEamDb.SOFTWARE_CR_DB_SCHEMA_VERSION; +import org.sleuthkit.autopsy.exceptions.AutopsyException; /** * @@ -181,32 +182,39 @@ public class EamDbUtil { } EamDb db = null; CoordinationService.Lock lock = null; - String messageForDialog = ""; + //get connection try { - db = EamDb.getInstance(); - } catch (EamDbException ex) { - LOGGER.log(Level.SEVERE, "Error updating central repository, unable to make connection", ex); - messageForDialog = Bundle.EamDbUtil_centralRepoConnectionFailed_message() + Bundle.EamDbUtil_centralRepoDisabled_message(); - } - //get lock necessary for upgrade - if (db != null) { try { - // This may return null if locking isn't supported, which is fine. It will - // throw an exception if locking is supported but we can't get the lock - // (meaning the database is in use by another user) - lock = db.getExclusiveMultiUserDbLock(); - //perform upgrade + db = EamDb.getInstance(); + } catch (EamDbException ex) { + LOGGER.log(Level.SEVERE, "Error updating central repository, unable to make connection", ex); + throw new EamDbException("Error updating central repository, unable to make connection", Bundle.EamDbUtil_centralRepoConnectionFailed_message() + Bundle.EamDbUtil_centralRepoDisabled_message(), ex); + } + //get lock necessary for upgrade + if (db != null) { + try { + // This may return null if locking isn't supported, which is fine. It will + // throw an exception if locking is supported but we can't get the lock + // (meaning the database is in use by another user) + lock = db.getExclusiveMultiUserDbLock(); + //perform upgrade + } catch (EamDbException ex) { + LOGGER.log(Level.SEVERE, "Error updating central repository, unable to acquire exclusive lock", ex); + throw new EamDbException("Error updating central repository, unable to acquire exclusive lock", Bundle.EamDbUtil_exclusiveLockAquisitionFailure_message() + Bundle.EamDbUtil_centralRepoDisabled_message(), ex); + } + try { db.upgradeSchema(); - } catch (EamDbException | SQLException | IncompatibleCentralRepoException ex) { + } catch (EamDbException ex) { LOGGER.log(Level.SEVERE, "Error updating central repository", ex); - messageForDialog = Bundle.EamDbUtil_centralRepoUpgradeFailed_message() + Bundle.EamDbUtil_centralRepoDisabled_message(); - if (ex instanceof IncompatibleCentralRepoException) { - messageForDialog = ex.getMessage() + "\n\n" + messageForDialog; - } else if (ex instanceof EamDbException) { - messageForDialog = ex.getMessage() + Bundle.EamDbUtil_centralRepoDisabled_message(); - } + throw new EamDbException("Error updating central repository", ex.getUserMessage() + Bundle.EamDbUtil_centralRepoDisabled_message(), ex); + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "Error updating central repository", ex); + throw new EamDbException("Error updating central repository", Bundle.EamDbUtil_centralRepoUpgradeFailed_message() + Bundle.EamDbUtil_centralRepoDisabled_message(), ex); + } catch (IncompatibleCentralRepoException ex) { + LOGGER.log(Level.SEVERE, "Error updating central repository", ex); + throw new EamDbException("Error updating central repository", ex.getMessage() + "\n\n" + Bundle.EamDbUtil_centralRepoUpgradeFailed_message() + Bundle.EamDbUtil_centralRepoDisabled_message(), ex); } finally { if (lock != null) { try { @@ -216,16 +224,11 @@ public class EamDbUtil { } } } - } catch (EamDbException ex) { - LOGGER.log(Level.SEVERE, "Error updating central repository, unable to acquire exclusive lock", ex); - messageForDialog = Bundle.EamDbUtil_exclusiveLockAquisitionFailure_message() + Bundle.EamDbUtil_centralRepoDisabled_message(); + } else { + throw new EamDbException("Unable to connect to database", Bundle.EamDbUtil_centralRepoConnectionFailed_message() + Bundle.EamDbUtil_centralRepoDisabled_message()); } - - } else { - messageForDialog = Bundle.EamDbUtil_centralRepoConnectionFailed_message() + Bundle.EamDbUtil_centralRepoDisabled_message(); - } - // Disable the central repo and clear the current settings. - if (!messageForDialog.isEmpty()) { + } catch (EamDbException ex) { + // Disable the central repo and clear the current settings. try { if (null != EamDb.getInstance()) { EamDb.getInstance().shutdownConnections(); @@ -235,8 +238,7 @@ public class EamDbUtil { } EamDbPlatformEnum.setSelectedPlatform(EamDbPlatformEnum.DISABLED.name()); EamDbPlatformEnum.saveSelectedPlatform(); - - throw new EamDbException(messageForDialog, messageForDialog); + throw ex; } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java index 49d80819ab..41b6dd3cb2 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java @@ -76,7 +76,7 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i ingestStateUpdated(Case.isCaseOpen()); } - @Messages({"GlobalSettingsPanel.updateFailed.title=Central repository upgrade failed"}) + @Messages({"GlobalSettingsPanel.updateFailed.title=Central repository disabled"}) private void updateDatabase() { if (EamDbPlatformEnum.getSelectedPlatform().equals(DISABLED)) { @@ -90,7 +90,7 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i } catch (EamDbException ex) { setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); JOptionPane.showMessageDialog(this, - ex.getMessage(), + ex.getUserMessage(), NbBundle.getMessage(this.getClass(), "GlobalSettingsPanel.updateFailed.title"), JOptionPane.WARNING_MESSAGE); diff --git a/Core/src/org/sleuthkit/autopsy/exceptions/AutopsyException.java b/Core/src/org/sleuthkit/autopsy/exceptions/AutopsyException.java index afccff0105..ec268eaa51 100644 --- a/Core/src/org/sleuthkit/autopsy/exceptions/AutopsyException.java +++ b/Core/src/org/sleuthkit/autopsy/exceptions/AutopsyException.java @@ -18,7 +18,7 @@ */ package org.sleuthkit.autopsy.exceptions; -public class AutopsyException extends Exception { +public abstract class AutopsyException extends Exception { private static final long serialVersionUID = 1L; From 48ece808a4f6465d0c59a35d537fdbd0e80caf95 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Fri, 2 Aug 2019 15:11:14 -0400 Subject: [PATCH 012/134] 5373 move autopsyexceptionhandler --- Core/nbproject/project.xml | 1 + .../org/sleuthkit/autopsy/coreutils/Bundle.properties-MERGED | 4 +--- .../{coreutils => exceptions}/AutopsyExceptionHandler.java | 5 ++++- 3 files changed, 6 insertions(+), 4 deletions(-) rename Core/src/org/sleuthkit/autopsy/{coreutils => exceptions}/AutopsyExceptionHandler.java (96%) diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index 967399cc20..518d0028f1 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -327,6 +327,7 @@ org.sleuthkit.autopsy.datasourceprocessors org.sleuthkit.autopsy.directorytree org.sleuthkit.autopsy.events + org.sleuthkit.autopsy.exceptions org.sleuthkit.autopsy.filesearch org.sleuthkit.autopsy.guiutils org.sleuthkit.autopsy.healthmonitor diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/coreutils/Bundle.properties-MERGED index 17791d159d..702b726e08 100755 --- a/Core/src/org/sleuthkit/autopsy/coreutils/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/coreutils/Bundle.properties-MERGED @@ -23,9 +23,7 @@ PlatformUtil.getProcVmUsed.sigarNotInit.msg=Cannot get virt mem used, sigar not PlatformUtil.getProcVmUsed.gen.msg=Cannot get virt mem used, {0} PlatformUtil.getJvmMemInfo.usageText=JVM heap usage: {0}, JVM non-heap usage: {1} PlatformUtil.getPhysicalMemInfo.usageText=Physical memory usage (max, total, free): {0}, {1}, {2} -PlatformUtil.getAllMemUsageInfo.usageText={0}\n\ -{1}\n\ -Process Virtual Memory: {2} +PlatformUtil.getAllMemUsageInfo.usageText={0}\n{1}\nProcess Virtual Memory: {2} # {0} - file name ReadImageTask.mesageText=Reading image: {0} StringExtract.illegalStateException.cannotInit.msg=Unicode table not properly initialized, cannot instantiate StringExtract diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/AutopsyExceptionHandler.java b/Core/src/org/sleuthkit/autopsy/exceptions/AutopsyExceptionHandler.java similarity index 96% rename from Core/src/org/sleuthkit/autopsy/coreutils/AutopsyExceptionHandler.java rename to Core/src/org/sleuthkit/autopsy/exceptions/AutopsyExceptionHandler.java index 3b7c333bc3..13b73583a2 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/AutopsyExceptionHandler.java +++ b/Core/src/org/sleuthkit/autopsy/exceptions/AutopsyExceptionHandler.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.coreutils; +package org.sleuthkit.autopsy.exceptions; import java.util.logging.Filter; import java.util.logging.Handler; @@ -26,6 +26,9 @@ import java.util.logging.SimpleFormatter; import javax.swing.JOptionPane; import org.openide.util.lookup.ServiceProvider; import org.netbeans.core.NbErrorManager; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.autopsy.coreutils.Version; /** * Replaces default NetBeans exception handler. Displays messages in a dialog. From a5b20b24d99ba37765dd4ebb17599cdfa54ab96f Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Fri, 2 Aug 2019 15:28:17 -0400 Subject: [PATCH 013/134] 5373 AutopsyException comment and merged props --- .../sleuthkit/autopsy/coreutils/Bundle.properties-MERGED | 4 +++- .../org/sleuthkit/autopsy/exceptions/AutopsyException.java | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/coreutils/Bundle.properties-MERGED index 702b726e08..17791d159d 100755 --- a/Core/src/org/sleuthkit/autopsy/coreutils/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/coreutils/Bundle.properties-MERGED @@ -23,7 +23,9 @@ PlatformUtil.getProcVmUsed.sigarNotInit.msg=Cannot get virt mem used, sigar not PlatformUtil.getProcVmUsed.gen.msg=Cannot get virt mem used, {0} PlatformUtil.getJvmMemInfo.usageText=JVM heap usage: {0}, JVM non-heap usage: {1} PlatformUtil.getPhysicalMemInfo.usageText=Physical memory usage (max, total, free): {0}, {1}, {2} -PlatformUtil.getAllMemUsageInfo.usageText={0}\n{1}\nProcess Virtual Memory: {2} +PlatformUtil.getAllMemUsageInfo.usageText={0}\n\ +{1}\n\ +Process Virtual Memory: {2} # {0} - file name ReadImageTask.mesageText=Reading image: {0} StringExtract.illegalStateException.cannotInit.msg=Unicode table not properly initialized, cannot instantiate StringExtract diff --git a/Core/src/org/sleuthkit/autopsy/exceptions/AutopsyException.java b/Core/src/org/sleuthkit/autopsy/exceptions/AutopsyException.java index ec268eaa51..1053d841a1 100644 --- a/Core/src/org/sleuthkit/autopsy/exceptions/AutopsyException.java +++ b/Core/src/org/sleuthkit/autopsy/exceptions/AutopsyException.java @@ -18,6 +18,9 @@ */ package org.sleuthkit.autopsy.exceptions; +/* + * An exception to be thrown which can contain a user friendly message. + */ public abstract class AutopsyException extends Exception { private static final long serialVersionUID = 1L; @@ -71,7 +74,8 @@ public abstract class AutopsyException extends Exception { /** * Get the user friendly message if one exists. * - * @return the user friendly message if one exists, otherwise returns the exceptions normal message + * @return the user friendly message if one exists, otherwise returns the + * exceptions normal message */ public String getUserMessage() { return userMessage; From f1d0cb026bf850363f9aa717ebeb925a4710fcdb Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Fri, 2 Aug 2019 15:42:27 -0400 Subject: [PATCH 014/134] 5373 update merged bundle properties --- .../centralrepository/datamodel/Bundle.properties-MERGED | 1 + .../centralrepository/optionspanel/Bundle.properties-MERGED | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Bundle.properties-MERGED index c94c442588..9b43833ee2 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Bundle.properties-MERGED @@ -39,6 +39,7 @@ EamDbUtil.centralRepoUpgradeFailed.message=Failed to upgrade Central Repository. EamDbUtil.exclusiveLockAquisitionFailure.message=Unable to acquire exclusive lock for Central Repository. PostgresEamDb.centralRepoDisabled.message=Central Repository module is not enabled. PostgresEamDb.connectionFailed.message=Error getting connection to database. +PostgresEamDb.multiUserLockError.message=Error acquiring database lock SqliteEamDb.centralRepositoryDisabled.message=Central Repository module is not enabled. SqliteEamDb.connectionFailedMessage.message=Error getting connection to database. SqliteEamDb.databaseMissing.message=Central repository database missing diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties-MERGED index dc42aa3b68..7277910e7d 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties-MERGED @@ -33,7 +33,7 @@ EamDbSettingsDialog.validation.finished=Click OK to save your database settings EamDbSettingsDialog.validation.incompleteFields=Fill in all values for the selected database. EamOptionsController.moduleErr=Error processing value changes. EamOptionsController.moduleErr.msg=Value change processing failed. -GlobalSettingsPanel.updateFailed.title=Central repository upgrade failed +GlobalSettingsPanel.updateFailed.title=Central repository disabled GlobalSettingsPanel.validationErrMsg.ingestRunning=You cannot change settings while ingest is running. GlobalSettingsPanel.validationerrMsg.mustConfigure=Configure the database to enable this module. ManageCasesDialog.title.text=Manage Cases From 62b3882ba95b25ade3569463f77cc14b84b5cca2 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 6 Aug 2019 11:20:15 -0400 Subject: [PATCH 015/134] 5373 update comments to meet standards --- .../autopsy/exceptions/AutopsyException.java | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/exceptions/AutopsyException.java b/Core/src/org/sleuthkit/autopsy/exceptions/AutopsyException.java index 1053d841a1..9bc3888164 100644 --- a/Core/src/org/sleuthkit/autopsy/exceptions/AutopsyException.java +++ b/Core/src/org/sleuthkit/autopsy/exceptions/AutopsyException.java @@ -19,7 +19,7 @@ package org.sleuthkit.autopsy.exceptions; /* - * An exception to be thrown which can contain a user friendly message. + * An exception to be thrown which can contain a user-friendly message. */ public abstract class AutopsyException extends Exception { @@ -28,7 +28,8 @@ public abstract class AutopsyException extends Exception { private final String userMessage; /** - * Constructs an exception to be thrown by a autopsy without a user message. + * Constructs an AutopsyException with identical exception and user-friendly + * messages. * * @param message Exception message. */ @@ -38,10 +39,11 @@ public abstract class AutopsyException extends Exception { } /** - * Constructs an exception to be thrown by a autopsy with a user exception. + * Constructs an AutopsyException with an exception message and user-friendly message. * * @param message Exception message. - * @param userMessage the user friendly message to include in this exception + * @param userMessage The user-friendly message to include in this + * exception. */ public AutopsyException(String message, String userMessage) { super(message); @@ -49,7 +51,8 @@ public abstract class AutopsyException extends Exception { } /** - * Constructs an exception to be thrown by a autopsy without a user message. + * Constructs an AutopsyException with identical exception and user-friendly + * messages. * * @param message Exception message. * @param cause Exception cause. @@ -60,10 +63,11 @@ public abstract class AutopsyException extends Exception { } /** - * Constructs an exception to be thrown by a autopsy with a user exception. + * Constructs an AutopsyException with an exception message, a user-friendly messages, and a cause. * * @param message Exception message. - * @param userMessage the user friendly message to include in this exception + * @param userMessage The user-friendly message to include in this + * exception. * @param cause Exception cause. */ public AutopsyException(String message, String userMessage, Throwable cause) { @@ -72,10 +76,10 @@ public abstract class AutopsyException extends Exception { } /** - * Get the user friendly message if one exists. + * Get the user-friendly message if one exists. * - * @return the user friendly message if one exists, otherwise returns the - * exceptions normal message + * @return The user-friendly message if one was explicitly set, otherwise + * returns the exception message. */ public String getUserMessage() { return userMessage; From 65112077f193ea714ab1a5441ad669bda381881f Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 6 Aug 2019 11:23:20 -0400 Subject: [PATCH 016/134] 5373 fix easy to fix codacy issues --- .../autopsy/centralrepository/datamodel/EamDbException.java | 2 +- .../autopsy/centralrepository/datamodel/EamDbUtil.java | 1 - Core/src/org/sleuthkit/autopsy/exceptions/AutopsyException.java | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbException.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbException.java index 99dd72483e..262b550f5c 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbException.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbException.java @@ -20,7 +20,7 @@ package org.sleuthkit.autopsy.centralrepository.datamodel; import org.sleuthkit.autopsy.exceptions.AutopsyException; -/* +/** * An exception to be thrown by an artifact manager. */ public class EamDbException extends AutopsyException { diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbUtil.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbUtil.java index 94723d29db..c287990d79 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbUtil.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/EamDbUtil.java @@ -31,7 +31,6 @@ import org.sleuthkit.autopsy.coordinationservice.CoordinationService.Coordinatio import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ModuleSettings; import static org.sleuthkit.autopsy.centralrepository.datamodel.AbstractSqlEamDb.SOFTWARE_CR_DB_SCHEMA_VERSION; -import org.sleuthkit.autopsy.exceptions.AutopsyException; /** * diff --git a/Core/src/org/sleuthkit/autopsy/exceptions/AutopsyException.java b/Core/src/org/sleuthkit/autopsy/exceptions/AutopsyException.java index 9bc3888164..1c91c6d773 100644 --- a/Core/src/org/sleuthkit/autopsy/exceptions/AutopsyException.java +++ b/Core/src/org/sleuthkit/autopsy/exceptions/AutopsyException.java @@ -18,7 +18,7 @@ */ package org.sleuthkit.autopsy.exceptions; -/* +/** * An exception to be thrown which can contain a user-friendly message. */ public abstract class AutopsyException extends Exception { From 944a961da596d82a3a2bc81ea6dd52dd39da5d58 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 13 Aug 2019 14:18:54 -0400 Subject: [PATCH 017/134] 5373 move exception handler back to coreutils --- .../{exceptions => coreutils}/AutopsyExceptionHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename Core/src/org/sleuthkit/autopsy/{exceptions => coreutils}/AutopsyExceptionHandler.java (99%) diff --git a/Core/src/org/sleuthkit/autopsy/exceptions/AutopsyExceptionHandler.java b/Core/src/org/sleuthkit/autopsy/coreutils/AutopsyExceptionHandler.java similarity index 99% rename from Core/src/org/sleuthkit/autopsy/exceptions/AutopsyExceptionHandler.java rename to Core/src/org/sleuthkit/autopsy/coreutils/AutopsyExceptionHandler.java index 13b73583a2..d1772ce6e8 100644 --- a/Core/src/org/sleuthkit/autopsy/exceptions/AutopsyExceptionHandler.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/AutopsyExceptionHandler.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.exceptions; +package org.sleuthkit.autopsy.coreutils; import java.util.logging.Filter; import java.util.logging.Handler; From bc49ac1a1bc35a695310422e1dbb3d09d59e9039 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 28 Aug 2019 10:48:44 -0400 Subject: [PATCH 018/134] 5373 remove unnecessary imports per codacy --- .../sleuthkit/autopsy/coreutils/AutopsyExceptionHandler.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/AutopsyExceptionHandler.java b/Core/src/org/sleuthkit/autopsy/coreutils/AutopsyExceptionHandler.java index d1772ce6e8..3b7c333bc3 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/AutopsyExceptionHandler.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/AutopsyExceptionHandler.java @@ -26,9 +26,6 @@ import java.util.logging.SimpleFormatter; import javax.swing.JOptionPane; import org.openide.util.lookup.ServiceProvider; import org.netbeans.core.NbErrorManager; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import org.sleuthkit.autopsy.coreutils.Version; /** * Replaces default NetBeans exception handler. Displays messages in a dialog. From 5dce361907b14264274043e4d202a443576dee67 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 11 Sep 2019 17:43:57 -0400 Subject: [PATCH 019/134] 5297 address review feedback --- .../autopsy/textextractors/SqliteTextExtractor.java | 2 +- .../experimental/autoingest/FileExportRuleSet.java | 13 ++++--------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/textextractors/SqliteTextExtractor.java b/Core/src/org/sleuthkit/autopsy/textextractors/SqliteTextExtractor.java index aa95594be0..50151c9da9 100755 --- a/Core/src/org/sleuthkit/autopsy/textextractors/SqliteTextExtractor.java +++ b/Core/src/org/sleuthkit/autopsy/textextractors/SqliteTextExtractor.java @@ -204,7 +204,7 @@ final class SqliteTextExtractor implements TextExtractor { */ @Override public int read(char[] cbuf, int off, int len) throws IOException { - buf = cbuf.clone(); + buf = cbuf; //needs to be the same memory address and not a copy of the contents since we are filling it in bufIndex = off; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/FileExportRuleSet.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/FileExportRuleSet.java index 7cc730f532..198dd8c5bd 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/FileExportRuleSet.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/FileExportRuleSet.java @@ -350,12 +350,12 @@ final class FileExportRuleSet implements Serializable, Comparable evaluate(long dataSourceId) throws ExportRulesException { try { SleuthkitCase db = Case.getCurrentCaseThrows().getSleuthkitCase(); - ResultSet resultSet = null; - try (SleuthkitCase.CaseDbQuery queryResult = db.executeQuery(getQuery(dataSourceId))) { - resultSet = queryResult.getResultSet(); + try (SleuthkitCase.CaseDbQuery queryResult = db.executeQuery(getQuery(dataSourceId)); + ResultSet resultSet = queryResult.getResultSet();) { List fileIds = new ArrayList<>(); while (resultSet.next()) { fileIds.add(resultSet.getLong("obj_id")); } return fileIds; - } finally { - if (resultSet != null) { - resultSet.close(); - } } } catch (NoCurrentCaseException ex) { throw new ExportRulesException("No current case", ex); From 48a04cd3a9898e04f60714b3fc14ae8401c789c1 Mon Sep 17 00:00:00 2001 From: Raman Arora Date: Fri, 4 Oct 2019 11:24:53 -0400 Subject: [PATCH 020/134] 5560: add support for FB messenger call logs. --- InternalPythonModules/android/fbmessenger.py | 343 +++++++++++++------ 1 file changed, 234 insertions(+), 109 deletions(-) diff --git a/InternalPythonModules/android/fbmessenger.py b/InternalPythonModules/android/fbmessenger.py index 3a678f16d0..e3c27d1765 100644 --- a/InternalPythonModules/android/fbmessenger.py +++ b/InternalPythonModules/android/fbmessenger.py @@ -45,6 +45,7 @@ from org.sleuthkit.datamodel import Account from org.sleuthkit.datamodel.blackboardutils import CommunicationArtifactsHelper 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 @@ -181,15 +182,240 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer): contactsDb.close() - - ## Adds a recipient to given list + ## Extracts recipeint id from 'user_key' column and adds recipient to given list. def addRecipientToList(self, user_key, recipientList): if user_key is not None: recipientId = user_key.replace('FACEBOOK:', '') recipientList.append(recipientId) + + + ## Extracts sender id from the json in 'sender' column. + def getSenderIdFromJson(self, senderJsonStr): + senderId = None; + if senderJsonStr is not None: + sender_dict = json.loads(senderJsonStr) + senderId = sender_dict['user_key'] + senderId = senderId.replace('FACEBOOK:', '') - ## Analyze messages - def analyzeMessages(self, dataSource, fileManager, context): + return senderId + + ## determines communication direction by comparing senderId with selfAccountId + def deduceDirectionFromSenderId(self, senderId): + direction = CommunicationDirection.UNKNOWN + if senderId is not None: + if senderId == self.selfAccountId: + direction = CommunicationDirection.OUTGOING + else: + direction = CommunicationDirection.INCOMING + return direction + + ## Analyzes messages + def analyzeMessages(self, threadsDb, threadsDBHelper): + try: + + ## Messages are found in the messages table. + ## This query filters messages by msg_type to only get actual user created conversation messages (msg_type 0). + ## The participant ids can be found in the thread_participants table. + ## Participant names are found in thread_users table. + ## Joining these tables produces multiple rows per message, one row for each recipient. + ## 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 + 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 + WHERE msg_type = 0 + ORDER BY msg_id + """ + + messagesResultSet = threadsDb.runQuery(sqlString) + if messagesResultSet is not None: + oldMsgId = None + + direction = CommunicationDirection.UNKNOWN + fromId = None + recipientIdsList = None + timeStamp = -1 + msgText = "" + threadId = "" + + while messagesResultSet.next(): + msgId = messagesResultSet.getString("msg_id") + + # new msg begins when msgId changes + if msgId != oldMsgId: + # Create message artifact with collected attributes + if oldMsgId is not None: + messageArtifact = threadsDBHelper.addMessage( + self._MESSAGE_TYPE, + direction, + fromId, + recipientIdsList, + timeStamp, + MessageReadStatus.UNKNOWN, + "", # subject + msgText, + threadId) + + oldMsgId = msgId + + # New message - collect all attributes + recipientIdsList = [] + + ## get sender id by parsing JSON in sender column + fromId = self.getSenderIdFromJson(messagesResultSet.getString("sender")) + direction = self.deduceDirectionFromSenderId(fromId) + + # Get recipient and add to list + self.addRecipientToList(messagesResultSet.getString("user_key"), + recipientIdsList) + + timeStamp = messagesResultSet.getLong("timestamp_ms") / 1000 + + # Get msg text + # Sometimes there may not be an explict msg text, + # but an app generated snippet instead + msgText = messagesResultSet.getString("text") + if not msgText: + msgText = messagesResultSet.getString("snippet") + + # TBD: get attachment + + threadId = messagesResultSet.getString("thread_key") + + else: # same msgId as last, just collect recipient from current row + self.addRecipientToList(messagesResultSet.getString("user_key"), + recipientIdsList) + + + # at the end of the loop, add last message + messageArtifact = threadsDBHelper.addMessage( + self._MESSAGE_TYPE, + direction, + fromId, + recipientIdsList, + timeStamp, + MessageReadStatus.UNKNOWN, + "", # subject + msgText, + threadId) + + except SQLException as ex: + self._logger.log(Level.WARNING, "Error processing query result for FB Messenger messages.", ex) + self._logger.log(Level.WARNING, traceback.format_exc()) + except TskCoreException as ex: + self._logger.log(Level.SEVERE, "Failed to add FB Messenger message artifacts.", ex) + self._logger.log(Level.SEVERE, traceback.format_exc()) + except BlackboardException as ex: + self._logger.log(Level.WARNING, "Failed to post artifacts.", ex) + self._logger.log(Level.WARNING, traceback.format_exc()) + + ## Analyzes call logs + def analyzeCallLogs(self, threadsDb, threadsDBHelper): + try: + + ## Call logs are found in the messages table. + ## msg_type indicates type of call: + ## 9: one to one calls + ## 203: group call + ## 1-to-1 calls only have a call_ended record. + ## group calls have a call_started_record as well as call_ended recorded, with *different* message ids. + ## all the data we need can be found in the call_ended record. + + sqlString = """ + SELECT msg_id, text, sender, timestamp_ms, msg_type, admin_text_thread_rtc_event, + generic_admin_message_extensible_data, + messages.thread_key as thread_key, + thread_participants.user_key as user_key, + thread_users.name as name + 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 + WHERE msg_type = 9 OR (msg_type = 203 AND admin_text_thread_rtc_event = 'group_call_ended') + ORDER BY msg_id + """ + + messagesResultSet = threadsDb.runQuery(sqlString) + if messagesResultSet is not None: + oldMsgId = None + + direction = CommunicationDirection.UNKNOWN + callerId = None + calleeIdsList = None + startTimeStamp = -1 + endTimeStamp = -1 + duration = 0 + mediaType = CallMediaType.AUDIO + + while messagesResultSet.next(): + msgId = messagesResultSet.getString("msg_id") + + # new call begins when msgId changes + if msgId != oldMsgId: + # Create call log artifact with collected attributes + if oldMsgId is not None: + messageArtifact = threadsDBHelper.addCalllog( + direction, + callerId, + calleeIdsList, + startTimeStamp, + endTimeStamp, + mediaType ) + + oldMsgId = msgId + + # New message - collect all attributes + calleeIdsList = [] + + ## get caller id by parsing JSON in sender column + callerId = self.getSenderIdFromJson(messagesResultSet.getString("sender")) + direction = self.deduceDirectionFromSenderId(callerId) + + # Get recipient and add to list + self.addRecipientToList(messagesResultSet.getString("user_key"), + calleeIdsList) + + # the timestamp from call ended msg is used as end timestamp + endTimeStamp = messagesResultSet.getLong("timestamp_ms") / 1000 + + # parse the generic_admin_message_extensible_data JSON to extract the duration and video fields + adminDataJsonStr = messagesResultSet.getString("generic_admin_message_extensible_data") + if adminDataJsonStr is not None: + adminData_dict = json.loads(adminDataJsonStr) + duration = adminData_dict['call_duration'] # call duration in seconds + isVideo = adminData_dict['video'] + if isVideo: + mediaType = CallMediaType.VIDEO + + startTimeStamp = endTimeStamp - duration + + else: # same msgId as last, just collect callee from current row + self.addRecipientToList(messagesResultSet.getString("user_key"), + calleeIdsList) + + # at the end of the loop, add last message + messageArtifact = threadsDBHelper.addCalllog( + direction, + callerId, + calleeIdsList, + startTimeStamp, + endTimeStamp, + mediaType ) + + except SQLException as ex: + self._logger.log(Level.WARNING, "Error processing query result for FB Messenger call logs.", ex) + self._logger.log(Level.WARNING, traceback.format_exc()) + except TskCoreException as ex: + self._logger.log(Level.SEVERE, "Failed to add FB Messenger call log artifacts.", ex) + self._logger.log(Level.SEVERE, traceback.format_exc()) + except BlackboardException as ex: + self._logger.log(Level.WARNING, "Failed to post FB Messenger call log artifacts.", ex) + self._logger.log(Level.WARNING, traceback.format_exc()) + + + ## Analyze messages and call log threads + def analyzeMessagesAndCallLogs(self, dataSource, fileManager, context): threadsDbs = AppSQLiteDB.findAppDatabases(dataSource, "threads_db2", True, self._FB_MESSENGER_PACKAGE_NAME) for threadsDb in threadsDbs: try: @@ -202,113 +428,12 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer): self._MODULE_NAME, threadsDb.getDBFile(), Account.Type.FACEBOOK) - ## Messages are found in the messages table. - ## This query filters messages by msg_type to only get actual user created conversation messages (msg_type 0). - ## The participant ids can be found in the thread_participants table. - ## Participant names are found in thread_users table. - ## Joining these tables produces multiple rows per message, one row for each recipient. - ## 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 - 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 - WHERE msg_type = 0 - ORDER BY msg_id - """ - - messagesResultSet = threadsDb.runQuery(sqlString) - if messagesResultSet is not None: - oldMsgId = None - - direction = CommunicationDirection.UNKNOWN - fromId = None - recipientIdsList = None - timeStamp = -1 - msgText = "" - threadId = "" - - while messagesResultSet.next(): - msgId = messagesResultSet.getString("msg_id") - - # new msg begins when msgId changes - if msgId != oldMsgId: - # Create message artifact with collected attributes - if oldMsgId is not None: - messageArtifact = threadsDBHelper.addMessage( - self._MESSAGE_TYPE, - direction, - fromId, - recipientIdsList, - timeStamp, - MessageReadStatus.UNKNOWN, - "", # subject - msgText, - threadId) - - oldMsgId = msgId - - # New message - collect all attributes - recipientIdsList = [] - - ## get sender id by parsing JSON in sender column - senderJsonStr = messagesResultSet.getString("sender") - if senderJsonStr is not None: - sender_dict = json.loads(senderJsonStr) - senderId = sender_dict['user_key'] - senderId = senderId.replace('FACEBOOK:', '') - senderName = sender_dict['name'] - fromId = senderId - if senderId == self.selfAccountId: - direction = CommunicationDirection.OUTGOING - else: - direction = CommunicationDirection.INCOMING - - - # Get recipient and add to list - self.addRecipientToList(messagesResultSet.getString("user_key"), - recipientIdsList) - - timeStamp = messagesResultSet.getLong("timestamp_ms") / 1000 - - # Get msg text - # Sometimes there may not be an explict msg text, - # but an app generated snippet instead - msgText = messagesResultSet.getString("text") - if not msgText: - msgText = messagesResultSet.getString("snippet") - - # TBD: get attachment - - threadId = messagesResultSet.getString("thread_key") - - else: # same msgId as last, just collect recipient from current row - self.addRecipientToList(messagesResultSet.getString("user_key"), - recipientIdsList) - - - # at the end of the loop, add last message - messageArtifact = threadsDBHelper.addMessage( - self._MESSAGE_TYPE, - direction, - fromId, - recipientIdsList, - timeStamp, - MessageReadStatus.UNKNOWN, - "", # subject - msgText, - threadId) + self.analyzeMessages(threadsDb, threadsDBHelper) + self.analyzeCallLogs(threadsDb, threadsDBHelper) - except SQLException as ex: - self._logger.log(Level.WARNING, "Error processing query result for FB Messenger messages.", ex) - self._logger.log(Level.WARNING, traceback.format_exc()) except TskCoreException as ex: - self._logger.log(Level.SEVERE, "Failed to add FB Messenger message artifacts.", ex) + self._logger.log(Level.SEVERE, "Failed to to create CommunicationArtifactsHelper for FB Messenger.", ex) self._logger.log(Level.SEVERE, traceback.format_exc()) - except BlackboardException as ex: - self._logger.log(Level.WARNING, "Failed to post artifacts.", ex) - self._logger.log(Level.WARNING, traceback.format_exc()) finally: threadsDb.close() @@ -321,6 +446,6 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer): return self.analyzeContacts(dataSource, fileManager, context) - self.analyzeMessages(dataSource, fileManager, context) + self.analyzeMessagesAndCallLogs(dataSource, fileManager, context) From b7cc97c7696fa32759f94cef8c8b3d6e9fae604f Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Tue, 8 Oct 2019 16:14:19 -0400 Subject: [PATCH 021/134] Initial changes --- .../contentviewers/MediaPlayerPanel.java | 88 +++++++++++++------ 1 file changed, 63 insertions(+), 25 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java index 5e0c85e807..b25d41d1c9 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java @@ -53,6 +53,7 @@ import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.TskData; import javafx.embed.swing.JFXPanel; import javax.swing.event.ChangeListener; +import org.freedesktop.gstreamer.Clock; import org.freedesktop.gstreamer.GstException; /** @@ -179,6 +180,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie //Update progress bar and time label during video playback private final Timer timer = new Timer(75, new VideoPanelUpdater()); private static final int PROGRESS_SLIDER_SIZE = 2000; + private static final int SKIP_IN_SECONDS = 30; private ExtractMedia extractMediaWorker; @@ -307,8 +309,8 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie if (gstPlayBin != null) { gstPlayBin.stop(); gstPlayBin.getBus().disconnect(endOfStreamListener); - gstPlayBin.getBus().disconnect(endOfStreamListener); - gstPlayBin.getBus().disconnect(endOfStreamListener); + gstPlayBin.getBus().disconnect(stateChangeListener); + gstPlayBin.getBus().disconnect(errorListener); gstPlayBin.dispose(); fxAppSink.clear(); gstPlayBin = null; @@ -533,6 +535,8 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie progressLabel = new javax.swing.JLabel(); VolumeIcon = new javax.swing.JLabel(); audioSlider = new javax.swing.JSlider(); + fastForwardButton = new javax.swing.JButton(); + rewindButton = new javax.swing.JButton(); javax.swing.GroupLayout videoPanelLayout = new javax.swing.GroupLayout(videoPanel); videoPanel.setLayout(videoPanelLayout); @@ -542,7 +546,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie ); videoPanelLayout.setVerticalGroup( videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 259, Short.MAX_VALUE) + .addGap(0, 257, Short.MAX_VALUE) ); progressSlider.setValue(0); @@ -568,12 +572,20 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie audioSlider.setMajorTickSpacing(10); audioSlider.setMaximum(50); audioSlider.setMinorTickSpacing(5); - audioSlider.setPaintTicks(true); audioSlider.setToolTipText(org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.audioSlider.toolTipText")); // NOI18N audioSlider.setValue(25); audioSlider.setMinimumSize(new java.awt.Dimension(200, 21)); audioSlider.setPreferredSize(new java.awt.Dimension(200, 21)); + org.openide.awt.Mnemonics.setLocalizedText(fastForwardButton, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.fastForwardButton.text")); // NOI18N + fastForwardButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + fastForwardButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(rewindButton, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.rewindButton.text")); // NOI18N + javax.swing.GroupLayout controlPanelLayout = new javax.swing.GroupLayout(controlPanel); controlPanel.setLayout(controlPanelLayout); controlPanelLayout.setHorizontalGroup( @@ -582,34 +594,42 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie .addContainerGap() .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(controlPanelLayout.createSequentialGroup() + .addComponent(infoLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 246, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 73, Short.MAX_VALUE) + .addComponent(rewindButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(playButton, javax.swing.GroupLayout.PREFERRED_SIZE, 64, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 680, Short.MAX_VALUE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(progressLabel)) - .addGroup(controlPanelLayout.createSequentialGroup() - .addComponent(infoLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGap(18, 18, 18) - .addComponent(VolumeIcon, javax.swing.GroupLayout.PREFERRED_SIZE, 64, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(2, 2, 2) - .addComponent(audioSlider, javax.swing.GroupLayout.PREFERRED_SIZE, 229, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(fastForwardButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 191, Short.MAX_VALUE) + .addComponent(VolumeIcon, javax.swing.GroupLayout.PREFERRED_SIZE, 64, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(audioSlider, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) + .addComponent(progressLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .addContainerGap()) ); controlPanelLayout.setVerticalGroup( controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(controlPanelLayout.createSequentialGroup() + .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(progressLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 23, Short.MAX_VALUE)) .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addComponent(progressLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addComponent(playButton)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(audioSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(VolumeIcon, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(infoLabel))) - .addGap(13, 13, 13)) + .addGroup(controlPanelLayout.createSequentialGroup() + .addGap(17, 17, 17) + .addComponent(infoLabel)) + .addGroup(controlPanelLayout.createSequentialGroup() + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(VolumeIcon, javax.swing.GroupLayout.PREFERRED_SIZE, 21, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(playButton) + .addComponent(fastForwardButton) + .addComponent(rewindButton)) + .addComponent(audioSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))) + .addContainerGap(13, Short.MAX_VALUE)) ); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); @@ -636,15 +656,33 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie } }//GEN-LAST:event_playButtonActionPerformed + private void fastForwardButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_fastForwardButtonActionPerformed + long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS); + Clock playbinClock = gstPlayBin.getClock(); + long currentTime = playbinClock.getTime(); + //Skip 30 seconds. + long skipAhead = TimeUnit.SECONDS.convert(SKIP_IN_SECONDS, TimeUnit.NANOSECONDS); + //Ensure new video position is within bounds + long newTime = Math.max(currentTime + skipAhead, duration); + gstPlayBin.seek(newTime, TimeUnit.NANOSECONDS); + //0 <= newTimePercent <= 1.0 + double newTimePercent = ((double)newTime)/duration; + //0 <= newProgressSliderPos <= PROGRESS_SLIDER_SIZE + int newProgressSliderPos = (int)(PROGRESS_SLIDER_SIZE * newTimePercent); + progressSlider.setValue(newProgressSliderPos); + }//GEN-LAST:event_fastForwardButtonActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JLabel VolumeIcon; private javax.swing.JSlider audioSlider; private javax.swing.JPanel controlPanel; + private javax.swing.JButton fastForwardButton; private javax.swing.JLabel infoLabel; private javax.swing.JButton playButton; private javax.swing.JLabel progressLabel; private javax.swing.JSlider progressSlider; + private javax.swing.JButton rewindButton; private javax.swing.JPanel videoPanel; // End of variables declaration//GEN-END:variables } From b3aaf99c72c5cd75abd9a3a0237986a3a20666f2 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Wed, 9 Oct 2019 15:22:32 -0400 Subject: [PATCH 022/134] Incremental improvements --- .../contentviewers/MediaPlayerPanel.form | 186 ++++++++++------- .../contentviewers/MediaPlayerPanel.java | 191 +++++++++++------- 2 files changed, 230 insertions(+), 147 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form index 880528e787..c1c04e24c2 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form @@ -1,6 +1,6 @@ -
+ @@ -41,7 +41,7 @@ - + @@ -51,47 +51,33 @@ - - + + - - - - - + + + + + - - - - - - - - + - - - - - - + + + + - - - - - - - - - + + + + @@ -112,8 +98,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -122,48 +202,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java index 2ba5548724..06ce6f785d 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java @@ -53,7 +53,6 @@ import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.TskData; import javafx.embed.swing.JFXPanel; import javax.swing.event.ChangeListener; -import org.freedesktop.gstreamer.Clock; import org.freedesktop.gstreamer.GstException; /** @@ -242,12 +241,14 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie endOfStreamListener = new Bus.EOS() { @Override public void endOfStream(GstObject go) { - gstPlayBin.seek(ClockTime.ZERO); - progressSlider.setValue(0); + playButton.setText("►"); + System.out.println(gstPlayBin.getState()); + //gstPlayBin.seek(ClockTime.ZERO); + //progressSlider.setValue(0); /** * Keep the video from automatically playing */ - Gst.getExecutor().submit(() -> gstPlayBin.pause()); + //Gst.getExecutor().submit(() -> gstPlayBin.pause()); } }; } @@ -390,19 +391,13 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie return Bundle.MediaPlayerPanel_unknownTime(); } - double millis = ns / 1000000.0; - double seconds; - if (ceiling) { - seconds = Math.ceil(millis / 1000); - } else { - seconds = millis / 1000; - } - double hours = seconds / 3600; - seconds -= (int) hours * 3600; - double minutes = seconds / 60; - seconds -= (int) minutes * 60; + long seconds = TimeUnit.SECONDS.convert(ns, TimeUnit.NANOSECONDS); + long hours = TimeUnit.HOURS.convert(seconds, TimeUnit.SECONDS); + seconds -= TimeUnit.SECONDS.convert(hours, TimeUnit.HOURS); + long minutes = TimeUnit.MINUTES.convert(seconds, TimeUnit.SECONDS); + seconds -= TimeUnit.SECONDS.convert(minutes, TimeUnit.MINUTES); - return String.format(Bundle.MediaPlayerPanel_timeFormat(), (int) hours, (int) minutes, (int) seconds); + return String.format(Bundle.MediaPlayerPanel_timeFormat(), hours, minutes, seconds); } /** @@ -526,17 +521,19 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie @SuppressWarnings("unchecked") // //GEN-BEGIN:initComponents private void initComponents() { + java.awt.GridBagConstraints gridBagConstraints; videoPanel = new javax.swing.JPanel(); controlPanel = new javax.swing.JPanel(); progressSlider = new javax.swing.JSlider(); - infoLabel = new javax.swing.JLabel(); - playButton = new javax.swing.JButton(); progressLabel = new javax.swing.JLabel(); - VolumeIcon = new javax.swing.JLabel(); - audioSlider = new javax.swing.JSlider(); + jPanel1 = new javax.swing.JPanel(); + playButton = new javax.swing.JButton(); fastForwardButton = new javax.swing.JButton(); rewindButton = new javax.swing.JButton(); + VolumeIcon = new javax.swing.JLabel(); + audioSlider = new javax.swing.JSlider(); + infoLabel = new javax.swing.JLabel(); javax.swing.GroupLayout videoPanelLayout = new javax.swing.GroupLayout(videoPanel); videoPanel.setLayout(videoPanelLayout); @@ -546,7 +543,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie ); videoPanelLayout.setVerticalGroup( videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 257, Short.MAX_VALUE) + .addGap(0, 124, Short.MAX_VALUE) ); progressSlider.setValue(0); @@ -555,8 +552,9 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie progressSlider.setMinimumSize(new java.awt.Dimension(36, 21)); progressSlider.setPreferredSize(new java.awt.Dimension(200, 21)); - org.openide.awt.Mnemonics.setLocalizedText(infoLabel, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.infoLabel.text")); // NOI18N - infoLabel.setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR)); + org.openide.awt.Mnemonics.setLocalizedText(progressLabel, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.progressLabel.text")); // NOI18N + + jPanel1.setLayout(new java.awt.GridBagLayout()); org.openide.awt.Mnemonics.setLocalizedText(playButton, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.playButton.text")); // NOI18N playButton.addActionListener(new java.awt.event.ActionListener() { @@ -564,10 +562,50 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie playButtonActionPerformed(evt); } }); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 0; + gridBagConstraints.ipadx = 21; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(5, 6, 0, 0); + jPanel1.add(playButton, gridBagConstraints); - org.openide.awt.Mnemonics.setLocalizedText(progressLabel, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.progressLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(fastForwardButton, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.fastForwardButton.text")); // NOI18N + fastForwardButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + fastForwardButtonActionPerformed(evt); + } + }); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 0; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(5, 6, 0, 0); + jPanel1.add(fastForwardButton, gridBagConstraints); + + org.openide.awt.Mnemonics.setLocalizedText(rewindButton, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.rewindButton.text")); // NOI18N + rewindButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + rewindButtonActionPerformed(evt); + } + }); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(5, 0, 1, 0); + jPanel1.add(rewindButton, gridBagConstraints); org.openide.awt.Mnemonics.setLocalizedText(VolumeIcon, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.VolumeIcon.text")); // NOI18N + VolumeIcon.setHorizontalTextPosition(javax.swing.SwingConstants.LEFT); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 3; + gridBagConstraints.gridy = 0; + gridBagConstraints.ipadx = 8; + gridBagConstraints.ipady = 7; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(6, 6, 0, 0); + jPanel1.add(VolumeIcon, gridBagConstraints); audioSlider.setMajorTickSpacing(10); audioSlider.setMaximum(50); @@ -576,60 +614,45 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie audioSlider.setValue(25); audioSlider.setMinimumSize(new java.awt.Dimension(200, 21)); audioSlider.setPreferredSize(new java.awt.Dimension(200, 21)); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 4; + gridBagConstraints.gridy = 0; + gridBagConstraints.ipadx = -116; + gridBagConstraints.ipady = 7; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(3, 1, 0, 10); + jPanel1.add(audioSlider, gridBagConstraints); - org.openide.awt.Mnemonics.setLocalizedText(fastForwardButton, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.fastForwardButton.text")); // NOI18N - fastForwardButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - fastForwardButtonActionPerformed(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(rewindButton, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.rewindButton.text")); // NOI18N + infoLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT); + org.openide.awt.Mnemonics.setLocalizedText(infoLabel, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.infoLabel.text")); // NOI18N + infoLabel.setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR)); javax.swing.GroupLayout controlPanelLayout = new javax.swing.GroupLayout(controlPanel); controlPanel.setLayout(controlPanelLayout); controlPanelLayout.setHorizontalGroup( controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, controlPanelLayout.createSequentialGroup() + .addGroup(controlPanelLayout.createSequentialGroup() .addContainerGap() .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(controlPanelLayout.createSequentialGroup() - .addComponent(infoLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 246, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 73, Short.MAX_VALUE) - .addComponent(rewindButton) + .addComponent(infoLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel1, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 1090, Short.MAX_VALUE) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, controlPanelLayout.createSequentialGroup() + .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(playButton, javax.swing.GroupLayout.PREFERRED_SIZE, 64, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(fastForwardButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 191, Short.MAX_VALUE) - .addComponent(VolumeIcon, javax.swing.GroupLayout.PREFERRED_SIZE, 64, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addComponent(audioSlider, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) - .addComponent(progressLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addComponent(progressLabel))) .addContainerGap()) ); controlPanelLayout.setVerticalGroup( controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(controlPanelLayout.createSequentialGroup() + .addGap(0, 0, 0) .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) .addComponent(progressLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 23, Short.MAX_VALUE)) - .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(controlPanelLayout.createSequentialGroup() - .addGap(17, 17, 17) - .addComponent(infoLabel)) - .addGroup(controlPanelLayout.createSequentialGroup() - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(VolumeIcon, javax.swing.GroupLayout.PREFERRED_SIZE, 21, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(playButton) - .addComponent(fastForwardButton) - .addComponent(rewindButton)) - .addComponent(audioSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))) - .addContainerGap(13, Short.MAX_VALUE)) + .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(infoLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 28, Short.MAX_VALUE)) ); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); @@ -648,6 +671,28 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie ); }// //GEN-END:initComponents + private void rewindButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_rewindButtonActionPerformed + long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS); + long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); + //Skip 30 seconds. + long skipBehind = TimeUnit.NANOSECONDS.convert(SKIP_IN_SECONDS, TimeUnit.SECONDS); + //Ensure new video position is within bounds + long newTime = Math.max(currentTime - skipBehind, 0); + gstPlayBin.seek(newTime, TimeUnit.NANOSECONDS); + syncProgressSlider(newTime, duration); + }//GEN-LAST:event_rewindButtonActionPerformed + + private void fastForwardButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_fastForwardButtonActionPerformed + long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS); + long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); + //Skip 30 seconds. + long skipAhead = TimeUnit.NANOSECONDS.convert(SKIP_IN_SECONDS, TimeUnit.SECONDS); + //Ensure new video position is within bounds + long newTime = Math.min(currentTime + skipAhead, duration); + gstPlayBin.seek(newTime, TimeUnit.NANOSECONDS); + syncProgressSlider(newTime, duration); + }//GEN-LAST:event_fastForwardButtonActionPerformed + private void playButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_playButtonActionPerformed if (gstPlayBin.isPlaying()) { gstPlayBin.pause(); @@ -656,22 +701,21 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie } }//GEN-LAST:event_playButtonActionPerformed - private void fastForwardButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_fastForwardButtonActionPerformed - long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS); - Clock playbinClock = gstPlayBin.getClock(); - long currentTime = playbinClock.getTime(); - //Skip 30 seconds. - long skipAhead = TimeUnit.SECONDS.convert(SKIP_IN_SECONDS, TimeUnit.NANOSECONDS); - //Ensure new video position is within bounds - long newTime = Math.max(currentTime + skipAhead, duration); - gstPlayBin.seek(newTime, TimeUnit.NANOSECONDS); + /** + * Sync progress slider to the newTime position if it is not already there. + * + * @param newTime New time value that progress slider may or may not be positioned at + * @param duration Total duration of the video, used for determining slider position + */ + private void syncProgressSlider(long newTime, long duration) { //0 <= newTimePercent <= 1.0 double newTimePercent = ((double)newTime)/duration; //0 <= newProgressSliderPos <= PROGRESS_SLIDER_SIZE int newProgressSliderPos = (int)(PROGRESS_SLIDER_SIZE * newTimePercent); progressSlider.setValue(newProgressSliderPos); - }//GEN-LAST:event_fastForwardButtonActionPerformed - + updateTimeLabel(newTime, duration); + } + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JLabel VolumeIcon; @@ -679,6 +723,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie private javax.swing.JPanel controlPanel; private javax.swing.JButton fastForwardButton; private javax.swing.JLabel infoLabel; + private javax.swing.JPanel jPanel1; private javax.swing.JButton playButton; private javax.swing.JLabel progressLabel; private javax.swing.JSlider progressSlider; From 69631c809063c0196222b28eeaa67d717267cefa Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Wed, 9 Oct 2019 16:22:10 -0400 Subject: [PATCH 023/134] Initial commit of new geolocation window --- Core/ivy.xml | 4 + Core/nbproject/project.properties | 1 + Core/nbproject/project.xml | 4 + .../autopsy/geolocation/ArtifactWaypoint.java | 35 +++ .../autopsy/geolocation/Bundle.properties | 6 + .../geolocation/Bundle.properties-MERGED | 9 + .../geolocation/DefaultArtifactWaypoint.java | 89 +++++++ .../autopsy/geolocation/EXIFWaypoint.java | 76 ++++++ .../geolocation/GeolocationTopComponent.form | 28 ++ .../geolocation/GeolocationTopComponent.java | 250 ++++++++++++++++++ .../geolocation/GeolocationUtilities.java | 110 ++++++++ .../autopsy/geolocation/MapPanel.form | 89 +++++++ .../autopsy/geolocation/MapPanel.java | 239 +++++++++++++++++ .../geolocation/OpenGeolocationAction.java | 95 +++++++ .../autopsy/geolocation/RefreshPanel.form | 86 ++++++ .../autopsy/geolocation/RefreshPanel.java | 98 +++++++ .../sleuthkit/autopsy/geolocation/Route.java | 151 +++++++++++ .../geolocation/SimpleArtifactWaypoint.java | 58 ++++ .../images/arrow-circle-double-135.png | Bin 0 -> 864 bytes .../geolocation/images/cross-script.png | Bin 0 -> 623 bytes 20 files changed, 1428 insertions(+) create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/ArtifactWaypoint.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties-MERGED create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/DefaultArtifactWaypoint.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/EXIFWaypoint.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.form create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/GeolocationUtilities.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.form create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/OpenGeolocationAction.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/RefreshPanel.form create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/RefreshPanel.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/Route.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/SimpleArtifactWaypoint.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/images/arrow-circle-double-135.png create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/images/cross-script.png diff --git a/Core/ivy.xml b/Core/ivy.xml index 9ba4a3c371..699c3e8237 100644 --- a/Core/ivy.xml +++ b/Core/ivy.xml @@ -48,6 +48,10 @@ + + + + diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties index 2a952926ba..736428abc2 100644 --- a/Core/nbproject/project.properties +++ b/Core/nbproject/project.properties @@ -50,6 +50,7 @@ file.reference.jsoup-1.11.3.jar=release\\modules\\ext\\jsoup-1.11.3.jar file.reference.jul-to-slf4j-1.7.25.jar=release\\modules\\ext\\jul-to-slf4j-1.7.25.jar file.reference.juniversalchardet-1.0.3.jar=release\\modules\\ext\\juniversalchardet-1.0.3.jar file.reference.junrar-2.0.0.jar=release\\modules\\ext\\junrar-2.0.0.jar +file.reference.jxmapviewer2-2.4.jar=release\\modules\\ext\\jxmapviewer2-2.4.jar file.reference.jython-standalone-2.7.0.jar=release/modules/ext/jython-standalone-2.7.0.jar file.reference.mchange-commons-java-0.2.9.jar=release/modules/ext/mchange-commons-java-0.2.9.jar file.reference.metadata-extractor-2.11.0.jar=release\\modules\\ext\\metadata-extractor-2.11.0.jar diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index c33dabfbc7..3d465105fc 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -449,6 +449,10 @@ ext/commons-pool2-2.4.2.jar release/modules/ext/commons-pool2-2.4.2.jar + + ext/jxmapviewer2-2.4.jar + release/modules/ext/jxmapviewer2-2.4.jar + ext/jdom-2.0.5-contrib.jar release/modules/ext/jdom-2.0.5-contrib.jar diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/ArtifactWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/ArtifactWaypoint.java new file mode 100755 index 0000000000..3cb1fa14ea --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/ArtifactWaypoint.java @@ -0,0 +1,35 @@ +/* + * 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 org.jxmapviewer.viewer.Waypoint; +import org.sleuthkit.datamodel.BlackboardArtifact; + +/** + * ArtifactWaypoint interface. + */ +interface ArtifactWaypoint extends Waypoint{ + + String getLabel() ; + + BlackboardArtifact getArtifact() ; + + long getTimestamp() ; +} + \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties b/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties new file mode 100755 index 0000000000..7a22608a3b --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties @@ -0,0 +1,6 @@ +CTL_OpenGeolocation=Geolocation +CTL_GeolocationTopComponentAction=GeolocationTopComponent +CTL_GeolocationTopComponent=Geolocation +RefreshPanel.refreshLabel.text=The geolocation data has been updated, the visualization may be out of date. +RefreshPanel.refreshButton.text=Refresh View +RefreshPanel.closeButton.text= diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties-MERGED new file mode 100755 index 0000000000..5f7a6cd2f0 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties-MERGED @@ -0,0 +1,9 @@ +CTL_OpenGeolocation=Geolocation +CTL_GeolocationTopComponentAction=GeolocationTopComponent +CTL_GeolocationTopComponent=Geolocation +GLTopComponent_name=Geolocation +OpenGeolocationAction_displayName=Geolocation +OpenGeolocationAction_name=Geolocation +RefreshPanel.refreshLabel.text=The geolocation data has been updated, the visualization may be out of date. +RefreshPanel.refreshButton.text=Refresh View +RefreshPanel.closeButton.text= diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/DefaultArtifactWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/DefaultArtifactWaypoint.java new file mode 100755 index 0000000000..7bbc0a317f --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/DefaultArtifactWaypoint.java @@ -0,0 +1,89 @@ +/* + * 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 org.jxmapviewer.viewer.GeoPosition; +import org.sleuthkit.datamodel.BlackboardArtifact; + +/** + * Basic parent class for representing a Blackboard artifact waypoint. + * + */ +class DefaultArtifactWaypoint implements ArtifactWaypoint{ + private final BlackboardArtifact artifact; + private String label; + private long timestamp; + private GeoPosition position; + + /** + * Construct a default way point object. + * + * @param artifact The artifact that the waypoint is for. + */ + DefaultArtifactWaypoint(BlackboardArtifact artifact) { + this.artifact = artifact; + } + + @Override + public String getLabel() { + return label; + } + + void setLabel(String label) { + this.label = label; + } + + @Override + public BlackboardArtifact getArtifact() { + return artifact; + } + + @Override + public long getTimestamp() { + return timestamp; + } + + void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + @Override + public GeoPosition getPosition() { + return position; + } + + /** + * Set the GeoPosition for the waypoint + * + * @param position GeoPosition for the waypoint + */ + void setPosition(GeoPosition position) { + this.position = position; + } + + /** + * Create and set the GeoPosition for the way point. + * + * @param latitude double latitude value + * @param longitude double logitude value + */ + void setPosition(double latitude, double longitude) { + position = new GeoPosition(latitude, longitude); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/EXIFWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/EXIFWaypoint.java new file mode 100755 index 0000000000..54a93a812b --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/EXIFWaypoint.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.geolocation; + +import java.util.logging.Level; +import org.jxmapviewer.viewer.GeoPosition; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * EXIF way point object + */ +final class EXIFWaypoint extends DefaultArtifactWaypoint{ + + private static final Logger logger = Logger.getLogger(EXIFWaypoint.class.getName()); + + private AbstractFile imageFile; + + /** + * Construct a EXIF way point + * @param artifact + */ + EXIFWaypoint(BlackboardArtifact artifact) { + super(artifact); + initWaypoint(); + } + + /** + * Initialize the way point. + */ + private void initWaypoint() { + BlackboardArtifact artifact = getArtifact(); + + Double longitude = GeolocationUtilities.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE); + Double latitude = GeolocationUtilities.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE); + + if (longitude != null && latitude != null) { + setPosition(new GeoPosition(latitude, longitude)); + } else { + setPosition(null); + // No need to bother with other attributes if there are no + // location parameters + return; + } + + Long datetime = GeolocationUtilities.getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED); + if (datetime != null) { + setTimestamp(datetime * 1000); + } + + try { + imageFile = artifact.getSleuthkitCase().getAbstractFileById(artifact.getObjectID()); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, String.format("Failed to getAbstractFileByID for %d ", artifact.getObjectID()), ex); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.form b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.form new file mode 100755 index 0000000000..bddebcbc0c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.form @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java new file mode 100755 index 0000000000..7af2bae7ea --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java @@ -0,0 +1,250 @@ +/* + * 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.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.beans.PropertyChangeListener; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import javax.swing.SwingWorker; +import org.jxmapviewer.viewer.Waypoint; +import org.openide.util.NbBundle.Messages; +import org.openide.windows.RetainLocation; +import org.openide.windows.TopComponent; +import org.sleuthkit.autopsy.casemodule.Case; +import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_CASE; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; +import org.sleuthkit.autopsy.ingest.IngestManager; +import static org.sleuthkit.autopsy.ingest.IngestManager.IngestModuleEvent.DATA_ADDED; +import org.sleuthkit.autopsy.ingest.ModuleDataEvent; +import org.sleuthkit.datamodel.BlackboardArtifact; + +/** + * Top component which displays the Geolocation Tool. + * + */ +@TopComponent.Description(preferredID = "GeolocationTopComponent", persistenceType = TopComponent.PERSISTENCE_NEVER) +@TopComponent.Registration(mode = "geolocation", openAtStartup = false) +@RetainLocation("geolocation") +@SuppressWarnings("PMD.SingularField") +public final class GeolocationTopComponent extends TopComponent { + + private static final Logger logger = Logger.getLogger(GeolocationTopComponent.class.getName()); + + private static final Set INGEST_MODULE_EVENTS_OF_INTEREST = EnumSet.of(DATA_ADDED); + + private final PropertyChangeListener ingestListener; + + final RefreshPanel refreshPanel = new RefreshPanel(); + + @Messages({ + "GLTopComponent_name=Geolocation" + }) + + /** + * Creates new form GeoLocationTopComponent + */ + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) + public GeolocationTopComponent() { + initComponents(); + initWaypoints(); + setName(Bundle.GLTopComponent_name()); + + this.ingestListener = pce -> { + String eventType = pce.getPropertyName(); + if (eventType.equals(DATA_ADDED.toString())) { + // Indicate that a refresh may be needed, unless the data added is Keyword or Hashset hits + ModuleDataEvent eventData = (ModuleDataEvent) pce.getOldValue(); + if (null != eventData + && (eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT.getTypeID() + || eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH.getTypeID() + || eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION.getTypeID() + || eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE.getTypeID() + || eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF.getTypeID() + || eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK.getTypeID())) { + + showRefreshPanel(true); + } + } + }; + + + + refreshPanel.addCloseActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + showRefreshPanel(false); + } + }); + + refreshPanel.addRefreshActionListner(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + mapPanel.clearWaypoints(); + initWaypoints(); + showRefreshPanel(false); + } + }); + } + + @Override + public void addNotify() { + super.addNotify(); + IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, ingestListener); + Case.addEventTypeSubscriber(EnumSet.of(CURRENT_CASE), evt -> { + mapPanel.clearWaypoints(); + if(evt.getNewValue() != null) { + initWaypoints(); + } + }); + } + + @Override + public void removeNotify() { + super.removeNotify(); + IngestManager.getInstance().removeIngestModuleEventListener(ingestListener); + } + + /** + * Set the state of the refresh panel at the top of the mapPanel. + * + * @param show Whether to show or hide the panel. + */ + private void showRefreshPanel(boolean show) { + if(show) { + mapPanel.add(refreshPanel, BorderLayout.NORTH); + } else { + mapPanel.remove(refreshPanel); + } + mapPanel.revalidate(); + } + + /** + * Use a SwingWorker thread to find all of the artifacts that have GPS + * coordinates. + * + */ + private void initWaypoints() { + SwingWorker, Waypoint> worker = new SwingWorker, Waypoint>() { + @Override + protected List doInBackground() throws Exception { + List waypoints = new ArrayList<>(); + + Case currentCase; + try { + currentCase = Case.getCurrentCaseThrows(); + } catch (NoCurrentCaseException ex) { + logger.log(Level.WARNING, "Unable to get artifacts for geolocation window, no current open", ex); + // Popup a message or something? + return waypoints; + } + + // TSK_GPS_TRACKPOINT, TSK_GPS_SEARCH, TSK_GPS_LAST_KNOWN_LOCATION + // and TSK_GPS_BOOKMARK have similar attributes and can be processed + // similarly + List artifacts = new ArrayList<>(); + artifacts.addAll(currentCase.getSleuthkitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT)); + artifacts.addAll(currentCase.getSleuthkitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH)); + artifacts.addAll(currentCase.getSleuthkitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION)); + artifacts.addAll(currentCase.getSleuthkitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK)); + + for (BlackboardArtifact artifact : artifacts) { + Waypoint point = new SimpleArtifactWaypoint(artifact); + + if (point.getPosition() != null) { + waypoints.add(new SimpleArtifactWaypoint(artifact)); + } + } + + // Handle the TSK_GPS_ROUTE artifacts + List routes = currentCase.getSleuthkitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE); + for (BlackboardArtifact artifact : routes) { + Route route = new Route(artifact); + for (ArtifactWaypoint point : route.getRoute()) { + waypoints.add(point); + } + } + + artifacts = currentCase.getSleuthkitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF); + for (BlackboardArtifact artifact : artifacts) { + Waypoint point = new EXIFWaypoint(artifact); + + if (point.getPosition() != null) { + waypoints.add(new SimpleArtifactWaypoint(artifact)); + } + } + + return waypoints; + } + + @Override + protected void done() { + if(isDone() && !isCancelled()) { + try { + List waypoints = get(); + if(waypoints == null || waypoints.isEmpty()) { + return; + } + + for(Waypoint point: waypoints) { + mapPanel.addWaypoint(point); + } + + // 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 (InterruptedException | ExecutionException ex) { + logger.log(Level.WARNING, "Unable to add points to geolocation.", ex); + } + } + } + }; + + worker.execute(); + } + + /** + * 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() { + + mapPanel = new org.sleuthkit.autopsy.geolocation.MapPanel(); + + setLayout(new java.awt.BorderLayout()); + add(mapPanel, java.awt.BorderLayout.CENTER); + }// //GEN-END:initComponents + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private org.sleuthkit.autopsy.geolocation.MapPanel mapPanel; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationUtilities.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationUtilities.java new file mode 100755 index 0000000000..13514820d3 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationUtilities.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.util.logging.Level; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Helper function for managing the getting artifact information. These functions + * were grabbed from KMLReport. + * + */ +class GeolocationUtilities { + private static final Logger logger = Logger.getLogger(GeolocationUtilities.class.getName()); + /** + * Get a Double from an artifact if it exists, return null otherwise. + * + * @param artifact The artifact to query + * @param type The attribute type we're looking for + * + * @return The Double if it exists, or null if not + */ + static Double getDouble(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE type) { + Double returnValue = null; + try { + BlackboardAttribute bba = artifact.getAttribute(new BlackboardAttribute.Type(type)); + if (bba != null) { + Double value = bba.getValueDouble(); + returnValue = value; + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Error getting Double value: " + type.toString(), ex); //NON-NLS + } + return returnValue; + } + + /** + * Get a Long from an artifact if it exists, return null otherwise. + * + * @param artifact The artifact to query + * @param type The attribute type we're looking for + * + * @return The Long if it exists, or null if not + */ + static Long getLong(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE type) { + Long returnValue = null; + try { + BlackboardAttribute bba = artifact.getAttribute(new BlackboardAttribute.Type(type)); + if (bba != null) { + Long value = bba.getValueLong(); + returnValue = value; + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Error getting Long value: " + type.toString(), ex); //NON-NLS + } + return returnValue; + } + + /** + * Get an Integer from an artifact if it exists, return null otherwise. + * + * @param artifact The artifact to query + * @param type The attribute type we're looking for + * + * @return The Integer if it exists, or null if not + */ + static Integer getInteger(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE type) { + Integer returnValue = null; + try { + BlackboardAttribute bba = artifact.getAttribute(new BlackboardAttribute.Type(type)); + if (bba != null) { + Integer value = bba.getValueInt(); + returnValue = value; + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Error getting Integer value: " + type.toString(), ex); //NON-NLS + } + return returnValue; + } + + /** + * Get a String from an artifact if it exists, return null otherwise. + * + * @param artifact The artifact to query + * @param type The attribute type we're looking for + * + * @return The String if it exists, or null if not + */ + static String getString(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE type) { + String returnValue = null; + try { + BlackboardAttribute bba = artifact.getAttribute(new BlackboardAttribute.Type(type)); + if (bba != null) { + String value = bba.getValueString(); + if (value != null && !value.isEmpty()) { + returnValue = value; + } + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Error getting String value: " + type.toString(), ex); //NON-NLS + } + return returnValue; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.form b/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.form new file mode 100755 index 0000000000..ad0de45d6e --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.form @@ -0,0 +1,89 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java new file mode 100755 index 0000000000..8b22c16938 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java @@ -0,0 +1,239 @@ +/* + * 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.util.HashSet; +import java.util.Set; +import javax.swing.DefaultListModel; +import javax.swing.event.ListDataEvent; +import javax.swing.event.ListDataListener; +import javax.swing.event.MouseInputListener; +import org.jxmapviewer.OSMTileFactoryInfo; +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.TileFactoryInfo; +import org.jxmapviewer.viewer.Waypoint; +import org.jxmapviewer.viewer.WaypointPainter; + +/** + * Main panel with the JJXMapViewer object and its basic controls. + */ +public class MapPanel extends javax.swing.JPanel { + + private boolean zoomChanging = false; + private final boolean sliderReversed = false; + + // Using a DefaultListModel to store the way points because we get + // a lot of functionality for free, like listeners. + private final DefaultListModel waypointListModel; + + /** + * Creates new form MapPanel + */ + public MapPanel() { + waypointListModel = new DefaultListModel<>(); + initComponents(); + initMap(); + } + + /** + * Initialize the map. + */ + private void initMap() { + + TileFactoryInfo info = new OSMTileFactoryInfo(); + DefaultTileFactory tileFactory = new DefaultTileFactory(info); + mapViewer.setTileFactory(tileFactory); + + // Add Mouse interactions + MouseInputListener mia = new PanMouseInputListener(mapViewer); + mapViewer.addMouseListener(mia); + mapViewer.addMouseMotionListener(mia); + + mapViewer.addMouseListener(new CenterMapListener(mapViewer)); + mapViewer.addMouseWheelListener(new ZoomMouseWheelListenerCursor(mapViewer)); + + // Listen to the map for a change in zoom so that we can update the slider. + mapViewer.addPropertyChangeListener("zoom", new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + zoomSlider.setValue(mapViewer.getZoom()); + } + }); + + zoomSlider.setMinimum(tileFactory.getInfo().getMinimumZoomLevel()); + zoomSlider.setMaximum(tileFactory.getInfo().getMaximumZoomLevel()); + + + mapViewer.setZoom(tileFactory.getInfo().getMaximumZoomLevel()- 1); + mapViewer.setAddressLocation(new GeoPosition(0, 0)); + + // Listener for new way points being added to the map. + waypointListModel.addListDataListener(new ListDataListener() { + @Override + public void intervalAdded(ListDataEvent e) { + mapViewer.repaint(); + } + + @Override + public void intervalRemoved(ListDataEvent e) { + mapViewer.repaint(); + } + + @Override + public void contentsChanged(ListDataEvent e) { + mapViewer.repaint(); + } + + }); + + // Basic painters for the way points. + WaypointPainter waypointPainter = new WaypointPainter() { + @Override + public Set getWaypoints() { + Set set = new HashSet<>(); + for (int index = 0; index < waypointListModel.getSize(); index++) { + set.add(waypointListModel.get(index)); + } + return set; + } + }; + + mapViewer.setOverlayPainter(waypointPainter); + } + + /** + * Add a way point to the map. + * + * @param waypoint + */ + void addWaypoint(Waypoint waypoint) { + waypointListModel.addElement(waypoint); + } + + void setCenterLocation(Waypoint waypoint) { + mapViewer.setCenterPosition(waypoint.getPosition()); + } + + /** + * Set the current zoom level. + * + * @param zoom + */ + void setZoom(int zoom) { + zoomChanging = true; + mapViewer.setZoom(zoom); + if (sliderReversed) { + zoomSlider.setValue(zoomSlider.getMaximum() - zoom); + } else { + zoomSlider.setValue(zoom); + } + zoomChanging = false; + } + + /** + * Remove all of the way points from the map. + */ + void clearWaypoints() { + waypointListModel.removeAllElements(); + } + + /** + * 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; + + mapViewer = new org.jxmapviewer.JXMapViewer(); + zoomPanel = new javax.swing.JPanel(); + zoomSlider = new javax.swing.JSlider(); + + setFocusable(false); + setLayout(new java.awt.BorderLayout()); + + mapViewer.setLayout(new java.awt.GridBagLayout()); + + zoomPanel.setFocusable(false); + zoomPanel.setOpaque(false); + zoomPanel.setRequestFocusEnabled(false); + + zoomSlider.setMaximum(15); + zoomSlider.setMinimum(10); + zoomSlider.setMinorTickSpacing(1); + zoomSlider.setOrientation(javax.swing.JSlider.VERTICAL); + zoomSlider.setPaintTicks(true); + zoomSlider.setSnapToTicks(true); + zoomSlider.setMinimumSize(new java.awt.Dimension(35, 100)); + zoomSlider.setOpaque(false); + zoomSlider.setPreferredSize(new java.awt.Dimension(35, 190)); + zoomSlider.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + zoomSliderStateChanged(evt); + } + }); + + javax.swing.GroupLayout zoomPanelLayout = new javax.swing.GroupLayout(zoomPanel); + zoomPanel.setLayout(zoomPanelLayout); + zoomPanelLayout.setHorizontalGroup( + zoomPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(zoomPanelLayout.createSequentialGroup() + .addGap(0, 0, 0) + .addComponent(zoomSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + zoomPanelLayout.setVerticalGroup( + zoomPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, zoomPanelLayout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(zoomSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0)) + ); + + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.weighty = 1.0; + gridBagConstraints.insets = new java.awt.Insets(4, 4, 4, 4); + mapViewer.add(zoomPanel, gridBagConstraints); + + add(mapViewer, java.awt.BorderLayout.CENTER); + }// //GEN-END:initComponents + + private void zoomSliderStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_zoomSliderStateChanged + if (!zoomChanging) { + setZoom(zoomSlider.getValue()); + } + }//GEN-LAST:event_zoomSliderStateChanged + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private org.jxmapviewer.JXMapViewer mapViewer; + private javax.swing.JPanel zoomPanel; + private javax.swing.JSlider zoomSlider; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/OpenGeolocationAction.java b/Core/src/org/sleuthkit/autopsy/geolocation/OpenGeolocationAction.java new file mode 100755 index 0000000000..2e9ffa0f76 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/OpenGeolocationAction.java @@ -0,0 +1,95 @@ +/* + * 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 org.openide.awt.ActionID; +import org.openide.awt.ActionReference; +import org.openide.awt.ActionReferences; +import org.openide.awt.ActionRegistration; +import org.openide.util.HelpCtx; +import org.openide.util.NbBundle.Messages; +import org.openide.util.actions.CallableSystemAction; +import org.openide.windows.TopComponent; +import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.core.RuntimeProperties; + +/** + * Action that opens the Geolocation window. Available through the Tools menu. + * + */ +@ActionID(category = "Tools", + id = "org.sleuthkit.autopsy.geolocation.OpenGeolocationAction") +@ActionRegistration(displayName = "#CTL_OpenGeolocation", lazy = false) +@ActionReferences(value = { + @ActionReference(path = "Menu/Tools", position = 102)}) +public class OpenGeolocationAction extends CallableSystemAction { + + private final PropertyChangeListener caseChangeListener; + + @Messages({ + "OpenGeolocationAction_name=Geolocation", + "OpenGeolocationAction_displayName=Geolocation" + }) + + /** + * Constructs the new action of opening the Geolocation window. + */ + public OpenGeolocationAction() { + setEnabled(false); //disabled by default. Will be enabled in Case.java when a case is opened. + + caseChangeListener = (PropertyChangeEvent evt) -> { + if (evt.getPropertyName().equals(Case.Events.CURRENT_CASE.toString())) { + setEnabled(RuntimeProperties.runningWithGUI() && evt.getNewValue() != null); + } + }; + + Case.addPropertyChangeListener(caseChangeListener); + } + + @Override + public void performAction() { + final TopComponent topComponent = WindowManager.getDefault().findTopComponent("GeolocationTopComponent"); + if (topComponent != null) { + if (topComponent.isOpened() == false) { + topComponent.open(); + } + topComponent.toFront(); + topComponent.requestActive(); + } + } + + @Override + public String getName() { + return Bundle.OpenGeolocationAction_displayName(); + } + + @Override + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } + + @Override + public boolean asynchronous() { + return false; // run on edt + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/RefreshPanel.form b/Core/src/org/sleuthkit/autopsy/geolocation/RefreshPanel.form new file mode 100755 index 0000000000..400c788d42 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/RefreshPanel.form @@ -0,0 +1,86 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/RefreshPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/RefreshPanel.java new file mode 100755 index 0000000000..29db4fb2bd --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/RefreshPanel.java @@ -0,0 +1,98 @@ +/* + * 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.event.ActionListener; + +/** + * + * + */ +public class RefreshPanel extends javax.swing.JPanel { + + /** + * Creates new form RefreshPanel + */ + public RefreshPanel() { + initComponents(); + } + + void addCloseActionListener(ActionListener listener) { + closeButton.addActionListener(listener); + } + + void addRefreshActionListner(ActionListener listener) { + refreshButton.addActionListener(listener); + } + + void removeCloseActionListner(ActionListener listener) { + closeButton.removeActionListener(listener); + } + + void removeRefreshActionListner(ActionListener listener) { + refreshButton.removeActionListener(listener); + } + + /** + * 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; + + refreshLabel = new javax.swing.JLabel(); + refreshButton = new javax.swing.JButton(); + closeButton = new javax.swing.JButton(); + + setBackground(new java.awt.Color(0, 51, 51)); + setLayout(new java.awt.GridBagLayout()); + + refreshLabel.setForeground(new java.awt.Color(255, 255, 255)); + refreshLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/warning16.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(refreshLabel, org.openide.util.NbBundle.getMessage(RefreshPanel.class, "RefreshPanel.refreshLabel.text")); // NOI18N + 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(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 + 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 + 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); + closeButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + closeButtonActionPerformed(evt); + } + }); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.insets = new java.awt.Insets(0, 5, 0, 5); + add(closeButton, gridBagConstraints); + }// //GEN-END:initComponents + + private void closeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_closeButtonActionPerformed + + }//GEN-LAST:event_closeButtonActionPerformed + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton closeButton; + private javax.swing.JButton refreshButton; + private javax.swing.JLabel refreshLabel; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/Route.java b/Core/src/org/sleuthkit/autopsy/geolocation/Route.java new file mode 100755 index 0000000000..55b1af4586 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/Route.java @@ -0,0 +1,151 @@ +/* + * 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.List; +import org.jxmapviewer.viewer.GeoPosition; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; + +/** + * A route is a set of Geolocations with a common parent artifact; + * + */ +class Route { + + private final BlackboardArtifact artifact; + private final List waypoints; + private long timestamp; + + /** + * Construct a route for the given artifact. + * + * @param artifact TSK_GPS_ROUTE artifact object + */ + Route(BlackboardArtifact artifact) { + this.artifact = artifact; + waypoints = new ArrayList<>(); + initRoute(); + } + + /** + * Construct a route with the given artifact and list of way points. + * + * @param artifact TSK_GPS_ROUTE artifact object + * @param waypoints List of waypoints for this route + */ + Route(BlackboardArtifact artifact, List waypoints) { + this.artifact = artifact; + this.waypoints = waypoints; + } + + /** + * Add a way point to the route. + * + * @param point Waypoint to add to the route. + */ + void addWaypoint(ArtifactWaypoint point) { + waypoints.add(point); + } + + /** + * Get the list of way points for this route; + * + * @return List of ArtifactWaypoints for this route + */ + List getRoute() { + return waypoints; + } + + /** + * Initialize the route. + */ + private void initRoute() { + if (artifact == null) { + return; + } + + // Get the start logitude and latitude + Double latitude = GeolocationUtilities.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START); + Double longitude = GeolocationUtilities.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START); + + if (latitude != null && longitude != null) { + addWaypoint(new RouteWaypoint(this, latitude, longitude, "Start")); + } + + // Get the end logitude and latitude + latitude = GeolocationUtilities.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END); + longitude = GeolocationUtilities.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END); + + if (latitude != null && longitude != null) { + addWaypoint(new RouteWaypoint(this, latitude, longitude, "End")); + } + + // Get the creation date + Long dateTime = GeolocationUtilities.getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME); + if (dateTime != null) { + timestamp = dateTime * 1000; + } + } + + /** + * Waypoint object for routes. + */ + class RouteWaypoint implements ArtifactWaypoint { + + private final Route parent; + private final GeoPosition position; + private final String label; + + /** + * Construct a route way point. + * + * @param parent The parent route object. + * @param latitude Latitude for waypoint + * @param longitude Longitude for waypoint + * @param label Way point label. + */ + RouteWaypoint(Route parent, double latitude, double longitude, String label) { + this.parent = parent; + this.position = new GeoPosition(latitude, longitude); + this.label = label; + } + + @Override + public String getLabel() { + return label; + } + + @Override + public BlackboardArtifact getArtifact() { + return parent.artifact; + } + + @Override + public long getTimestamp() { + return parent.timestamp; + } + + @Override + public GeoPosition getPosition() { + return position; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/SimpleArtifactWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/SimpleArtifactWaypoint.java new file mode 100755 index 0000000000..5c06b75e31 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/SimpleArtifactWaypoint.java @@ -0,0 +1,58 @@ +/* + * 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 org.jxmapviewer.viewer.GeoPosition; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; + +/** + * This is a wrapper for artifacts with the basic set of geolocation attributes. + */ +class SimpleArtifactWaypoint extends DefaultArtifactWaypoint { + + SimpleArtifactWaypoint(BlackboardArtifact artifact) { + super(artifact); + initWaypoint(); + } + + /** + * Initialize the waypoint basic information. + */ + private void initWaypoint() { + BlackboardArtifact artifact = getArtifact(); + + Double longitude = GeolocationUtilities.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE); + Double latitude = GeolocationUtilities.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE); + + if (longitude != null && latitude != null) { + setPosition(new GeoPosition(latitude, longitude)); + } else { + setPosition(null); + // No need to bother with other attributes if there are no + // location parameters + return; + } + + Long datetime = GeolocationUtilities.getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME); + if (datetime != null) { + setTimestamp(datetime * 1000); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/images/arrow-circle-double-135.png b/Core/src/org/sleuthkit/autopsy/geolocation/images/arrow-circle-double-135.png new file mode 100755 index 0000000000000000000000000000000000000000..4f40ba52062c62295a89856c8459052c0b78eadb GIT binary patch literal 864 zcmV-m1E2hfP)RAm^3ulMxKna&KIw$o|t$6$e0ZEG|phiWCMQ9(+YsAv#fxHYh-3}{$6em-`;9HZz2|fwY>x$fOC!yjt+&+xw*ON{ObqusX{`jv^6o- z!~{wu4-TZV^RM;~2ENQ9GW53!))*Q!&B@uP!kq_gGMNO|O4Zek?N$wFK0TP$6QkYn zZVQed&S$^9v^gblToDoD130bG+E6KLasz43U05vwS48AEV=a zYexDKnbJnP54h_A7_C8sMBve=?Q-haXlS&aFwXa6 zdET;!gzuf22`f7qfOMzN`R}einbB(faC56_DkN0j*#04F%xMvsZ#SFlwpt5T>nn?2 zFBNSv$!bH`)(WdNR_n(AV=C*VJI&6X3|oFi#@?El>K@5|A#uFqti9GfoIml*yGO&H zcc7=gi<%c;wB|2~aTFP6%+2EB`NH@|Z?oA-dR{nP7Nzwc4_)Yt=t~DcusSF9UM` z(T`sp_8&{_h|4RvxV+LC^&Y%*@7D1$`N{kEoedyEWY?&cZphk&WhvjRdEqA&lG&Hy q`zwXL1RjtA(m>)rd~pOg!2St{?uz8+#hTgx0000uz-;<4aiE#*F2T#| zt;WP8_2kp17jJ+5TnKdXT%g9FN8i3}lmYYqfHOcjj~~!Ph-`p^4LiH!rC-0^KL7i72TcG6TZ`7-bKTQXvCc;3)#dB$oaEf1Gi^b0a{20RY5Z;(@&ISGfQH002ov JPDHLkV1iqyA&vk5 literal 0 HcmV?d00001 From baa80cd85985d1c60c596db768b50688f0fe9f13 Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Wed, 9 Oct 2019 16:41:35 -0400 Subject: [PATCH 024/134] updated layer.xml for geolocation window --- .../org/sleuthkit/autopsy/core/geolocationWsmode.xml | 11 +++++++++++ Core/src/org/sleuthkit/autopsy/core/layer.xml | 1 + 2 files changed, 12 insertions(+) create mode 100755 Core/src/org/sleuthkit/autopsy/core/geolocationWsmode.xml diff --git a/Core/src/org/sleuthkit/autopsy/core/geolocationWsmode.xml b/Core/src/org/sleuthkit/autopsy/core/geolocationWsmode.xml new file mode 100755 index 0000000000..4e29bb3e2d --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/core/geolocationWsmode.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/core/layer.xml b/Core/src/org/sleuthkit/autopsy/core/layer.xml index 6a9673a6d3..630af198a2 100644 --- a/Core/src/org/sleuthkit/autopsy/core/layer.xml +++ b/Core/src/org/sleuthkit/autopsy/core/layer.xml @@ -448,6 +448,7 @@ + From b2d4e220301f7606e1a2f158a00c6ee93173eed3 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Thu, 10 Oct 2019 15:09:52 -0400 Subject: [PATCH 025/134] Finished implementing and testing fast forward/rewind buttons --- .../contentviewers/MediaPlayerPanel.form | 2 +- .../contentviewers/MediaPlayerPanel.java | 37 +++---------------- 2 files changed, 7 insertions(+), 32 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form index c1c04e24c2..449e3c09ea 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form @@ -55,7 +55,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java index 06ce6f785d..98f3350e87 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java @@ -37,7 +37,6 @@ import javax.swing.SwingWorker; import javax.swing.Timer; import javax.swing.event.ChangeEvent; import org.freedesktop.gstreamer.Bus; -import org.freedesktop.gstreamer.ClockTime; import org.freedesktop.gstreamer.Gst; import org.freedesktop.gstreamer.GstObject; import org.freedesktop.gstreamer.State; @@ -53,6 +52,7 @@ import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.TskData; import javafx.embed.swing.JFXPanel; import javax.swing.event.ChangeListener; +import org.freedesktop.gstreamer.ClockTime; import org.freedesktop.gstreamer.GstException; /** @@ -241,14 +241,8 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie endOfStreamListener = new Bus.EOS() { @Override public void endOfStream(GstObject go) { - playButton.setText("►"); - System.out.println(gstPlayBin.getState()); - //gstPlayBin.seek(ClockTime.ZERO); - //progressSlider.setValue(0); - /** - * Keep the video from automatically playing - */ - //Gst.getExecutor().submit(() -> gstPlayBin.pause()); + Gst.getExecutor().submit(() -> gstPlayBin.pause()); + gstPlayBin.seek(ClockTime.ZERO); } }; } @@ -376,7 +370,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie * @param total */ private void updateTimeLabel(long start, long total) { - progressLabel.setText(formatTime(start, false) + "/" + formatTime(total, true)); + progressLabel.setText(formatTime(start) + "/" + formatTime(total)); } /** @@ -386,7 +380,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie "MediaPlayerPanel.unknownTime=Unknown", "MediaPlayerPanel.timeFormat=%02d:%02d:%02d" }) - private String formatTime(long ns, boolean ceiling) { + private String formatTime(long ns) { if (ns == -1) { return Bundle.MediaPlayerPanel_unknownTime(); } @@ -635,7 +629,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie .addContainerGap() .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(infoLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(jPanel1, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 1090, Short.MAX_VALUE) + .addComponent(jPanel1, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 738, Short.MAX_VALUE) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, controlPanelLayout.createSequentialGroup() .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) @@ -672,14 +666,12 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie }// //GEN-END:initComponents private void rewindButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_rewindButtonActionPerformed - long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS); long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); //Skip 30 seconds. long skipBehind = TimeUnit.NANOSECONDS.convert(SKIP_IN_SECONDS, TimeUnit.SECONDS); //Ensure new video position is within bounds long newTime = Math.max(currentTime - skipBehind, 0); gstPlayBin.seek(newTime, TimeUnit.NANOSECONDS); - syncProgressSlider(newTime, duration); }//GEN-LAST:event_rewindButtonActionPerformed private void fastForwardButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_fastForwardButtonActionPerformed @@ -690,7 +682,6 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie //Ensure new video position is within bounds long newTime = Math.min(currentTime + skipAhead, duration); gstPlayBin.seek(newTime, TimeUnit.NANOSECONDS); - syncProgressSlider(newTime, duration); }//GEN-LAST:event_fastForwardButtonActionPerformed private void playButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_playButtonActionPerformed @@ -701,22 +692,6 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie } }//GEN-LAST:event_playButtonActionPerformed - /** - * Sync progress slider to the newTime position if it is not already there. - * - * @param newTime New time value that progress slider may or may not be positioned at - * @param duration Total duration of the video, used for determining slider position - */ - private void syncProgressSlider(long newTime, long duration) { - //0 <= newTimePercent <= 1.0 - double newTimePercent = ((double)newTime)/duration; - //0 <= newProgressSliderPos <= PROGRESS_SLIDER_SIZE - int newProgressSliderPos = (int)(PROGRESS_SLIDER_SIZE * newTimePercent); - progressSlider.setValue(newProgressSliderPos); - updateTimeLabel(newTime, duration); - } - - // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JLabel VolumeIcon; private javax.swing.JSlider audioSlider; From 2b946af86966db1504da71ad5225e23f6176faa4 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Thu, 10 Oct 2019 16:07:23 -0400 Subject: [PATCH 026/134] Disallow seeking to immediate end (buggy when doing so), add budnle files --- .../autopsy/contentviewers/Bundle.properties | 8 ++-- .../contentviewers/Bundle.properties-MERGED | 8 ++-- .../contentviewers/MediaPlayerPanel.form | 6 +-- .../contentviewers/MediaPlayerPanel.java | 48 +++++++++++-------- 4 files changed, 40 insertions(+), 30 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties index c799a17d61..5734df98d9 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties @@ -84,9 +84,11 @@ MediaViewImagePanel.zoomTextField.text= MediaViewImagePanel.rotationTextField.text= MediaViewImagePanel.rotateLeftButton.toolTipText= HtmlPanel.showImagesToggleButton.text=Download Images -MediaPlayerPanel.audioSlider.toolTipText= -MediaPlayerPanel.VolumeIcon.text=\ \ \ \ \ Volume +MediaViewImagePanel.tagsMenu.text_1=Tags Menu MediaPlayerPanel.progressLabel.text=00:00:00/00:00:00 +MediaPlayerPanel.audioSlider.toolTipText= +MediaPlayerPanel.rewindButton.text=\u2bc7\u2bc7 +MediaPlayerPanel.fastForwardButton.text=\u25ba\u25ba MediaPlayerPanel.playButton.text=\u25ba MediaPlayerPanel.infoLabel.text=No Errors -MediaViewImagePanel.tagsMenu.text_1=Tags Menu +MediaPlayerPanel.VolumeIcon.text=\ \ \ \ \ Volume diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED index ff3341b60f..1209c49953 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED @@ -154,12 +154,14 @@ MediaViewImagePanel.zoomTextField.text= MediaViewImagePanel.rotationTextField.text= MediaViewImagePanel.rotateLeftButton.toolTipText= HtmlPanel.showImagesToggleButton.text=Download Images -MediaPlayerPanel.audioSlider.toolTipText= -MediaPlayerPanel.VolumeIcon.text=\ \ \ \ \ Volume +MediaViewImagePanel.tagsMenu.text_1=Tags Menu MediaPlayerPanel.progressLabel.text=00:00:00/00:00:00 +MediaPlayerPanel.audioSlider.toolTipText= +MediaPlayerPanel.rewindButton.text=\u2bc7\u2bc7 +MediaPlayerPanel.fastForwardButton.text=\u25ba\u25ba MediaPlayerPanel.playButton.text=\u25ba MediaPlayerPanel.infoLabel.text=No Errors -MediaViewImagePanel.tagsMenu.text_1=Tags Menu +MediaPlayerPanel.VolumeIcon.text=\ \ \ \ \ Volume # {0} - tableName SQLiteViewer.readTable.errorText=Error getting rows for table: {0} # {0} - tableName diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form index 449e3c09ea..230254020d 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form @@ -55,7 +55,7 @@ - + @@ -75,7 +75,7 @@ - + @@ -105,7 +105,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java index 98f3350e87..1d02dfcd2b 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java @@ -241,8 +241,12 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie endOfStreamListener = new Bus.EOS() { @Override public void endOfStream(GstObject go) { + gstPlayBin.seek(ClockTime.ZERO); + progressSlider.setValue(0); + /** + * Keep the video from automatically playing + */ Gst.getExecutor().submit(() -> gstPlayBin.pause()); - gstPlayBin.seek(ClockTime.ZERO); } }; } @@ -265,7 +269,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie try { //Pushing off initialization to the background extractMediaWorker = new ExtractMedia(file, VideoUtils.getVideoFileInTempDir(file)); - extractMediaWorker.execute(); + extractMediaWorker.execute(); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS infoLabel.setText(String.format("%s", Bundle.GstVideoPanel_noOpenCase_errMsg())); @@ -314,6 +318,8 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie progressSlider.setEnabled(isEnabled); videoPanel.setEnabled(isEnabled); audioSlider.setEnabled(isEnabled); + rewindButton.setEnabled(isEnabled); + fastForwardButton.setEnabled(isEnabled); } @Override @@ -434,8 +440,8 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie protected void done() { try { super.get(); - - if(this.isCancelled()) { + + if (this.isCancelled()) { return; } @@ -451,8 +457,8 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie playBinBus.connect(endOfStreamListener); playBinBus.connect(stateChangeListener); playBinBus.connect(errorListener); - - if(this.isCancelled()) { + + if (this.isCancelled()) { return; } @@ -462,14 +468,14 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie videoPanel.add(fxPanel); fxAppSink = new JavaFxAppSink("JavaFxAppSink", fxPanel); gstPlayBin.setVideoSink(fxAppSink); - - if(this.isCancelled()) { + + if (this.isCancelled()) { return; } gstPlayBin.setVolume((audioSlider.getValue() * 2.0) / 100.0); gstPlayBin.pause(); - + timer.start(); enableComponents(true); } catch (CancellationException ex) { @@ -497,7 +503,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie * pipeline. We start this updater when data-flow has just been * initiated so buffering may still be in progress. */ - if (duration != -1) { + if (duration > 0 && position > 0) { double relativePosition = (double) position / duration; progressSlider.setValue((int) (relativePosition * PROGRESS_SLIDER_SIZE)); } @@ -521,7 +527,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie controlPanel = new javax.swing.JPanel(); progressSlider = new javax.swing.JSlider(); progressLabel = new javax.swing.JLabel(); - jPanel1 = new javax.swing.JPanel(); + buttonPanel = new javax.swing.JPanel(); playButton = new javax.swing.JButton(); fastForwardButton = new javax.swing.JButton(); rewindButton = new javax.swing.JButton(); @@ -548,7 +554,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie org.openide.awt.Mnemonics.setLocalizedText(progressLabel, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.progressLabel.text")); // NOI18N - jPanel1.setLayout(new java.awt.GridBagLayout()); + buttonPanel.setLayout(new java.awt.GridBagLayout()); org.openide.awt.Mnemonics.setLocalizedText(playButton, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.playButton.text")); // NOI18N playButton.addActionListener(new java.awt.event.ActionListener() { @@ -562,7 +568,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie gridBagConstraints.ipadx = 21; gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; gridBagConstraints.insets = new java.awt.Insets(5, 6, 0, 0); - jPanel1.add(playButton, gridBagConstraints); + buttonPanel.add(playButton, gridBagConstraints); org.openide.awt.Mnemonics.setLocalizedText(fastForwardButton, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.fastForwardButton.text")); // NOI18N fastForwardButton.addActionListener(new java.awt.event.ActionListener() { @@ -575,7 +581,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie gridBagConstraints.gridy = 0; gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; gridBagConstraints.insets = new java.awt.Insets(5, 6, 0, 0); - jPanel1.add(fastForwardButton, gridBagConstraints); + buttonPanel.add(fastForwardButton, gridBagConstraints); org.openide.awt.Mnemonics.setLocalizedText(rewindButton, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.rewindButton.text")); // NOI18N rewindButton.addActionListener(new java.awt.event.ActionListener() { @@ -588,7 +594,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie gridBagConstraints.gridy = 0; gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; gridBagConstraints.insets = new java.awt.Insets(5, 0, 1, 0); - jPanel1.add(rewindButton, gridBagConstraints); + buttonPanel.add(rewindButton, gridBagConstraints); org.openide.awt.Mnemonics.setLocalizedText(VolumeIcon, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.VolumeIcon.text")); // NOI18N VolumeIcon.setHorizontalTextPosition(javax.swing.SwingConstants.LEFT); @@ -599,7 +605,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie gridBagConstraints.ipady = 7; gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; gridBagConstraints.insets = new java.awt.Insets(6, 6, 0, 0); - jPanel1.add(VolumeIcon, gridBagConstraints); + buttonPanel.add(VolumeIcon, gridBagConstraints); audioSlider.setMajorTickSpacing(10); audioSlider.setMaximum(50); @@ -615,7 +621,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie gridBagConstraints.ipady = 7; gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; gridBagConstraints.insets = new java.awt.Insets(3, 1, 0, 10); - jPanel1.add(audioSlider, gridBagConstraints); + buttonPanel.add(audioSlider, gridBagConstraints); infoLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT); org.openide.awt.Mnemonics.setLocalizedText(infoLabel, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.infoLabel.text")); // NOI18N @@ -629,7 +635,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie .addContainerGap() .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(infoLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(jPanel1, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 738, Short.MAX_VALUE) + .addComponent(buttonPanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 738, Short.MAX_VALUE) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, controlPanelLayout.createSequentialGroup() .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) @@ -644,7 +650,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie .addComponent(progressLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(buttonPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(infoLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 28, Short.MAX_VALUE)) ); @@ -680,7 +686,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie //Skip 30 seconds. long skipAhead = TimeUnit.NANOSECONDS.convert(SKIP_IN_SECONDS, TimeUnit.SECONDS); //Ensure new video position is within bounds - long newTime = Math.min(currentTime + skipAhead, duration); + long newTime = Math.min(currentTime + skipAhead, duration - 1); gstPlayBin.seek(newTime, TimeUnit.NANOSECONDS); }//GEN-LAST:event_fastForwardButtonActionPerformed @@ -695,10 +701,10 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JLabel VolumeIcon; private javax.swing.JSlider audioSlider; + private javax.swing.JPanel buttonPanel; private javax.swing.JPanel controlPanel; private javax.swing.JButton fastForwardButton; private javax.swing.JLabel infoLabel; - private javax.swing.JPanel jPanel1; private javax.swing.JButton playButton; private javax.swing.JLabel progressLabel; private javax.swing.JSlider progressSlider; From d9dea468f394addbf6bb946b9046763307d1d00f Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Fri, 11 Oct 2019 12:14:13 -0400 Subject: [PATCH 027/134] Added tutorial one to docs --- docs/doxygen/Doxyfile | 4 +- docs/doxygen/images/bigAndRoundFiles.png | Bin 0 -> 31278 bytes docs/doxygen/images/demoScript_folder.png | Bin 0 -> 7417 bytes docs/doxygen/images/ingest-modules.PNG | Bin 0 -> 43805 bytes docs/doxygen/{ => images}/viewer_image.JPG | Bin docs/doxygen/main.dox | 4 +- docs/doxygen/modDSIngestTutorial.dox | 5 + docs/doxygen/modFileIngestTutorial.dox | 154 +++++++++++++++++++++ 8 files changed, 164 insertions(+), 3 deletions(-) create mode 100644 docs/doxygen/images/bigAndRoundFiles.png create mode 100644 docs/doxygen/images/demoScript_folder.png create mode 100644 docs/doxygen/images/ingest-modules.PNG rename docs/doxygen/{ => images}/viewer_image.JPG (100%) mode change 100755 => 100644 create mode 100644 docs/doxygen/modDSIngestTutorial.dox create mode 100644 docs/doxygen/modFileIngestTutorial.dox diff --git a/docs/doxygen/Doxyfile b/docs/doxygen/Doxyfile index b640c41549..0037b15063 100644 --- a/docs/doxygen/Doxyfile +++ b/docs/doxygen/Doxyfile @@ -772,6 +772,8 @@ INPUT = main.dox \ regressionTesting.dox \ native_libs.dox \ modDevPython.dox \ + modFileIngestTutorial.dox \ + modDSIngestTutorial.dox \ debugTsk.dox \ ../../Core/src \ ../../CoreLibs/src \ @@ -867,7 +869,7 @@ EXAMPLE_RECURSIVE = NO # that contain images that are to be included in the documentation (see the # \image command). -IMAGE_PATH = . +IMAGE_PATH = images/ # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program diff --git a/docs/doxygen/images/bigAndRoundFiles.png b/docs/doxygen/images/bigAndRoundFiles.png new file mode 100644 index 0000000000000000000000000000000000000000..fa6430e7d8f9e9d59f73d60bf63a4e07710912bc GIT binary patch literal 31278 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 JRf!k|{SU*u6|?{V literal 0 HcmV?d00001 diff --git a/docs/doxygen/images/demoScript_folder.png b/docs/doxygen/images/demoScript_folder.png new file mode 100644 index 0000000000000000000000000000000000000000..01d7d16f4c592b9d3f07c0c8bae604ef765b38d4 GIT binary patch literal 7417 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 HI^cf*s>lmm literal 0 HcmV?d00001 diff --git a/docs/doxygen/viewer_image.JPG b/docs/doxygen/images/viewer_image.JPG old mode 100755 new mode 100644 similarity index 100% rename from docs/doxygen/viewer_image.JPG rename to docs/doxygen/images/viewer_image.JPG diff --git a/docs/doxygen/main.dox b/docs/doxygen/main.dox index 22bcd097ad..094c61f612 100644 --- a/docs/doxygen/main.dox +++ b/docs/doxygen/main.dox @@ -9,8 +9,8 @@ If these pages don't answer your question, then send the question to the Writing Python or Java Modules If you want to write Java or Python modules, then there are some tutorials and detailed pages in this document. The Python tutorials include: -- File Ingest Modules: http://www.basistech.com/python-autopsy-module-tutorial-1-the-file-ingest-module/ -- Data Source Ingest Modules: http://www.basistech.com/python-autopsy-module-tutorial-2-the-data-source-ingest-module/ +- File Ingest Modules: \subpage mod_python_file_ingest_tutorial_page +- Data Source Ingest Modules: \subpage mod_python_ds_ingest_tutorial_page - Report Modules: http://www.basistech.com/python-autopsy-module-tutorial-3-the-report-module/ This document contains the following pages: diff --git a/docs/doxygen/modDSIngestTutorial.dox b/docs/doxygen/modDSIngestTutorial.dox new file mode 100644 index 0000000000..e457cbd7d0 --- /dev/null +++ b/docs/doxygen/modDSIngestTutorial.dox @@ -0,0 +1,5 @@ +/*! \page mod_python_ds_ingest_tutorial_page Python Tutorial #2: Writing a Data Source Ingest Module + + + +*/ \ No newline at end of file diff --git a/docs/doxygen/modFileIngestTutorial.dox b/docs/doxygen/modFileIngestTutorial.dox new file mode 100644 index 0000000000..def7e91c2a --- /dev/null +++ b/docs/doxygen/modFileIngestTutorial.dox @@ -0,0 +1,154 @@ +/*! \page mod_python_file_ingest_tutorial_page Python Tutorial #1: Writing a File Ingest Module + + +\section python_tutorial1_why Why Write a File Ingest Module? +
    +
  • Autopsy hides the fact that a file is coming from a file system, was carved, was from inside of a ZIP file, or was part of a local file. So, you don’t need to spend time supporting all of the ways that your user may want to get data to you. You just need to worry about analyzing the content.
  • +
  • Autopsy displays files automatically and can include them in reports if you use standard blackboard artifacts (described later). That means you don’t need to worry about UIs and reports.
  • +
  • Autopsy gives you access to results from other modules. So, you can build on top of their results instead of duplicating them.
  • +
+ +\section python_tutorial1_ingest_modules Ingest Modules + +For our first example, we’re going to write an ingest module. Ingest modules in Autopsy run on the data sources that are added to a case. When you add a disk image (or local drive or logical folder) in Autopsy, you’ll be presented with a list of modules to run (such as hash lookup and keyword search). + +\image html ingest-modules.PNG + +Those are all ingest modules. We’re going to write one of those. There are two types of ingest modules that we can build: +
    +
  • File Ingest Modules are the easiest to write. During their lifetime, they will get passed in each file in the data source. This includes files that are found via carving or inside of ZIP files (if those modules are also enabled).
  • +
  • Data Source Ingest Modules require slightly more work because you have to query the database for the files of interest. If you only care about a small number of files, know their name, and know they won’t be inside of ZIP files, then these are your best bet.
  • +
+ +For this first tutorial, we’re going to write a file ingest module. The \ref mod_python_ds_ingest_tutorial_page "second tutorial" will focus on data source ingest modules. Regardless of the type of ingest module you are writing, you will need to work with two classes: +
    +
  • The factory class provides Autopsy with module information such as display name and version. It also creates instances of ingest modules as needed.
  • +
  • The ingest module class will do the actual analysis. One of these will be created per thread. For file ingest modules, Autopsy will typically create two or more of these at a time so that it can analyze files in parallel. If you keep things simple, and don’t use static variables, then you don’t have to think about anything multithreaded.
  • +
+ +\section python_tutorial1_getting_started Getting Started + +To write your first file ingest module, you’ll need: +
+ +Some other general notes are that you will be writing in Jython, which converts Python-looking code into Java. It has some limitations, including: +
    +
  • You can’t use Python 3 (you are limited to Python 2.7)
  • +
  • You can’t use libraries that use native code
  • +
+ +But, Jython will give you access to all of the Java classes and services that Autopsy provides. So, if you want to stray from this example, then refer to the Developer docs on what classes and methods you have access to. The comments in the sample file will identify what type of object is being passed in along with a URL to its documentation. + +\subsection python_tutorial1_folder Making Your Module Folder + +Every Python module in Autopsy gets its own folder. This reduces naming collisions between modules. To find out where you should put your Python module, launch Autopsy and choose the Tools -> Python Plugins menu item. That will open a folder in your AppData folder, such as "C:\Users\JDoe\AppData\Roaming\Autopsy\python_modules". + +

Make a folder inside of there to store your module. Call it "DemoScript". Copy the fileIngestModule.py sample file listed above into the this new folder and rename it to FindBigRoundFiles.py. Your folder should look like this: + +\image html demoScript_folder.png + +\subsection python_tutorial1_writing Writing the Script + +We are going to write a script that flags any file that is larger than 10MB and whose size is a multiple of 4096. We’ll call these big and round files. This kind of technique could be useful for finding encrypted files. An additional check would be for entropy of the file, but we’ll keep the example simple. + +Open the FindBigRoundFiles.py file in your favorite python text editor. The sample Autopsy Python modules all have TODO entries in them to let you know what you should change. The below steps jump from one TODO to the next. +

    +
  1. Factory Class Name: The first thing to do is rename the sample class name from “SampleJythonFileIngestModuleFactory” to “FindBigRoundFilesIngestModuleFactory”. In the sample module, there are several uses of this class name, so you should search and replace for these strings.
  2. +
  3. Name and Description: The next TODO entries are for names and descriptions. These are shown to users. For this example, we’ll name it “Big and Round File Finder”. The description can be anything you want. Note that Autopsy requires that modules have unique names, so don’t make it too generic.
  4. +
  5. Ingest Module Class Name: The next thing to do is rename the ingest module class from “SampleJythonFileIngestModule” to “FindBigRoundFilesIngestModule”. Our usual naming convention is that this class is the same as the factory class with “Factory” removed from the end.
  6. +
  7. startUp() method: The startUp() method is where each module initializes. For our example, we don’t need to do anything special in here. Typically though, this is where you want to do stuff that could fail because throwing an exception here causes the entire ingest to stop.
  8. +
  9. process() method: This is where we do our analysis. The sample module is well documented with what it does. It ignores non-files, looks at the file name, and makes a blackboard artifact for “.txt” files. There are also a bunch of other things that it does to show examples for easy copy and pasting, but we don’t need them in our module. We’ll cover what goes into this method in the next section.
  10. +
  11. shutdown() method: The shutDown() method either frees resources that were allocated or sends summary messages. For our module, it will do nothing.
  12. +
+ +\subsection python_tutorial1_process The process() Method + +The process() method is passed in a reference to an AbstractFile Object. With this, you have access to all of a file’s contents and metadata. We want to flag files that are larger than 10MB and that are a multiple of 4096 bytes. The following code does that: + +\verbatim if ((file.getSize() > 10485760) and ((file.getSize() % 4096) == 0)): +\endverbatim + +Now that we have found the files, we want to do something with them. In our situation, we just want to alert the user to them. We do this by making an "Interesting Item" blackboard artifact. The Blackboard is where ingest modules can communicate with each other and with the Autopsy GUI. The blackboard has a set of artifacts on it and each artifact:

+
    +
  • Has a type
  • +
  • Is associated with a file
  • +
  • Has one or more attributes. Attributes are simply name and value pairs.
  • +
+ +For our example, we are going to make an artifact of type "TSK_INTERESTING_FILE" whenever we find a big and round file. These are one of the most generic artifact types and are simply a way of alerting the user that a file is interesting for some reason. Once you make the artifact, it will be shown in the UI. The below code makes an artifact for the file and puts it into the set of "Big and Round Files". You can create whatever set names you want. The Autopsy GUI organizes Interesting Files by their set name. +\verbatim + art = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT) + att = BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID(), + FindBigRoundFilesIngestModuleFactory.moduleName, "Big and Round Files") + art.addAttribute(att)\endverbatim + +The above code adds the artifact and a single attribute to the blackboard in the embedded database, but it does not notify other modules or the UI. The UI will eventually refresh, but it is faster to fire an event with this: +\verbatim + IngestServices.getInstance().fireModuleDataEvent( + ModuleDataEvent(FindBigRoundFilesIngestModuleFactory.moduleName, + BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT, None))\endverbatim + +That’s it. Your process() method should look something like this: +\verbatim + def process(self, file): + + # Skip non-files + + if ((file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) or + + (file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS) or + + (file.isFile() == False)): + + return IngestModule.ProcessResult.OK + + + + # Look for files bigger than 10MB that are a multiple of 4096 + + if ((file.getSize() > 10485760) and ((file.getSize() % 4096) == 0)): + + + + # Make an artifact on the blackboard. TSK_INTERESTING_FILE_HIT is a generic type of + + # artifact. Refer to the developer docs for other examples. + + art = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT) + + att = BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID(), + + FindBigRoundFilesIngestModuleFactory.moduleName, "Big and Round Files") + + art.addAttribute(att) + + + + # Fire an event to notify the UI and others that there is a new artifact + + IngestServices.getInstance().fireModuleDataEvent( + + ModuleDataEvent(FindBigRoundFilesIngestModuleFactory.moduleName, + + BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT, None)) + + + + return IngestModule.ProcessResult.OK\endverbatim + +Save this file and run the module on some of your data. If you have any big and round files, you should see an entry under the “Interesting Items” node in the tree. + +\image html bigAndRoundFiles.png + +\subsection python_tutorial1_debug Debugging and Development Tips + +Whenever you have syntax errors or other errors in your script, you will get some form of dialog from Autopsy when you try to run ingest modules. If that happens, fix the problem and run ingest modules again. You don’t need to restart Autopsy each time! + +The sample module has some log statements in there to help debug what is going on since we don’t know of better ways to debug the scripts while running in Autopsy. + + +*/ From 7b21f7a938cef46e347ca93c4f8b899256118680 Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Tue, 15 Oct 2019 11:46:51 -0400 Subject: [PATCH 028/134] start of refactor --- .../autopsy/geolocation/ArtifactWaypoint.java | 35 ---- .../geolocation/DefaultArtifactWaypoint.java | 89 ----------- .../autopsy/geolocation/EXIFWaypoint.java | 76 --------- .../geolocation/GeolocationTopComponent.java | 97 ++++------- .../geolocation/GeolocationUtilities.java | 110 ------------- .../autopsy/geolocation/MapPanel.java | 4 +- .../geolocation/OpenGeolocationAction.java | 4 +- .../autopsy/geolocation/RefreshPanel.java | 25 ++- .../sleuthkit/autopsy/geolocation/Route.java | 151 ------------------ .../geolocation/SimpleArtifactWaypoint.java | 58 ------- 10 files changed, 56 insertions(+), 593 deletions(-) delete mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/ArtifactWaypoint.java delete mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/DefaultArtifactWaypoint.java delete mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/EXIFWaypoint.java delete mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/GeolocationUtilities.java delete mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/Route.java delete mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/SimpleArtifactWaypoint.java diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/ArtifactWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/ArtifactWaypoint.java deleted file mode 100755 index 3cb1fa14ea..0000000000 --- a/Core/src/org/sleuthkit/autopsy/geolocation/ArtifactWaypoint.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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 org.jxmapviewer.viewer.Waypoint; -import org.sleuthkit.datamodel.BlackboardArtifact; - -/** - * ArtifactWaypoint interface. - */ -interface ArtifactWaypoint extends Waypoint{ - - String getLabel() ; - - BlackboardArtifact getArtifact() ; - - long getTimestamp() ; -} - \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/DefaultArtifactWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/DefaultArtifactWaypoint.java deleted file mode 100755 index 7bbc0a317f..0000000000 --- a/Core/src/org/sleuthkit/autopsy/geolocation/DefaultArtifactWaypoint.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * 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 org.jxmapviewer.viewer.GeoPosition; -import org.sleuthkit.datamodel.BlackboardArtifact; - -/** - * Basic parent class for representing a Blackboard artifact waypoint. - * - */ -class DefaultArtifactWaypoint implements ArtifactWaypoint{ - private final BlackboardArtifact artifact; - private String label; - private long timestamp; - private GeoPosition position; - - /** - * Construct a default way point object. - * - * @param artifact The artifact that the waypoint is for. - */ - DefaultArtifactWaypoint(BlackboardArtifact artifact) { - this.artifact = artifact; - } - - @Override - public String getLabel() { - return label; - } - - void setLabel(String label) { - this.label = label; - } - - @Override - public BlackboardArtifact getArtifact() { - return artifact; - } - - @Override - public long getTimestamp() { - return timestamp; - } - - void setTimestamp(long timestamp) { - this.timestamp = timestamp; - } - - @Override - public GeoPosition getPosition() { - return position; - } - - /** - * Set the GeoPosition for the waypoint - * - * @param position GeoPosition for the waypoint - */ - void setPosition(GeoPosition position) { - this.position = position; - } - - /** - * Create and set the GeoPosition for the way point. - * - * @param latitude double latitude value - * @param longitude double logitude value - */ - void setPosition(double latitude, double longitude) { - position = new GeoPosition(latitude, longitude); - } -} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/EXIFWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/EXIFWaypoint.java deleted file mode 100755 index 54a93a812b..0000000000 --- a/Core/src/org/sleuthkit/autopsy/geolocation/EXIFWaypoint.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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.logging.Level; -import org.jxmapviewer.viewer.GeoPosition; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * EXIF way point object - */ -final class EXIFWaypoint extends DefaultArtifactWaypoint{ - - private static final Logger logger = Logger.getLogger(EXIFWaypoint.class.getName()); - - private AbstractFile imageFile; - - /** - * Construct a EXIF way point - * @param artifact - */ - EXIFWaypoint(BlackboardArtifact artifact) { - super(artifact); - initWaypoint(); - } - - /** - * Initialize the way point. - */ - private void initWaypoint() { - BlackboardArtifact artifact = getArtifact(); - - Double longitude = GeolocationUtilities.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE); - Double latitude = GeolocationUtilities.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE); - - if (longitude != null && latitude != null) { - setPosition(new GeoPosition(latitude, longitude)); - } else { - setPosition(null); - // No need to bother with other attributes if there are no - // location parameters - return; - } - - Long datetime = GeolocationUtilities.getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED); - if (datetime != null) { - setTimestamp(datetime * 1000); - } - - try { - imageFile = artifact.getSleuthkitCase().getAbstractFileById(artifact.getObjectID()); - } catch (TskCoreException ex) { - logger.log(Level.WARNING, String.format("Failed to getAbstractFileByID for %d ", artifact.getObjectID()), ex); - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java index 7af2bae7ea..3678f562e8 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java @@ -35,13 +35,15 @@ import org.openide.windows.RetainLocation; import org.openide.windows.TopComponent; import org.sleuthkit.autopsy.casemodule.Case; import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_CASE; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.ingest.IngestManager; import static org.sleuthkit.autopsy.ingest.IngestManager.IngestModuleEvent.DATA_ADDED; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; /** * Top component which displays the Geolocation Tool. @@ -54,15 +56,16 @@ import org.sleuthkit.datamodel.BlackboardArtifact; public final class GeolocationTopComponent extends TopComponent { private static final Logger logger = Logger.getLogger(GeolocationTopComponent.class.getName()); - + private static final Set INGEST_MODULE_EVENTS_OF_INTEREST = EnumSet.of(DATA_ADDED); - + private final PropertyChangeListener ingestListener; - + final RefreshPanel refreshPanel = new RefreshPanel(); @Messages({ - "GLTopComponent_name=Geolocation" + "GLTopComponent_name=Geolocation", + "GLTopComponent_initilzation_error=An error occurred during waypoint initilization. Geolocation data maybe incomplete." }) /** @@ -73,7 +76,7 @@ public final class GeolocationTopComponent extends TopComponent { initComponents(); initWaypoints(); setName(Bundle.GLTopComponent_name()); - + this.ingestListener = pce -> { String eventType = pce.getPropertyName(); if (eventType.equals(DATA_ADDED.toString())) { @@ -86,13 +89,11 @@ public final class GeolocationTopComponent extends TopComponent { || eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE.getTypeID() || eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF.getTypeID() || eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK.getTypeID())) { - - showRefreshPanel(true); + + showRefreshPanel(true); } } }; - - refreshPanel.addCloseActionListener(new ActionListener() { @Override @@ -110,14 +111,14 @@ public final class GeolocationTopComponent extends TopComponent { } }); } - + @Override public void addNotify() { super.addNotify(); IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, ingestListener); Case.addEventTypeSubscriber(EnumSet.of(CURRENT_CASE), evt -> { mapPanel.clearWaypoints(); - if(evt.getNewValue() != null) { + if (evt.getNewValue() != null) { initWaypoints(); } }); @@ -128,14 +129,14 @@ public final class GeolocationTopComponent extends TopComponent { super.removeNotify(); IngestManager.getInstance().removeIngestModuleEventListener(ingestListener); } - + /** * Set the state of the refresh panel at the top of the mapPanel. - * + * * @param show Whether to show or hide the panel. */ private void showRefreshPanel(boolean show) { - if(show) { + if (show) { mapPanel.add(refreshPanel, BorderLayout.NORTH); } else { mapPanel.remove(refreshPanel); @@ -154,72 +155,37 @@ public final class GeolocationTopComponent extends TopComponent { protected List doInBackground() throws Exception { List waypoints = new ArrayList<>(); - Case currentCase; - try { - currentCase = Case.getCurrentCaseThrows(); - } catch (NoCurrentCaseException ex) { - logger.log(Level.WARNING, "Unable to get artifacts for geolocation window, no current open", ex); - // Popup a message or something? - return waypoints; - } + Case currentCase = Case.getCurrentCaseThrows(); - // TSK_GPS_TRACKPOINT, TSK_GPS_SEARCH, TSK_GPS_LAST_KNOWN_LOCATION - // and TSK_GPS_BOOKMARK have similar attributes and can be processed - // similarly - List artifacts = new ArrayList<>(); - artifacts.addAll(currentCase.getSleuthkitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT)); - artifacts.addAll(currentCase.getSleuthkitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH)); - artifacts.addAll(currentCase.getSleuthkitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION)); - artifacts.addAll(currentCase.getSleuthkitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK)); - - for (BlackboardArtifact artifact : artifacts) { - Waypoint point = new SimpleArtifactWaypoint(artifact); - - if (point.getPosition() != null) { - waypoints.add(new SimpleArtifactWaypoint(artifact)); - } - } - - // Handle the TSK_GPS_ROUTE artifacts - List routes = currentCase.getSleuthkitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE); - for (BlackboardArtifact artifact : routes) { - Route route = new Route(artifact); - for (ArtifactWaypoint point : route.getRoute()) { - waypoints.add(point); - } - } - - artifacts = currentCase.getSleuthkitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF); - for (BlackboardArtifact artifact : artifacts) { - Waypoint point = new EXIFWaypoint(artifact); - - if (point.getPosition() != null) { - waypoints.add(new SimpleArtifactWaypoint(artifact)); - } - } +// waypoints.addAll(getGPSRouteWaypoints(currentCase.getSleuthkitCase())); +// waypoints.addAll(getEXIFWaypoints(currentCase.getSleuthkitCase())); +// waypoints.addAll(getSimpleWaypoints(currentCase.getSleuthkitCase())); return waypoints; } @Override protected void done() { - if(isDone() && !isCancelled()) { + if (isDone() && !isCancelled()) { try { List waypoints = get(); - if(waypoints == null || waypoints.isEmpty()) { + if (waypoints == null || waypoints.isEmpty()) { return; } - - for(Waypoint point: waypoints) { + + for (Waypoint point : waypoints) { mapPanel.addWaypoint(point); } - + // 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 (InterruptedException | ExecutionException ex) { - logger.log(Level.WARNING, "Unable to add points to geolocation.", ex); + + } catch (ExecutionException ex) { + logger.log(Level.WARNING, "An exception occured while initalizing waypoints for geolocation window.", ex); + MessageNotifyUtil.Message.error(Bundle.GLTopComponent_initilzation_error()); + } catch (InterruptedException ex) { + logger.log(Level.WARNING, "The initilization thread for geolocation window was interrupted.", ex); } } } @@ -228,6 +194,7 @@ public final class GeolocationTopComponent extends TopComponent { worker.execute(); } + /** * 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 diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationUtilities.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationUtilities.java deleted file mode 100755 index 13514820d3..0000000000 --- a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationUtilities.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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.util.logging.Level; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * Helper function for managing the getting artifact information. These functions - * were grabbed from KMLReport. - * - */ -class GeolocationUtilities { - private static final Logger logger = Logger.getLogger(GeolocationUtilities.class.getName()); - /** - * Get a Double from an artifact if it exists, return null otherwise. - * - * @param artifact The artifact to query - * @param type The attribute type we're looking for - * - * @return The Double if it exists, or null if not - */ - static Double getDouble(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE type) { - Double returnValue = null; - try { - BlackboardAttribute bba = artifact.getAttribute(new BlackboardAttribute.Type(type)); - if (bba != null) { - Double value = bba.getValueDouble(); - returnValue = value; - } - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Error getting Double value: " + type.toString(), ex); //NON-NLS - } - return returnValue; - } - - /** - * Get a Long from an artifact if it exists, return null otherwise. - * - * @param artifact The artifact to query - * @param type The attribute type we're looking for - * - * @return The Long if it exists, or null if not - */ - static Long getLong(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE type) { - Long returnValue = null; - try { - BlackboardAttribute bba = artifact.getAttribute(new BlackboardAttribute.Type(type)); - if (bba != null) { - Long value = bba.getValueLong(); - returnValue = value; - } - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Error getting Long value: " + type.toString(), ex); //NON-NLS - } - return returnValue; - } - - /** - * Get an Integer from an artifact if it exists, return null otherwise. - * - * @param artifact The artifact to query - * @param type The attribute type we're looking for - * - * @return The Integer if it exists, or null if not - */ - static Integer getInteger(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE type) { - Integer returnValue = null; - try { - BlackboardAttribute bba = artifact.getAttribute(new BlackboardAttribute.Type(type)); - if (bba != null) { - Integer value = bba.getValueInt(); - returnValue = value; - } - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Error getting Integer value: " + type.toString(), ex); //NON-NLS - } - return returnValue; - } - - /** - * Get a String from an artifact if it exists, return null otherwise. - * - * @param artifact The artifact to query - * @param type The attribute type we're looking for - * - * @return The String if it exists, or null if not - */ - static String getString(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE type) { - String returnValue = null; - try { - BlackboardAttribute bba = artifact.getAttribute(new BlackboardAttribute.Type(type)); - if (bba != null) { - String value = bba.getValueString(); - if (value != null && !value.isEmpty()) { - returnValue = value; - } - } - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Error getting String value: " + type.toString(), ex); //NON-NLS - } - return returnValue; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java index 8b22c16938..7abc1ff5be 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java @@ -40,9 +40,11 @@ import org.jxmapviewer.viewer.WaypointPainter; * Main panel with the JJXMapViewer object and its basic controls. */ public class MapPanel extends javax.swing.JPanel { + + private static final long serialVersionUID = 1L; private boolean zoomChanging = false; - private final boolean sliderReversed = false; + private boolean sliderReversed = false; // Using a DefaultListModel to store the way points because we get // a lot of functionality for free, like listeners. diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/OpenGeolocationAction.java b/Core/src/org/sleuthkit/autopsy/geolocation/OpenGeolocationAction.java index 2e9ffa0f76..e13031cd6e 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/OpenGeolocationAction.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/OpenGeolocationAction.java @@ -43,8 +43,6 @@ import org.sleuthkit.autopsy.core.RuntimeProperties; @ActionReference(path = "Menu/Tools", position = 102)}) public class OpenGeolocationAction extends CallableSystemAction { - private final PropertyChangeListener caseChangeListener; - @Messages({ "OpenGeolocationAction_name=Geolocation", "OpenGeolocationAction_displayName=Geolocation" @@ -56,7 +54,7 @@ public class OpenGeolocationAction extends CallableSystemAction { public OpenGeolocationAction() { setEnabled(false); //disabled by default. Will be enabled in Case.java when a case is opened. - caseChangeListener = (PropertyChangeEvent evt) -> { + PropertyChangeListener caseChangeListener = (PropertyChangeEvent evt) -> { if (evt.getPropertyName().equals(Case.Events.CURRENT_CASE.toString())) { setEnabled(RuntimeProperties.runningWithGUI() && evt.getNewValue() != null); } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/RefreshPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/RefreshPanel.java index 29db4fb2bd..d53dd26b70 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/RefreshPanel.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/RefreshPanel.java @@ -1,17 +1,32 @@ /* - * 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; import java.awt.event.ActionListener; +import javax.swing.JPanel; /** - * + * Message panel to info user that the MapPanel may need to be refreshed + * due to new artifacts. * */ -public class RefreshPanel extends javax.swing.JPanel { +class RefreshPanel extends JPanel { /** * Creates new form RefreshPanel diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/Route.java b/Core/src/org/sleuthkit/autopsy/geolocation/Route.java deleted file mode 100755 index 55b1af4586..0000000000 --- a/Core/src/org/sleuthkit/autopsy/geolocation/Route.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * 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.List; -import org.jxmapviewer.viewer.GeoPosition; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardAttribute; - -/** - * A route is a set of Geolocations with a common parent artifact; - * - */ -class Route { - - private final BlackboardArtifact artifact; - private final List waypoints; - private long timestamp; - - /** - * Construct a route for the given artifact. - * - * @param artifact TSK_GPS_ROUTE artifact object - */ - Route(BlackboardArtifact artifact) { - this.artifact = artifact; - waypoints = new ArrayList<>(); - initRoute(); - } - - /** - * Construct a route with the given artifact and list of way points. - * - * @param artifact TSK_GPS_ROUTE artifact object - * @param waypoints List of waypoints for this route - */ - Route(BlackboardArtifact artifact, List waypoints) { - this.artifact = artifact; - this.waypoints = waypoints; - } - - /** - * Add a way point to the route. - * - * @param point Waypoint to add to the route. - */ - void addWaypoint(ArtifactWaypoint point) { - waypoints.add(point); - } - - /** - * Get the list of way points for this route; - * - * @return List of ArtifactWaypoints for this route - */ - List getRoute() { - return waypoints; - } - - /** - * Initialize the route. - */ - private void initRoute() { - if (artifact == null) { - return; - } - - // Get the start logitude and latitude - Double latitude = GeolocationUtilities.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START); - Double longitude = GeolocationUtilities.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START); - - if (latitude != null && longitude != null) { - addWaypoint(new RouteWaypoint(this, latitude, longitude, "Start")); - } - - // Get the end logitude and latitude - latitude = GeolocationUtilities.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END); - longitude = GeolocationUtilities.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END); - - if (latitude != null && longitude != null) { - addWaypoint(new RouteWaypoint(this, latitude, longitude, "End")); - } - - // Get the creation date - Long dateTime = GeolocationUtilities.getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME); - if (dateTime != null) { - timestamp = dateTime * 1000; - } - } - - /** - * Waypoint object for routes. - */ - class RouteWaypoint implements ArtifactWaypoint { - - private final Route parent; - private final GeoPosition position; - private final String label; - - /** - * Construct a route way point. - * - * @param parent The parent route object. - * @param latitude Latitude for waypoint - * @param longitude Longitude for waypoint - * @param label Way point label. - */ - RouteWaypoint(Route parent, double latitude, double longitude, String label) { - this.parent = parent; - this.position = new GeoPosition(latitude, longitude); - this.label = label; - } - - @Override - public String getLabel() { - return label; - } - - @Override - public BlackboardArtifact getArtifact() { - return parent.artifact; - } - - @Override - public long getTimestamp() { - return parent.timestamp; - } - - @Override - public GeoPosition getPosition() { - return position; - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/SimpleArtifactWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/SimpleArtifactWaypoint.java deleted file mode 100755 index 5c06b75e31..0000000000 --- a/Core/src/org/sleuthkit/autopsy/geolocation/SimpleArtifactWaypoint.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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 org.jxmapviewer.viewer.GeoPosition; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardAttribute; - -/** - * This is a wrapper for artifacts with the basic set of geolocation attributes. - */ -class SimpleArtifactWaypoint extends DefaultArtifactWaypoint { - - SimpleArtifactWaypoint(BlackboardArtifact artifact) { - super(artifact); - initWaypoint(); - } - - /** - * Initialize the waypoint basic information. - */ - private void initWaypoint() { - BlackboardArtifact artifact = getArtifact(); - - Double longitude = GeolocationUtilities.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE); - Double latitude = GeolocationUtilities.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE); - - if (longitude != null && latitude != null) { - setPosition(new GeoPosition(latitude, longitude)); - } else { - setPosition(null); - // No need to bother with other attributes if there are no - // location parameters - return; - } - - Long datetime = GeolocationUtilities.getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME); - if (datetime != null) { - setTimestamp(datetime * 1000); - } - } -} From c96a855955248a5ef0b4f70dcc4b5583deb461e0 Mon Sep 17 00:00:00 2001 From: Eugene Livis Date: Tue, 15 Oct 2019 17:49:01 -0400 Subject: [PATCH 029/134] Fixed / updated broken URLs --- .../Aug2015DataSourceTutorial/FindContactsDb.py | 6 +++--- .../July2015FileTutorial_BigRound/FindBigRoundFiles.py | 4 ++-- .../Sept2015ReportTutorial_CSV/CsvReportModule.py | 4 ++-- pythonExamples/dataSourceIngestModule.py | 10 +++++----- pythonExamples/fileIngestModule.py | 6 +++--- pythonExamples/fileIngestModuleWithGui.py | 2 +- pythonExamples/reportmodule.py | 4 ++-- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/pythonExamples/Aug2015DataSourceTutorial/FindContactsDb.py b/pythonExamples/Aug2015DataSourceTutorial/FindContactsDb.py index 69a103dcdf..e03ad34121 100644 --- a/pythonExamples/Aug2015DataSourceTutorial/FindContactsDb.py +++ b/pythonExamples/Aug2015DataSourceTutorial/FindContactsDb.py @@ -98,15 +98,15 @@ class ContactsDbIngestModule(DataSourceIngestModule): # Where any setup and configuration is done # 'context' is an instance of org.sleuthkit.autopsy.ingest.IngestJobContext. - # See: http://sleuthkit.org/autopsy/docs/api-docs/4.6.0/classorg_1_1sleuthkit_1_1autopsy_1_1ingest_1_1_ingest_job_context.html + # See: http://sleuthkit.org/autopsy/docs/api-docs/4.13.0/classorg_1_1sleuthkit_1_1autopsy_1_1ingest_1_1_ingest_job_context.html def startUp(self, context): self.context = context # Where the analysis is done. # The 'dataSource' object being passed in is of type org.sleuthkit.datamodel.Content. - # See: http://www.sleuthkit.org/sleuthkit/docs/jni-docs/4.6.0/interfaceorg_1_1sleuthkit_1_1datamodel_1_1_content.html + # See: http://www.sleuthkit.org/sleuthkit/docs/jni-docs/4.13.0/interfaceorg_1_1sleuthkit_1_1datamodel_1_1_content.html # 'progressBar' is of type org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress - # See: http://sleuthkit.org/autopsy/docs/api-docs/4.6.0/classorg_1_1sleuthkit_1_1autopsy_1_1ingest_1_1_data_source_ingest_module_progress.html + # See: http://sleuthkit.org/autopsy/docs/api-docs/4.13.0/classorg_1_1sleuthkit_1_1autopsy_1_1ingest_1_1_data_source_ingest_module_progress.html def process(self, dataSource, progressBar): # we don't know how much work there is yet diff --git a/pythonExamples/July2015FileTutorial_BigRound/FindBigRoundFiles.py b/pythonExamples/July2015FileTutorial_BigRound/FindBigRoundFiles.py index 712cbd24c8..b2b13db96f 100644 --- a/pythonExamples/July2015FileTutorial_BigRound/FindBigRoundFiles.py +++ b/pythonExamples/July2015FileTutorial_BigRound/FindBigRoundFiles.py @@ -92,7 +92,7 @@ class FindBigRoundFilesIngestModule(FileIngestModule): # Where any setup and configuration is done # 'context' is an instance of org.sleuthkit.autopsy.ingest.IngestJobContext. - # See: http://sleuthkit.org/autopsy/docs/api-docs/4.6.0/classorg_1_1sleuthkit_1_1autopsy_1_1ingest_1_1_ingest_job_context.html + # See: http://sleuthkit.org/autopsy/docs/api-docs/4.13.0/classorg_1_1sleuthkit_1_1autopsy_1_1ingest_1_1_ingest_job_context.html # TODO: Add any setup code that you need here. def startUp(self, context): self.filesFound = 0 @@ -103,7 +103,7 @@ class FindBigRoundFilesIngestModule(FileIngestModule): # Where the analysis is done. Each file will be passed into here. # The 'file' object being passed in is of type org.sleuthkit.datamodel.AbstractFile. - # See: http://www.sleuthkit.org/sleuthkit/docs/jni-docs/4.6.0/classorg_1_1sleuthkit_1_1datamodel_1_1_abstract_file.html + # See: http://www.sleuthkit.org/sleuthkit/docs/jni-docs/4.13.0/classorg_1_1sleuthkit_1_1datamodel_1_1_abstract_file.html def process(self, file): # Use blackboard class to index blackboard artifacts for keyword search diff --git a/pythonExamples/Sept2015ReportTutorial_CSV/CsvReportModule.py b/pythonExamples/Sept2015ReportTutorial_CSV/CsvReportModule.py index 5e21c125bd..9253e0ee82 100644 --- a/pythonExamples/Sept2015ReportTutorial_CSV/CsvReportModule.py +++ b/pythonExamples/Sept2015ReportTutorial_CSV/CsvReportModule.py @@ -27,7 +27,7 @@ # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE. -# See http://sleuthkit.org/autopsy/docs/api-docs/4.6.0/index.html for documentation +# See http://sleuthkit.org/autopsy/docs/api-docs/4.13.0/index.html for documentation # Simple report module for Autopsy. # Used as part of Python tutorials from Basis Technology - September 2015 @@ -71,7 +71,7 @@ class CSVReportModule(GeneralReportModuleAdapter): # TODO: Update this method to make a report # The 'baseReportDir' object being passed in is a string with the directory that reports are being stored in. Report should go into baseReportDir + getRelativeFilePath(). # The 'progressBar' object is of type ReportProgressPanel. - # See: http://sleuthkit.org/autopsy/docs/api-docs/4.6.0/classorg_1_1sleuthkit_1_1autopsy_1_1report_1_1_report_progress_panel.html + # See: http://sleuthkit.org/autopsy/docs/api-docs/4.13.0/classorg_1_1sleuthkit_1_1autopsy_1_1report_1_1_report_progress_panel.html def generateReport(self, baseReportDir, progressBar): # Open the output file. diff --git a/pythonExamples/dataSourceIngestModule.py b/pythonExamples/dataSourceIngestModule.py index c2edfcd8fb..07e5520e74 100644 --- a/pythonExamples/dataSourceIngestModule.py +++ b/pythonExamples/dataSourceIngestModule.py @@ -29,7 +29,7 @@ # Simple data source-level ingest module for Autopsy. # Search for TODO for the things that you need to change -# See http://sleuthkit.org/autopsy/docs/api-docs/4.6.0/index.html for documentation +# See http://sleuthkit.org/autopsy/docs/api-docs/4.13.0/index.html for documentation import jarray import inspect @@ -94,7 +94,7 @@ class SampleJythonDataSourceIngestModule(DataSourceIngestModule): # Where any setup and configuration is done # 'context' is an instance of org.sleuthkit.autopsy.ingest.IngestJobContext. - # See: http://sleuthkit.org/autopsy/docs/api-docs/4.6.0/classorg_1_1sleuthkit_1_1autopsy_1_1ingest_1_1_ingest_job_context.html + # See: http://sleuthkit.org/autopsy/docs/api-docs/4.13.0/classorg_1_1sleuthkit_1_1autopsy_1_1ingest_1_1_ingest_job_context.html # TODO: Add any setup code that you need here. def startUp(self, context): @@ -104,9 +104,9 @@ class SampleJythonDataSourceIngestModule(DataSourceIngestModule): # Where the analysis is done. # The 'dataSource' object being passed in is of type org.sleuthkit.datamodel.Content. - # See: http://www.sleuthkit.org/sleuthkit/docs/jni-docs/4.6.0/interfaceorg_1_1sleuthkit_1_1datamodel_1_1_content.html + # See: http://www.sleuthkit.org/sleuthkit/docs/jni-docs/4.13.0/interfaceorg_1_1sleuthkit_1_1datamodel_1_1_content.html # 'progressBar' is of type org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress - # See: http://sleuthkit.org/autopsy/docs/api-docs/4.6.0/classorg_1_1sleuthkit_1_1autopsy_1_1ingest_1_1_data_source_ingest_module_progress.html + # See: http://sleuthkit.org/autopsy/docs/api-docs/4.13.0/classorg_1_1sleuthkit_1_1autopsy_1_1ingest_1_1_data_source_ingest_module_progress.html # TODO: Add your analysis code in here. def process(self, dataSource, progressBar): @@ -119,7 +119,7 @@ class SampleJythonDataSourceIngestModule(DataSourceIngestModule): # For our example, we will use FileManager to get all # files with the word "test" # in the name and then count and read them - # FileManager API: http://sleuthkit.org/autopsy/docs/api-docs/4.6.0/classorg_1_1sleuthkit_1_1autopsy_1_1casemodule_1_1services_1_1_file_manager.html + # FileManager API: http://sleuthkit.org/autopsy/docs/api-docs/4.13.0/classorg_1_1sleuthkit_1_1autopsy_1_1casemodule_1_1services_1_1_file_manager.html fileManager = Case.getCurrentCase().getServices().getFileManager() files = fileManager.findFiles(dataSource, "%test%") diff --git a/pythonExamples/fileIngestModule.py b/pythonExamples/fileIngestModule.py index b36eea7c57..078623c61d 100644 --- a/pythonExamples/fileIngestModule.py +++ b/pythonExamples/fileIngestModule.py @@ -29,7 +29,7 @@ # Simple file-level ingest module for Autopsy. # Search for TODO for the things that you need to change -# See http://sleuthkit.org/autopsy/docs/api-docs/4.6.0/index.html for documentation +# See http://sleuthkit.org/autopsy/docs/api-docs/4.13.0/index.html for documentation import jarray import inspect @@ -94,7 +94,7 @@ class SampleJythonFileIngestModule(FileIngestModule): # Where any setup and configuration is done # 'context' is an instance of org.sleuthkit.autopsy.ingest.IngestJobContext. - # See: http://sleuthkit.org/autopsy/docs/api-docs/4.6.0/classorg_1_1sleuthkit_1_1autopsy_1_1ingest_1_1_ingest_job_context.html + # See: http://sleuthkit.org/autopsy/docs/api-docs/4.13.0/classorg_1_1sleuthkit_1_1autopsy_1_1ingest_1_1_ingest_job_context.html # TODO: Add any setup code that you need here. def startUp(self, context): self.filesFound = 0 @@ -105,7 +105,7 @@ class SampleJythonFileIngestModule(FileIngestModule): # Where the analysis is done. Each file will be passed into here. # The 'file' object being passed in is of type org.sleuthkit.datamodel.AbstractFile. - # See: http://www.sleuthkit.org/sleuthkit/docs/jni-docs/4.6.0/classorg_1_1sleuthkit_1_1datamodel_1_1_abstract_file.html + # See: http://www.sleuthkit.org/sleuthkit/docs/jni-docs/4.13.0/classorg_1_1sleuthkit_1_1datamodel_1_1_abstract_file.html # TODO: Add your analysis code in here. def process(self, file): # Skip non-files diff --git a/pythonExamples/fileIngestModuleWithGui.py b/pythonExamples/fileIngestModuleWithGui.py index cdcaca3576..a7daf5d982 100644 --- a/pythonExamples/fileIngestModuleWithGui.py +++ b/pythonExamples/fileIngestModuleWithGui.py @@ -35,7 +35,7 @@ # don't need a configuration UI, start with the other sample module. # # Search for TODO for the things that you need to change -# See http://sleuthkit.org/autopsy/docs/api-docs/4.6.0/index.html for documentation +# See http://sleuthkit.org/autopsy/docs/api-docs/4.13.0/index.html for documentation import jarray diff --git a/pythonExamples/reportmodule.py b/pythonExamples/reportmodule.py index 1011cfe753..d1f0e927a6 100644 --- a/pythonExamples/reportmodule.py +++ b/pythonExamples/reportmodule.py @@ -31,7 +31,7 @@ # Sample report module for Autopsy. Use as a starting point for new modules. # # Search for TODO for the things that you need to change -# See http://sleuthkit.org/autopsy/docs/api-docs/4.6.0/index.html for documentation +# See http://sleuthkit.org/autopsy/docs/api-docs/4.13.0/index.html for documentation import os from java.lang import System @@ -69,7 +69,7 @@ class SampleGeneralReportModule(GeneralReportModuleAdapter): # TODO: Update this method to make a report # The 'baseReportDir' object being passed in is a string with the directory that reports are being stored in. Report should go into baseReportDir + getRelativeFilePath(). # The 'progressBar' object is of type ReportProgressPanel. - # See: http://sleuthkit.org/autopsy/docs/api-docs/4.6.0/classorg_1_1sleuthkit_1_1autopsy_1_1report_1_1_report_progress_panel.html + # See: http://sleuthkit.org/autopsy/docs/api-docs/4.13.0/classorg_1_1sleuthkit_1_1autopsy_1_1report_1_1_report_progress_panel.html def generateReport(self, baseReportDir, progressBar): # For an example, we write a file with the number of files created in the past 2 weeks From 2a170a735f8a62e461c95509f9a0c69d2a79a22a Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Wed, 16 Oct 2019 14:14:43 -0400 Subject: [PATCH 030/134] inital commit --- .../datamodel/BlackboardArtifactPoint.java | 49 ++ .../geolocation/datamodel/DefaultPoint.java | 263 +++++++ .../datamodel/EXIFMetadataPoint.java | 59 ++ .../datamodel/GeolocationManager.java | 133 ++++ .../autopsy/geolocation/datamodel/Route.java | 116 ++++ .../geolocation/datamodel/RoutePoint.java | 61 ++ .../geolocation/datamodel/SimplePoint.java | 72 ++ .../autopsy/report/modules/kml/KMLReport.java | 650 +++++------------- 8 files changed, 921 insertions(+), 482 deletions(-) create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/datamodel/BlackboardArtifactPoint.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/datamodel/DefaultPoint.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/datamodel/EXIFMetadataPoint.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationManager.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/datamodel/RoutePoint.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/datamodel/SimplePoint.java diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/BlackboardArtifactPoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/BlackboardArtifactPoint.java new file mode 100755 index 0000000000..a93cb49f89 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/BlackboardArtifactPoint.java @@ -0,0 +1,49 @@ +/* + * + * 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 org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * + * + */ +public interface BlackboardArtifactPoint { + BlackboardArtifact getArtifact() ; + + Long getTimestamp() ; + + String getLabel() ; + + Double getLatitude(); + + Double getLongitude(); + + Double getAltitude(); + + String getDetails(); + + void initPosition() throws TskCoreException; + + String getFormattedCoordinates(); + + String getFormattedCoordinates(String format); +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/DefaultPoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/DefaultPoint.java new file mode 100755 index 0000000000..3bf1e56a82 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/DefaultPoint.java @@ -0,0 +1,263 @@ +/* + * + * 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.text.SimpleDateFormat; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * + * + */ +public abstract class DefaultPoint implements BlackboardArtifactPoint{ + final private BlackboardArtifact artifact; + + private String label = null; + private Long timestamp = null; + private String details = null; + private Double longitude = null; + private Double latitude = null; + private Double altitude = null; + + private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX"); + private static final String DEFAULT_COORD_FORMAT = "%.2f, %.2f"; + + public DefaultPoint(BlackboardArtifact artifact) { + this.artifact = artifact; + } + + @Override + public BlackboardArtifact getArtifact() { + return artifact; + } + + @Override + public Long getTimestamp() { + return timestamp; + } + + protected void setTimestamp(Long timestamp) { + this.timestamp = timestamp; + } + + @Override + public String getLabel() { + return label; + } + + protected void setLabel(String label) { + this.label = label; + } + + @Override + public String getDetails() { + return details; + } + + protected void setDetails(String details) { + this.details = details; + } + + @Override + public Double getLatitude() { + return latitude; + } + + protected void setLatitude(Double latitude) { + this.latitude = latitude; + } + + @Override + public Double getLongitude() { + return longitude; + } + + protected void setLongitude(Double longitude) { + this.longitude = longitude; + } + + @Override + public Double getAltitude() { + return altitude; + } + + protected void setAltitude(Double altitude) { + this.altitude = altitude; + } + + @Override + public String getFormattedCoordinates() { + return getFormattedCoordinates(DEFAULT_COORD_FORMAT); + } + + @Override + public String getFormattedCoordinates(String format) { + return String.format(format, getLatitude(), getLongitude()); + } + + /** + * This method creates a text description for a map feature using all the + * geospatial and time data we can for the Artifact. It queries the + * following attributes: + * + * TSK_GEO_LATITUDE 54; TSK_GEO_LONGITUDE 55; TSK_GEO_LATITUDE_START 98; + * TSK_GEO_LATITUDE_END 99; TSK_GEO_LONGITUDE_START 100; + * TSK_GEO_LONGITUDE_END 101; TSK_GEO_VELOCITY 56; TSK_GEO_ALTITUDE 57; + * TSK_GEO_BEARING 58; TSK_GEO_HPRECISION 59; TSK_GEO_VPRECISION 60; + * TSK_GEO_MAPDATUM 61; TSK_DATETIME_START 83; TSK_DATETIME_END 84; + * TSK_LOCATION 86; TSK_PATH_SOURCE 94; + * + * @param artifact the artifact to query. + * @param featureType the type of Artifact we're working on. + * + * @return a String with the information we have available + */ + String getDetailsFromArtifact() throws TskCoreException{ + final String SEP = "
"; + StringBuilder result = new StringBuilder(); //NON-NLS + + result.append("

"); + result.append(getArtifact().getArtifactTypeName()); + result.append("

"); + + String name = getString(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); + if (name != null && !name.isEmpty()) { + result.append("Name: ").append(name).append(SEP); //NON-NLS + } + + String location = getString(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION); + if (location != null && !location.isEmpty()) { + result.append("Location: ").append(location).append(SEP); //NON-NLS + } + + if (timestamp != null) { + result.append("Timestamp: ").append(getTimeStamp(timestamp)).append(SEP); //NON-NLS + result.append("Unix timestamp: ").append(timestamp).append(SEP); //NON-NLS + } + + Long startingTimestamp = getLong( BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START); + if (startingTimestamp != null) { + result.append("Starting Timestamp: ").append(getTimeStamp(startingTimestamp)).append(SEP); //NON-NLS + result.append("Starting Unix timestamp: ").append(startingTimestamp).append(SEP); //NON-NLS + } + + Long endingTimestamp = getLong(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_END); + if (endingTimestamp != null) { + result.append("Ending Timestamp: ").append(getTimeStamp(endingTimestamp)).append(SEP); //NON-NLS + result.append("Ending Unix timestamp: ").append(endingTimestamp).append(SEP); //NON-NLS + } + + Long createdTimestamp = getLong(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED); + if (createdTimestamp != null) { + result.append("Created Timestamp: ").append(getTimeStamp(createdTimestamp)).append(SEP); //NON-NLS + result.append("Created Unix timestamp: ").append(createdTimestamp).append(SEP); //NON-NLS + } + + if (latitude != null) { + result.append("Latitude: ").append(latitude).append(SEP); //NON-NLS + } + + if (longitude != null) { + result.append("Longitude: ").append(longitude).append(SEP); //NON-NLS + } + + Double velocity = getDouble(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_VELOCITY); + if (velocity != null) { + result.append("Velocity: ").append(velocity).append(SEP); //NON-NLS + } + + if (altitude != null) { + result.append("Altitude: ").append(altitude).append(SEP); //NON-NLS + } + + Double bearing = getDouble(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_BEARING); + if (bearing != null) { + result.append("Bearing: ").append(bearing).append(SEP); //NON-NLS + } + + Integer hPrecision = getInteger(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_HPRECISION); + if (hPrecision != null) { + result.append("Horizontal Precision Figure of Merit: ").append(hPrecision).append(SEP); //NON-NLS + } + + Integer vPrecision = getInteger(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_VPRECISION); + if (vPrecision != null) { + result.append("Vertical Precision Figure of Merit: ").append(vPrecision).append(SEP); //NON-NLS + } + + String mapDatum = getString(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_MAPDATUM); + if (mapDatum != null && !mapDatum.isEmpty()) { + result.append("Map Datum: ").append(mapDatum).append(SEP); //NON-NLS + } + + String programName = getString(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME); + if (programName != null && !programName.isEmpty()) { + result.append("Reported by: ").append(programName).append(SEP); //NON-NLS + } + + String flag = getString(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FLAG); + if (flag != null && !flag.isEmpty()) { + result.append("Flag: ").append(flag).append(SEP); //NON-NLS + } + + String pathSource = getString(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_SOURCE); + if (pathSource != null && !pathSource.isEmpty()) { + result.append("Source: ").append(pathSource).append(SEP); //NON-NLS + } + + String deviceMake = getString(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_MAKE); + if (deviceMake != null && !deviceMake.isEmpty()) { + result.append("Device Make: ").append(deviceMake).append(SEP); //NON-NLS + } + + String deviceModel = getString(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_MODEL); + if (deviceModel != null && !deviceModel.isEmpty()) { + result.append("Device Model: ").append(deviceModel).append(SEP); //NON-NLS + } + + return result.toString(); + } + + String getString(BlackboardAttribute.ATTRIBUTE_TYPE type) throws TskCoreException{ + BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(type)); + return (attribute != null ? attribute.getValueString() : null); + } + + Double getDouble(BlackboardAttribute.ATTRIBUTE_TYPE type) throws TskCoreException{ + BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(type)); + return (attribute != null ? attribute.getValueDouble() : null); + } + + Long getLong(BlackboardAttribute.ATTRIBUTE_TYPE type) throws TskCoreException{ + BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(type)); + return (attribute != null ? attribute.getValueLong() : null); + } + + Integer getInteger(BlackboardAttribute.ATTRIBUTE_TYPE type) throws TskCoreException{ + BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(type)); + return (attribute != null ? attribute.getValueInt() : null); + } + + String getTimeStamp(long timeStamp) { + return DATE_FORMAT.format(new java.util.Date(getTimestamp() * 1000)); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/EXIFMetadataPoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/EXIFMetadataPoint.java new file mode 100755 index 0000000000..ea444a9d06 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/EXIFMetadataPoint.java @@ -0,0 +1,59 @@ +/* + * + * 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 org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * + * + */ +public class EXIFMetadataPoint extends SimplePoint{ + private AbstractFile imageFile; + + /** + * Construct a EXIF way point + * @param artifact + */ + EXIFMetadataPoint(BlackboardArtifact artifact) { + super(artifact); + } + + @Override + public void initPosition() throws TskCoreException{ + super.initPosition(); + + setTimestamp(getLong(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED)); + + BlackboardArtifact artifact = getArtifact(); + if(artifact != null) { + imageFile = artifact.getSleuthkitCase().getAbstractFileById(artifact.getObjectID()); + } + + setLabel(imageFile.getName()); + } + + public AbstractFile getImage() { + return imageFile; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationManager.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationManager.java new file mode 100755 index 0000000000..651ea22748 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationManager.java @@ -0,0 +1,133 @@ +/* + * + * 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.util.ArrayList; +import java.util.List; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * + * + */ +public class GeolocationManager { + + static public List getPoints(SleuthkitCase skCase, boolean includeRoute) throws TskCoreException { + List points = new ArrayList<>(); + + points.addAll(getSimplePoints(skCase)); + points.addAll(getEXIFPoints(skCase)); + + if(includeRoute) { + points.addAll(getGPSRouteWaypoints(skCase)); + } + + + return points; + } + + static public List getGPSRoutes(SleuthkitCase skCase) throws TskCoreException{ + List routes = new ArrayList<>(); + List artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE); + for (BlackboardArtifact artifact : artifacts) { + Route route = new Route(artifact); + route.initRoute(); + routes.add(route); + } + return routes; + } + + /** + * + * @param skCase + * + * @return + * + * @throws TskCoreException + */ + static private List getSimplePoints(SleuthkitCase skCase) throws TskCoreException { + + List points = new ArrayList<>(); + + // TSK_GPS_TRACKPOINT, TSK_GPS_SEARCH, TSK_GPS_LAST_KNOWN_LOCATION + // and TSK_GPS_BOOKMARK have similar attributes and can be processed + // similarly + List artifacts = new ArrayList<>(); + artifacts.addAll(skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT)); + artifacts.addAll(skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH)); + artifacts.addAll(skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION)); + artifacts.addAll(skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK)); + + for (BlackboardArtifact artifact : artifacts) { + BlackboardArtifactPoint point = new SimplePoint(artifact); + point.initPosition(); + // Good point only if it has the location + if(point.getLatitude() != null && point.getLongitude() != null) { + points.add(point); + } + } + + return points; + } + + /** + * + * @param skCase + * + * @return + * + * @throws TskCoreException + */ + static private List getEXIFPoints(SleuthkitCase skCase) throws TskCoreException { + List points = new ArrayList<>(); + List artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF); + for (BlackboardArtifact artifact : artifacts) { + BlackboardArtifactPoint point = new EXIFMetadataPoint(artifact); + + point.initPosition(); + if(point.getLatitude() != null && point.getLongitude() != null) { + points.add(point); + } + } + + return points; + } + + /** + * + * @param skCase + * + * @return + * + * @throws TskCoreException + */ + static private List getGPSRouteWaypoints(SleuthkitCase skCase) throws TskCoreException { + List points = new ArrayList<>(); + + for (Route route : getGPSRoutes(skCase)) { + points.addAll(route.getRoute()); + } + + return points; + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java new file mode 100755 index 0000000000..fc17096570 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java @@ -0,0 +1,116 @@ +/* + * + * 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.util.ArrayList; +import java.util.List; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * + * + */ +public class Route { + private final BlackboardArtifact artifact; + private final List waypoints; + private long timestamp; + private String details; + private Double altitude = null; + + /** + * Construct a route for the given artifact. + * + * @param artifact TSK_GPS_ROUTE artifact object + */ + protected Route(BlackboardArtifact artifact) { + this.artifact = artifact; + waypoints = new ArrayList<>(); + } + + /** + * Get the list of way points for this route; + * + * @return List of ArtifactWaypoints for this route + */ + public List getRoute() { + return waypoints; + } + + public String getDetails() { + return details; + } + + public BlackboardArtifact getArtifact() { + return artifact; + } + + public Long getTimestamp() { + return timestamp; + } + + public Double getAltitude() { + return altitude; + } + + /** + * Initialize the route. + */ + protected void initRoute() throws TskCoreException { + if (artifact == null) { + return; + } + + Double latitude; + Double longitude; + + latitude = getDoubleAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START); + longitude = getDoubleAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START); + + if (latitude != null && longitude != null) { + waypoints.add(new RoutePoint(this, latitude, longitude, "Start")); + } + + latitude = getDoubleAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END); + longitude = getDoubleAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END); + + if (latitude != null && longitude != null) { + waypoints.add(new RoutePoint(this, latitude, longitude, "End")); + } + + altitude = getDoubleAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE); + + // Get the creation date + BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME)); + Long dateTime = attribute != null ? attribute.getValueLong() : null; + if (dateTime != null) { + timestamp = dateTime * 1000; + } + } + + private Double getDoubleAttribute(BlackboardAttribute.ATTRIBUTE_TYPE type) throws TskCoreException{ + BlackboardAttribute attribute; + + attribute = artifact.getAttribute(new BlackboardAttribute.Type(type)); + return attribute != null ? attribute.getValueDouble() : null; + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/RoutePoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/RoutePoint.java new file mode 100755 index 0000000000..784b0ea4f1 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/RoutePoint.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.geolocation.datamodel; + +public class RoutePoint extends DefaultPoint { + + private final Route parent; + + /** + * Construct a route way point. + * + * @param parent The parent route object. + * @param latitude Latitude for waypoint + * @param longitude Longitude for waypoint + * @param label Way point label. + */ + protected RoutePoint(Route parent, double latitude, double longitude, String label) { + super(parent.getArtifact()); + + this.parent = parent; + setLabel(label); + setLongitude(longitude); + setLatitude(latitude); + } + + @Override + public void initPosition() { + } + + @Override + public Long getTimestamp() { + return parent.getTimestamp(); + } + + @Override + public String getDetails() { + return parent.getDetails(); + } + + @Override + public Double getAltitude() { + return parent.getAltitude(); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/SimplePoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/SimplePoint.java new file mode 100755 index 0000000000..e75443af51 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/SimplePoint.java @@ -0,0 +1,72 @@ +/* + * + * 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 org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * + * + */ +public class SimplePoint extends DefaultPoint{ + + SimplePoint(BlackboardArtifact artifact) { + super(artifact); + } + + @Override + public void initPosition() throws TskCoreException{ + setLongitude(getDouble(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE)); + setLatitude(getDouble(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE)); + setAltitude(getDouble(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE)); + setTimestamp(getLong(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME)); + + setDetails(getDetailsFromArtifact()); + setLabel(getLabelBasedOnType()); + } + + String getLabelBasedOnType() throws TskCoreException{ + String label = getString(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); + + if(label == null || label.isEmpty()) { + if (getArtifact().getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH.getTypeID()) { + label = getString(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION); + if(label == null || label.isEmpty()) { + label = "GPS Search"; + } + } else if (getArtifact().getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT.getTypeID()) { + label = getString(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME); + if(label == null || label.isEmpty()) { + label = getString(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FLAG); + } + + if(label == null || label.isEmpty()) { + label = "GPS Trackpoint"; + } + } else if (getArtifact().getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION.getTypeID()) { + label = "Last Known Location"; + } + } + + return label; + } +} 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 c1a8ec4046..fe5ab34556 100644 --- a/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java +++ b/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java @@ -36,6 +36,7 @@ import java.io.OutputStream; import java.nio.file.Path; import java.nio.file.Paths; import java.text.SimpleDateFormat; +import java.util.List; import java.util.logging.Level; import org.jdom2.Document; import org.jdom2.Element; @@ -45,9 +46,13 @@ import org.jdom2.output.XMLOutputter; import org.jdom2.CDATA; import org.openide.filesystems.FileUtil; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.geolocation.datamodel.EXIFMetadataPoint; +import org.sleuthkit.autopsy.geolocation.datamodel.GeolocationManager; +import org.sleuthkit.autopsy.geolocation.datamodel.Route; import org.sleuthkit.autopsy.report.ReportBranding; import org.sleuthkit.autopsy.report.ReportProgressPanel; import org.sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException; +import org.sleuthkit.autopsy.geolocation.datamodel.BlackboardArtifactPoint; /** * Generates a KML file based on geospatial information from the BlackBoard. @@ -64,6 +69,13 @@ class KMLReport implements GeneralReportModule { private final SimpleDateFormat kmlDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX"); private Namespace ns; private final String SEP = "
"; + + private Element gpsExifMetadataFolder; + private Element gpsBookmarksFolder; + private Element gpsLastKnownLocationFolder; + private Element gpsRouteFolder; + private Element gpsSearchesFolder; + private Element gpsTrackpointsFolder; private enum FeatureColor { RED("style.kml#redFeature"), @@ -138,270 +150,18 @@ class KMLReport implements GeneralReportModule { progressPanel.updateStatusLabel(NbBundle.getMessage(this.getClass(), "ReportKML.progress.loading")); - ns = Namespace.getNamespace("", "http://www.opengis.net/kml/2.2"); //NON-NLS - - Element kml = new Element("kml", ns); //NON-NLS - kml.addNamespaceDeclaration(Namespace.getNamespace("gx", "http://www.google.com/kml/ext/2.2")); //NON-NLS - kml.addNamespaceDeclaration(Namespace.getNamespace("kml", "http://www.opengis.net/kml/2.2")); //NON-NLS - kml.addNamespaceDeclaration(Namespace.getNamespace("atom", "http://www.w3.org/2005/Atom")); //NON-NLS - Document kmlDocument = new Document(kml); - - Element document = new Element("Document", ns); //NON-NLS - kml.addContent(document); - - Element name = new Element("name", ns); //NON-NLS - ReportBranding rb = new ReportBranding(); - name.setText(rb.getReportTitle() + " KML"); //NON-NLS - document.addContent(name); - - // Check if ingest has finished - if (IngestManager.getInstance().isIngestRunning()) { - Element ingestwarning = new Element("snippet", ns); //NON-NLS - ingestwarning.addContent(NbBundle.getMessage(this.getClass(), "ReportBodyFile.ingestWarning.text")); //NON-NLS - document.addContent(ingestwarning); - } - - // Create folder structure - Element gpsExifMetadataFolder = new Element("Folder", ns); //NON-NLS - CDATA cdataExifMetadataFolder = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/camera-icon-16.png"); //NON-NLS - Element hrefExifMetadata = new Element("href", ns).addContent(cdataExifMetadataFolder); //NON-NLS - gpsExifMetadataFolder.addContent(new Element("Icon", ns).addContent(hrefExifMetadata)); //NON-NLS - - Element gpsBookmarksFolder = new Element("Folder", ns); //NON-NLS - CDATA cdataBookmarks = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gpsfav.png"); //NON-NLS - Element hrefBookmarks = new Element("href", ns).addContent(cdataBookmarks); //NON-NLS - gpsBookmarksFolder.addContent(new Element("Icon", ns).addContent(hrefBookmarks)); //NON-NLS - - Element gpsLastKnownLocationFolder = new Element("Folder", ns); //NON-NLS - CDATA cdataLastKnownLocation = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-lastlocation.png"); //NON-NLS - Element hrefLastKnownLocation = new Element("href", ns).addContent(cdataLastKnownLocation); //NON-NLS - gpsLastKnownLocationFolder.addContent(new Element("Icon", ns).addContent(hrefLastKnownLocation)); //NON-NLS - - Element gpsRouteFolder = new Element("Folder", ns); //NON-NLS - CDATA cdataRoute = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-trackpoint.png"); //NON-NLS - Element hrefRoute = new Element("href", ns).addContent(cdataRoute); //NON-NLS - gpsRouteFolder.addContent(new Element("Icon", ns).addContent(hrefRoute)); //NON-NLS - - Element gpsSearchesFolder = new Element("Folder", ns); //NON-NLS - CDATA cdataSearches = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-search.png"); //NON-NLS - Element hrefSearches = new Element("href", ns).addContent(cdataSearches); //NON-NLS - gpsSearchesFolder.addContent(new Element("Icon", ns).addContent(hrefSearches)); //NON-NLS - - Element gpsTrackpointsFolder = new Element("Folder", ns); //NON-NLS - CDATA cdataTrackpoints = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-trackpoint.png"); //NON-NLS - Element hrefTrackpoints = new Element("href", ns).addContent(cdataTrackpoints); //NON-NLS - gpsTrackpointsFolder.addContent(new Element("Icon", ns).addContent(hrefTrackpoints)); //NON-NLS - - gpsExifMetadataFolder.addContent(new Element("name", ns).addContent("EXIF Metadata")); //NON-NLS - gpsBookmarksFolder.addContent(new Element("name", ns).addContent("GPS Bookmarks")); //NON-NLS - gpsLastKnownLocationFolder.addContent(new Element("name", ns).addContent("GPS Last Known Location")); //NON-NLS - gpsRouteFolder.addContent(new Element("name", ns).addContent("GPS Routes")); //NON-NLS - gpsSearchesFolder.addContent(new Element("name", ns).addContent("GPS Searches")); //NON-NLS - gpsTrackpointsFolder.addContent(new Element("name", ns).addContent("GPS Trackpoints")); //NON-NLS - - document.addContent(gpsExifMetadataFolder); - document.addContent(gpsBookmarksFolder); - document.addContent(gpsLastKnownLocationFolder); - document.addContent(gpsRouteFolder); - document.addContent(gpsSearchesFolder); - document.addContent(gpsTrackpointsFolder); + Document kmlDocument = setupReportDocument(); ReportProgressPanel.ReportStatus result = ReportProgressPanel.ReportStatus.COMPLETE; - - /** - * In the following code, nulls are okay, and are handled when we go to - * write out the KML feature. Nulls are expected to be returned from any - * method where the artifact is not found and is handled in the - * individual feature creation methods. This is done because we don't - * know beforehand which attributes will be included for which artifact, - * as anyone could write a module that adds additional attributes to an - * artifact. - * - * If there are any issues reading the database getting artifacts and - * attributes, or any exceptions thrown during this process, a severe - * error is logged, the report is marked as "Incomplete KML Report", and - * we use a best-effort method to generate KML information on everything - * we can successfully pull out of the database. - */ - try { - for (BlackboardArtifact artifact : skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF)) { - String fileName = ""; - long fileId = 0; - try { - Long timestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED); - String desc = getDescriptionFromArtifact(artifact, "EXIF Metadata With Locations"); //NON-NLS - Double lat = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE); - Double lon = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE); - Element point = makePoint(lat, lon, getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE)); - - if (lat != null && lat != 0.0 && lon != null && lon != 0.0) { - AbstractFile abstractFile = artifact.getSleuthkitCase().getAbstractFileById(artifact.getObjectID()); - fileName = abstractFile.getName(); - fileId = abstractFile.getId(); - Path path; - copyFileUsingStream(abstractFile, Paths.get(baseReportDir, abstractFile.getName()).toFile()); - try { - path = Paths.get(removeLeadingImgAndVol(abstractFile.getUniquePath())); - } catch (TskCoreException ex) { - path = Paths.get(abstractFile.getParentPath(), abstractFile.getName()); - } - String formattedCoordinates = String.format("%.2f, %.2f", lat, lon); - if (path == null) { - path = Paths.get(abstractFile.getName()); - } - gpsExifMetadataFolder.addContent(makePlacemarkWithPicture(abstractFile.getName(), FeatureColor.RED, desc, timestamp, point, path, formattedCoordinates)); - } - } catch (ReadContentInputStreamException ex) { - logger.log(Level.WARNING, String.format("Error reading file '%s' (id=%d).", fileName, fileId), ex); - } catch (Exception ex) { - errorMessage = Bundle.KMLReport_unableToExtractPhotos(); - logger.log(Level.SEVERE, errorMessage, ex); //NON-NLS - result = ReportProgressPanel.ReportStatus.ERROR; - } - } - } catch (TskCoreException ex) { - errorMessage = Bundle.KMLReport_exifPhotoError(); + + try { + makeRoutes(skCase); + addLocationsToReport(skCase, baseReportDir); + } catch(TskCoreException | IOException ex) { + errorMessage = "Failed to complete report."; logger.log(Level.SEVERE, errorMessage, ex); //NON-NLS result = ReportProgressPanel.ReportStatus.ERROR; - } - - try { - for (BlackboardArtifact artifact : skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK)) { - try { - Long timestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME); - String desc = getDescriptionFromArtifact(artifact, "GPS Bookmark"); //NON-NLS - Double lat = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE); - Double lon = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE); - Element point = makePoint(lat, lon, getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE)); - String bookmarkName = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); - String formattedCoordinates = String.format("%.2f, %.2f", lat, lon); - gpsBookmarksFolder.addContent(makePlacemark(bookmarkName, FeatureColor.BLUE, desc, timestamp, point, formattedCoordinates)); - } catch (Exception ex) { - errorMessage = Bundle.KMLReport_bookmarkError(); - logger.log(Level.SEVERE, errorMessage, ex); //NON-NLS - result = ReportProgressPanel.ReportStatus.ERROR; - } - } - } catch (TskCoreException ex) { - errorMessage = Bundle.KMLReport_gpsBookmarkError(); - logger.log(Level.SEVERE, errorMessage, ex); //NON-NLS - result = ReportProgressPanel.ReportStatus.ERROR; - } - - try { - for (BlackboardArtifact artifact : skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION)) { - try { - Long timestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME); - String desc = getDescriptionFromArtifact(artifact, "GPS Last Known Location"); //NON-NLS - Double lat = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE); - Double lon = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE); - Double alt = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE); - Element point = makePoint(lat, lon, alt); - String formattedCoordinates = String.format("%.2f, %.2f", lat, lon); - gpsLastKnownLocationFolder.addContent(makePlacemark("Last Known Location", FeatureColor.PURPLE, desc, timestamp, point, formattedCoordinates)); //NON-NLS - } catch (Exception ex) { - errorMessage = Bundle.KMLReport_locationError(); - logger.log(Level.SEVERE, errorMessage, ex); //NON-NLS - result = ReportProgressPanel.ReportStatus.ERROR; - } - } - } catch (TskCoreException ex) { - errorMessage = Bundle.KMLReport_locationDatabaseError(); - logger.log(Level.SEVERE, errorMessage, ex); //NON-NLS - result = ReportProgressPanel.ReportStatus.ERROR; - } - - try { - for (BlackboardArtifact artifact : skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE)) { - try { - Long timestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME); - String desc = getDescriptionFromArtifact(artifact, "GPS Route"); - Double latitudeStart = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START); - Double longitudeStart = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START); - Double latitudeEnd = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END); - Double longitudeEnd = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END); - Double altitude = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE); - - Element route = makeLineString(latitudeStart, longitudeStart, altitude, latitudeEnd, longitudeEnd, altitude); - Element startingPoint = makePoint(latitudeStart, longitudeStart, altitude); - Element endingPoint = makePoint(latitudeEnd, longitudeEnd, altitude); - - String formattedCoordinates = String.format("%.2f, %.2f to %.2f, %.2f", latitudeStart, longitudeStart, latitudeEnd, longitudeEnd); - gpsRouteFolder.addContent(makePlacemark("As-the-crow-flies Route", FeatureColor.GREEN, desc, timestamp, route, formattedCoordinates)); //NON-NLS - - formattedCoordinates = String.format("%.2f, %.2f", latitudeStart, longitudeStart); - gpsRouteFolder.addContent(makePlacemark("Start", FeatureColor.GREEN, desc, timestamp, startingPoint, formattedCoordinates)); //NON-NLS - - formattedCoordinates = String.format("%.2f, %.2f", latitudeEnd, longitudeEnd); - gpsRouteFolder.addContent(makePlacemark("End", FeatureColor.GREEN, desc, timestamp, endingPoint, formattedCoordinates)); //NON-NLS - } catch (Exception ex) { - errorMessage = Bundle.KMLReport_gpsRouteError(); - logger.log(Level.SEVERE, errorMessage, ex); //NON-NLS - result = ReportProgressPanel.ReportStatus.ERROR; - } - } - } catch (TskCoreException ex) { - errorMessage = Bundle.KMLReport_gpsRouteDatabaseError(); - logger.log(Level.SEVERE, errorMessage, ex); //NON-NLS - result = ReportProgressPanel.ReportStatus.ERROR; - } - - try { - for (BlackboardArtifact artifact : skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH)) { - Long timestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME); - String desc = getDescriptionFromArtifact(artifact, "GPS Search"); //NON-NLS - Double lat = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE); - Double lon = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE); - Double alt = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE); - Element point = makePoint(lat, lon, alt); - String formattedCoordinates = String.format("%.2f, %.2f", lat, lon); - String searchName = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); - if (searchName == null || searchName.isEmpty()) { - searchName = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION); - } - if (searchName == null || searchName.isEmpty()) { - searchName = "GPS Search"; - } - gpsSearchesFolder.addContent(makePlacemark(searchName, FeatureColor.WHITE, desc, timestamp, point, formattedCoordinates)); //NON-NLS - } - } catch (TskCoreException ex) { - errorMessage = Bundle.KMLReport_gpsSearchDatabaseError(); - logger.log(Level.SEVERE, errorMessage, ex); //NON-NLS - result = ReportProgressPanel.ReportStatus.ERROR; - } - - try { - for (BlackboardArtifact artifact : skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT)) { - try { - Long timestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME); - String desc = getDescriptionFromArtifact(artifact, "GPS Trackpoint"); //NON-NLS - Double lat = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE); - Double lon = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE); - Double alt = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE); - Element point = makePoint(lat, lon, alt); - String formattedCoordinates = String.format("%.2f, %.2f, %.2f", lat, lon, alt); - String trackName = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); - if (trackName == null || trackName.isEmpty()) { - trackName = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME); - } - if (trackName == null || trackName.isEmpty()) { - trackName = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FLAG); - } - if (trackName == null || trackName.isEmpty()) { - trackName = "GPS Trackpoint"; - } - gpsTrackpointsFolder.addContent(makePlacemark(trackName, FeatureColor.YELLOW, desc, timestamp, point, formattedCoordinates)); - } catch (Exception ex) { - errorMessage = Bundle.KMLReport_trackpointError(); - logger.log(Level.SEVERE, errorMessage, ex); //NON-NLS - result = ReportProgressPanel.ReportStatus.ERROR; - } - } - } catch (TskCoreException ex) { - errorMessage = Bundle.KMLReport_trackpointDatabaseError(); - logger.log(Level.SEVERE, errorMessage, ex); //NON-NLS - result = ReportProgressPanel.ReportStatus.ERROR; - } + } // Copy the style sheet try { @@ -440,242 +200,170 @@ class KMLReport implements GeneralReportModule { progressPanel.complete(result, errorMessage); } + + + private Document setupReportDocument() { + ns = Namespace.getNamespace("", "http://www.opengis.net/kml/2.2"); //NON-NLS - /** - * Get a Double from an artifact if it exists, return null otherwise. - * - * @param artifact The artifact to query - * @param type The attribute type we're looking for - * - * @return The Double if it exists, or null if not - */ - private Double getDouble(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE type) { - Double returnValue = null; - try { - BlackboardAttribute bba = artifact.getAttribute(new BlackboardAttribute.Type(type)); - if (bba != null) { - Double value = bba.getValueDouble(); - returnValue = value; - } - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Error getting Double value: " + type.toString(), ex); //NON-NLS + Element kml = new Element("kml", ns); //NON-NLS + kml.addNamespaceDeclaration(Namespace.getNamespace("gx", "http://www.google.com/kml/ext/2.2")); //NON-NLS + kml.addNamespaceDeclaration(Namespace.getNamespace("kml", "http://www.opengis.net/kml/2.2")); //NON-NLS + kml.addNamespaceDeclaration(Namespace.getNamespace("atom", "http://www.w3.org/2005/Atom")); //NON-NLS + Document kmlDocument = new Document(kml); + + Element document = new Element("Document", ns); //NON-NLS + kml.addContent(document); + + Element name = new Element("name", ns); //NON-NLS + ReportBranding rb = new ReportBranding(); + name.setText(rb.getReportTitle() + " KML"); //NON-NLS + document.addContent(name); + + // Check if ingest has finished + if (IngestManager.getInstance().isIngestRunning()) { + Element ingestwarning = new Element("snippet", ns); //NON-NLS + ingestwarning.addContent(NbBundle.getMessage(this.getClass(), "ReportBodyFile.ingestWarning.text")); //NON-NLS + document.addContent(ingestwarning); } - return returnValue; - } - /** - * Get a Long from an artifact if it exists, return null otherwise. - * - * @param artifact The artifact to query - * @param type The attribute type we're looking for - * - * @return The Long if it exists, or null if not - */ - private Long getLong(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE type) { - Long returnValue = null; - try { - BlackboardAttribute bba = artifact.getAttribute(new BlackboardAttribute.Type(type)); - if (bba != null) { - Long value = bba.getValueLong(); - returnValue = value; - } - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Error getting Long value: " + type.toString(), ex); //NON-NLS + // Create folder structure + gpsExifMetadataFolder = new Element("Folder", ns); //NON-NLS + CDATA cdataExifMetadataFolder = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/camera-icon-16.png"); //NON-NLS + Element hrefExifMetadata = new Element("href", ns).addContent(cdataExifMetadataFolder); //NON-NLS + gpsExifMetadataFolder.addContent(new Element("Icon", ns).addContent(hrefExifMetadata)); //NON-NLS + + gpsBookmarksFolder = new Element("Folder", ns); //NON-NLS + CDATA cdataBookmarks = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gpsfav.png"); //NON-NLS + Element hrefBookmarks = new Element("href", ns).addContent(cdataBookmarks); //NON-NLS + gpsBookmarksFolder.addContent(new Element("Icon", ns).addContent(hrefBookmarks)); //NON-NLS + + gpsLastKnownLocationFolder = new Element("Folder", ns); //NON-NLS + CDATA cdataLastKnownLocation = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-lastlocation.png"); //NON-NLS + Element hrefLastKnownLocation = new Element("href", ns).addContent(cdataLastKnownLocation); //NON-NLS + gpsLastKnownLocationFolder.addContent(new Element("Icon", ns).addContent(hrefLastKnownLocation)); //NON-NLS + + gpsRouteFolder = new Element("Folder", ns); //NON-NLS + CDATA cdataRoute = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-trackpoint.png"); //NON-NLS + Element hrefRoute = new Element("href", ns).addContent(cdataRoute); //NON-NLS + gpsRouteFolder.addContent(new Element("Icon", ns).addContent(hrefRoute)); //NON-NLS + + gpsSearchesFolder = new Element("Folder", ns); //NON-NLS + CDATA cdataSearches = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-search.png"); //NON-NLS + Element hrefSearches = new Element("href", ns).addContent(cdataSearches); //NON-NLS + gpsSearchesFolder.addContent(new Element("Icon", ns).addContent(hrefSearches)); //NON-NLS + + gpsTrackpointsFolder = new Element("Folder", ns); //NON-NLS + CDATA cdataTrackpoints = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-trackpoint.png"); //NON-NLS + Element hrefTrackpoints = new Element("href", ns).addContent(cdataTrackpoints); //NON-NLS + gpsTrackpointsFolder.addContent(new Element("Icon", ns).addContent(hrefTrackpoints)); //NON-NLS + + gpsExifMetadataFolder.addContent(new Element("name", ns).addContent("EXIF Metadata")); //NON-NLS + gpsBookmarksFolder.addContent(new Element("name", ns).addContent("GPS Bookmarks")); //NON-NLS + gpsLastKnownLocationFolder.addContent(new Element("name", ns).addContent("GPS Last Known Location")); //NON-NLS + gpsRouteFolder.addContent(new Element("name", ns).addContent("GPS Routes")); //NON-NLS + gpsSearchesFolder.addContent(new Element("name", ns).addContent("GPS Searches")); //NON-NLS + gpsTrackpointsFolder.addContent(new Element("name", ns).addContent("GPS Trackpoints")); //NON-NLS + + document.addContent(gpsExifMetadataFolder); + document.addContent(gpsBookmarksFolder); + document.addContent(gpsLastKnownLocationFolder); + document.addContent(gpsRouteFolder); + document.addContent(gpsSearchesFolder); + document.addContent(gpsTrackpointsFolder); + + return kmlDocument; + } + + + void addExifMetadataContent(EXIFMetadataPoint location, String baseReportDirectory) throws IOException{ + Element point = makePoint(location); + if(point == null) { + return; } - return returnValue; - } + + AbstractFile abstractFile = location.getImage(); + String details = "

EXIF Metadata With Locations

" + location.getDetails(); - /** - * Get an Integer from an artifact if it exists, return null otherwise. - * - * @param artifact The artifact to query - * @param type The attribute type we're looking for - * - * @return The Integer if it exists, or null if not - */ - private Integer getInteger(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE type) { - Integer returnValue = null; + Path path; + copyFileUsingStream(abstractFile, Paths.get(baseReportDirectory, abstractFile.getName()).toFile()); try { - BlackboardAttribute bba = artifact.getAttribute(new BlackboardAttribute.Type(type)); - if (bba != null) { - Integer value = bba.getValueInt(); - returnValue = value; - } + path = Paths.get(removeLeadingImgAndVol(abstractFile.getUniquePath())); } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Error getting Integer value: " + type.toString(), ex); //NON-NLS + path = Paths.get(abstractFile.getParentPath(), abstractFile.getName()); } - return returnValue; + if (path == null) { + path = Paths.get(abstractFile.getName()); + } + + gpsExifMetadataFolder.addContent(makePlacemarkWithPicture(abstractFile.getName(), FeatureColor.RED, details, location.getTimestamp(), point, path, location.getFormattedCoordinates())); } + + void addLocationsToReport(SleuthkitCase skCase, String baseReportDir) throws TskCoreException, IOException { + List points = GeolocationManager.getPoints(skCase, false); - /** - * Get a String from an artifact if it exists, return null otherwise. - * - * @param artifact The artifact to query - * @param type The attribute type we're looking for - * - * @return The String if it exists, or null if not - */ - private String getString(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE type) { - String returnValue = null; - try { - BlackboardAttribute bba = artifact.getAttribute(new BlackboardAttribute.Type(type)); - if (bba != null) { - String value = bba.getValueString(); - if (value != null && !value.isEmpty()) { - returnValue = value; + for (BlackboardArtifactPoint point : points) { + Element reportPoint = makePoint(point); + if (reportPoint == null) { + continue; + } + if (point.getArtifact().getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF.getTypeID()) { + addExifMetadataContent((EXIFMetadataPoint) point, baseReportDir); + + } else if (point.getArtifact().getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK.getTypeID()) { + gpsBookmarksFolder.addContent(makePlacemark(point.getLabel(), FeatureColor.BLUE, point.getDetails(), point.getTimestamp(), reportPoint, point.getFormattedCoordinates())); + } else if (point.getArtifact().getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION.getTypeID()) { + gpsLastKnownLocationFolder.addContent(makePlacemark(point.getLabel(), FeatureColor.PURPLE, point.getDetails(), point.getTimestamp(), reportPoint, point.getFormattedCoordinates())); + } else if (point.getArtifact().getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH.getTypeID()) { + gpsSearchesFolder.addContent(makePlacemark(point.getLabel(), FeatureColor.WHITE, point.getDetails(), point.getTimestamp(), reportPoint, point.getFormattedCoordinates())); + } else if (point.getArtifact().getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT.getTypeID()) { + gpsTrackpointsFolder.addContent(makePlacemark(point.getLabel(), FeatureColor.WHITE, point.getDetails(), point.getTimestamp(), reportPoint, point.getFormattedCoordinates())); + } + } + } + + void makeRoutes(SleuthkitCase skCase) throws TskCoreException { + List routes = GeolocationManager.getGPSRoutes(skCase); + + if (routes != null) { + for (Route route : routes) { + List routePoints = route.getRoute(); + BlackboardArtifactPoint start = null; + BlackboardArtifactPoint end = null; + // This is hardcoded knowledge that there is only two points + // a start and end. In the long run it would be nice to + // support the idea of a route with multiple points. The Route + // class supports that idea. Would be nice to figure out how to support + // for report. + if (routePoints != null && routePoints.size() > 1) { + start = routePoints.get(0); + end = routePoints.get(1); } + + if (start == null || end == null) { + continue; + } + + Element reportRoute = makeLineString(start.getLatitude(), start.getLongitude(), start.getAltitude(), end.getLatitude(), end.getLongitude(), end.getAltitude()); + Element startingPoint = makePoint(start.getLatitude(), start.getLongitude(), start.getAltitude()); + Element endingPoint = makePoint(end.getLatitude(), end.getLongitude(), end.getAltitude()); + + String formattedCoordinates = String.format("%s to %s", start.getFormattedCoordinates(), end.getFormattedCoordinates()); + + gpsRouteFolder.addContent(makePlacemark("As-the-crow-flies Route", FeatureColor.GREEN, route.getDetails(), route.getTimestamp(), reportRoute, formattedCoordinates)); //NON-NLS + gpsRouteFolder.addContent(makePlacemark(start.getLabel(), FeatureColor.GREEN, start.getDetails(), start.getTimestamp(), startingPoint, start.getFormattedCoordinates())); //NON-NLS + gpsRouteFolder.addContent(makePlacemark(end.getLabel(), FeatureColor.GREEN, end.getDetails(), end.getTimestamp(), endingPoint, end.getFormattedCoordinates())); //NON-NLS } - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Error getting String value: " + type.toString(), ex); //NON-NLS } - return returnValue; } - /** - * This method creates a text description for a map feature using all the - * geospatial and time data we can for the Artifact. It queries the - * following attributes: - * - * TSK_GEO_LATITUDE 54; TSK_GEO_LONGITUDE 55; TSK_GEO_LATITUDE_START 98; - * TSK_GEO_LATITUDE_END 99; TSK_GEO_LONGITUDE_START 100; - * TSK_GEO_LONGITUDE_END 101; TSK_GEO_VELOCITY 56; TSK_GEO_ALTITUDE 57; - * TSK_GEO_BEARING 58; TSK_GEO_HPRECISION 59; TSK_GEO_VPRECISION 60; - * TSK_GEO_MAPDATUM 61; TSK_DATETIME_START 83; TSK_DATETIME_END 84; - * TSK_LOCATION 86; TSK_PATH_SOURCE 94; - * - * @param artifact the artifact to query. - * @param featureType the type of Artifact we're working on. - * - * @return a String with the information we have available - */ - private String getDescriptionFromArtifact(BlackboardArtifact artifact, String featureType) { - StringBuilder result = new StringBuilder("

" + featureType + "

"); //NON-NLS - - String name = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); - if (name != null && !name.isEmpty()) { - result.append("Name: ").append(name).append(SEP); //NON-NLS - } - - String location = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION); - if (location != null && !location.isEmpty()) { - result.append("Location: ").append(location).append(SEP); //NON-NLS - } - - Long timestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME); - if (timestamp != null) { - result.append("Timestamp: ").append(getTimeStamp(timestamp)).append(SEP); //NON-NLS - result.append("Unix timestamp: ").append(timestamp).append(SEP); //NON-NLS - } - - Long startingTimestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START); - if (startingTimestamp != null) { - result.append("Starting Timestamp: ").append(getTimeStamp(startingTimestamp)).append(SEP); //NON-NLS - result.append("Starting Unix timestamp: ").append(startingTimestamp).append(SEP); //NON-NLS - } - - Long endingTimestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_END); - if (endingTimestamp != null) { - result.append("Ending Timestamp: ").append(getTimeStamp(endingTimestamp)).append(SEP); //NON-NLS - result.append("Ending Unix timestamp: ").append(endingTimestamp).append(SEP); //NON-NLS - } - - Long createdTimestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED); - if (createdTimestamp != null) { - result.append("Created Timestamp: ").append(getTimeStamp(createdTimestamp)).append(SEP); //NON-NLS - result.append("Created Unix timestamp: ").append(createdTimestamp).append(SEP); //NON-NLS - } - - Double latitude = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE); - if (latitude != null) { - result.append("Latitude: ").append(latitude).append(SEP); //NON-NLS - } - - Double longitude = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE); - if (longitude != null) { - result.append("Longitude: ").append(longitude).append(SEP); //NON-NLS - } - - Double latitudeStart = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START); - if (latitudeStart != null) { - result.append("Latitude Start: ").append(latitudeStart).append(SEP); //NON-NLS - } - - Double longitudeStart = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START); - if (longitudeStart != null) { - result.append("Longitude Start: ").append(longitudeStart).append(SEP); //NON-NLS - } - - Double latitudeEnd = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END); - if (latitudeEnd != null) { - result.append("Latitude End: ").append(latitudeEnd).append(SEP); //NON-NLS - } - - Double longitudeEnd = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END); - if (longitudeEnd != null) { - result.append("Longitude End: ").append(longitudeEnd).append(SEP); //NON-NLS - } - - Double velocity = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_VELOCITY); - if (velocity != null) { - result.append("Velocity: ").append(velocity).append(SEP); //NON-NLS - } - - Double altitude = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE); - if (altitude != null) { - result.append("Altitude: ").append(altitude).append(SEP); //NON-NLS - } - - Double bearing = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_BEARING); - if (bearing != null) { - result.append("Bearing: ").append(bearing).append(SEP); //NON-NLS - } - - Integer hPrecision = getInteger(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_HPRECISION); - if (hPrecision != null) { - result.append("Horizontal Precision Figure of Merit: ").append(hPrecision).append(SEP); //NON-NLS - } - - Integer vPrecision = getInteger(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_VPRECISION); - if (vPrecision != null) { - result.append("Vertical Precision Figure of Merit: ").append(vPrecision).append(SEP); //NON-NLS - } - - String mapDatum = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_MAPDATUM); - if (mapDatum != null && !mapDatum.isEmpty()) { - result.append("Map Datum: ").append(mapDatum).append(SEP); //NON-NLS - } - - String programName = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME); - if (programName != null && !programName.isEmpty()) { - result.append("Reported by: ").append(programName).append(SEP); //NON-NLS - } - - String flag = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FLAG); - if (flag != null && !flag.isEmpty()) { - result.append("Flag: ").append(flag).append(SEP); //NON-NLS - } - - String pathSource = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_SOURCE); - if (pathSource != null && !pathSource.isEmpty()) { - result.append("Source: ").append(pathSource).append(SEP); //NON-NLS - } - - String deviceMake = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_MAKE); - if (deviceMake != null && !deviceMake.isEmpty()) { - result.append("Device Make: ").append(deviceMake).append(SEP); //NON-NLS - } - - String deviceModel = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_MODEL); - if (deviceModel != null && !deviceModel.isEmpty()) { - result.append("Device Model: ").append(deviceModel).append(SEP); //NON-NLS - } - - return result.toString(); - } private String getTimeStamp(long timeStamp) { return kmlDateFormat.format(new java.util.Date(timeStamp * 1000)); } + + private Element makePoint(BlackboardArtifactPoint location) { + return makePoint(location.getLatitude(), location.getLongitude(), location.getAltitude()); + } /** * Create a Point for use in a Placemark. Note in this method altitude is @@ -691,12 +379,10 @@ class KMLReport implements GeneralReportModule { * @return the Point as an Element */ private Element makePoint(Double latitude, Double longitude, Double altitude) { - if (latitude == null) { - latitude = 0.0; - } - if (longitude == null) { - longitude = 0.0; + if (latitude == null || longitude == null) { + return null; } + if (altitude == null) { altitude = 0.0; } From ba9e8c2b8ac179951b648d6cbb4c9fb3fd76c03d Mon Sep 17 00:00:00 2001 From: Eammon Date: Wed, 16 Oct 2019 16:42:33 -0400 Subject: [PATCH 031/134] Workarounds to ensure that dialogs are not hidden on macOS. --- .../casemodule/CaseInformationPanel.java | 2 ++ .../autopsy/casemodule/CaseOpenAction.java | 17 +++++++++++++++-- .../autopsy/casemodule/CueBannerPanel.java | 2 ++ .../autopsy/casemodule/NewCaseWizardAction.java | 2 ++ 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java index a6494fe22b..76b56138d7 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseInformationPanel.java @@ -162,6 +162,8 @@ class CaseInformationPanel extends javax.swing.JPanel { editCasePropertiesDialog.setResizable(true); editCasePropertiesDialog.pack(); editCasePropertiesDialog.setLocationRelativeTo(this); + // Workaround to ensure dialog is not hidden on macOS + editCasePropertiesDialog.setAlwaysOnTop(true); editCasePropertiesDialog.setVisible(true); editCasePropertiesDialog.toFront(); caseDetailsPanel.updateCaseInfo(); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java index cc07148ba0..b68d1a49f6 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java @@ -84,10 +84,16 @@ public final class CaseOpenAction extends CallableSystemAction implements Action fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); fileChooser.setMultiSelectionEnabled(false); fileChooser.setFileFilter(caseMetadataFileFilter); + if (null != ModuleSettings.getConfigSetting(ModuleSettings.MAIN_SETTINGS, PROP_BASECASE)) { fileChooser.setCurrentDirectory(new File(ModuleSettings.getConfigSetting("Case", PROP_BASECASE))); //NON-NLS } - + + /** + * If the open multi user case dialog is open make sure it's not set + * to always be on top as this hides the file chooser on macOS. + */ + OpenMultiUserCaseDialog.getInstance().setAlwaysOnTop(false); String optionsDlgTitle = NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning.title"); String optionsDlgMessage = NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning"); if (IngestRunningCheck.checkAndConfirmProceed(optionsDlgTitle, optionsDlgMessage)) { @@ -95,7 +101,12 @@ public final class CaseOpenAction extends CallableSystemAction implements Action * Pop up a file chooser to allow the user to select a case metadata * file (.aut file). */ - int retval = fileChooser.showOpenDialog(WindowManager.getDefault().getMainWindow()); + /** + * Passing the fileChooser as its own parent gets around an issue + * where the fileChooser was hidden behind the CueBannerPanel ("Welcome" dialog) + * on macOS. + */ + int retval = fileChooser.showOpenDialog(fileChooser); if (retval == JFileChooser.APPROVE_OPTION) { /* * Close the startup window, if it is open. @@ -159,6 +170,8 @@ public final class CaseOpenAction extends CallableSystemAction implements Action OpenMultiUserCaseDialog multiUserCaseWindow = OpenMultiUserCaseDialog.getInstance(); multiUserCaseWindow.setLocationRelativeTo(WindowManager.getDefault().getMainWindow()); + // Workaround to ensure that dialog is not hidden on macOS. + multiUserCaseWindow.setAlwaysOnTop(true); multiUserCaseWindow.setVisible(true); WindowManager.getDefault().getMainWindow().setCursor(null); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CueBannerPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/CueBannerPanel.java index 3ddb97fcfd..37c6c7c8b8 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CueBannerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CueBannerPanel.java @@ -249,6 +249,8 @@ public class CueBannerPanel extends javax.swing.JPanel { private void openRecentCaseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_openRecentCaseButtonActionPerformed recentCasesWindow.setLocationRelativeTo(this); OpenRecentCasePanel.getInstance(); //refreshes the recent cases table + // Workaround to ensure that dialog is not hidden on macOS. + recentCasesWindow.setAlwaysOnTop(true); recentCasesWindow.setVisible(true); }//GEN-LAST:event_openRecentCaseButtonActionPerformed diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java index 50688b1ac1..c5e6ece78d 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardAction.java @@ -71,6 +71,8 @@ final class NewCaseWizardAction extends CallableSystemAction { wizardDescriptor.setTitleFormat(new MessageFormat("{0}")); wizardDescriptor.setTitle(NbBundle.getMessage(this.getClass(), "NewCaseWizardAction.newCase.windowTitle.text")); Dialog dialog = DialogDisplayer.getDefault().createDialog(wizardDescriptor); + // Workaround to ensure new case dialog is not hidden on macOS + dialog.setAlwaysOnTop(true); dialog.setVisible(true); dialog.toFront(); if (wizardDescriptor.getValue() == WizardDescriptor.FINISH_OPTION) { From f73eb23ad966d49d42e87f9030e369243f5ed1e8 Mon Sep 17 00:00:00 2001 From: Mark McKinnon Date: Wed, 16 Oct 2019 23:18:24 -0400 Subject: [PATCH 032/134] Update ThunderbirdMboxFileIngestModule.java Check threaded messageId if null and skip it if it is. Format code also. --- .../ThunderbirdMboxFileIngestModule.java | 161 +++++++++--------- 1 file changed, 81 insertions(+), 80 deletions(-) diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java index 5c42269a8a..b27f045398 100644 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java @@ -65,12 +65,13 @@ import org.sleuthkit.datamodel.TskException; * structure and metadata. */ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { + private static final Logger logger = Logger.getLogger(ThunderbirdMboxFileIngestModule.class.getName()); private final IngestServices services = IngestServices.getInstance(); private FileManager fileManager; private IngestJobContext context; private Blackboard blackboard; - + private Case currentCase; /** @@ -80,7 +81,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { } @Override - @Messages ({"ThunderbirdMboxFileIngestModule.noOpenCase.errMsg=Exception while getting open case."}) + @Messages({"ThunderbirdMboxFileIngestModule.noOpenCase.errMsg=Exception while getting open case."}) public void startUp(IngestJobContext context) throws IngestModuleException { this.context = context; try { @@ -103,8 +104,8 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { } //skip unalloc - if ((abstractFile.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS)) || - (abstractFile.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK))) { + if ((abstractFile.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS)) + || (abstractFile.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK))) { return ProcessResult.OK; } @@ -115,7 +116,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { // check its signature boolean isMbox = false; boolean isEMLFile = false; - + try { byte[] t = new byte[64]; if (abstractFile.getSize() > 64) { @@ -132,7 +133,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { if (isMbox) { return processMBox(abstractFile); } - + if (isEMLFile) { return processEMLFile(abstractFile); } @@ -140,7 +141,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { if (PstParser.isPstFile(abstractFile)) { return processPst(abstractFile); } - + if (VcardParser.isVcardFile(abstractFile)) { return processVcard(abstractFile); } @@ -160,7 +161,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { String fileName; try { fileName = getTempPath() + File.separator + abstractFile.getName() - + "-" + String.valueOf(abstractFile.getId()); + + "-" + String.valueOf(abstractFile.getId()); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS return ProcessResult.ERROR; @@ -188,11 +189,11 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { PstParser parser = new PstParser(services); PstParser.ParseResult result = parser.open(file, abstractFile.getId()); - switch( result) { + switch (result) { case OK: Iterator pstMsgIterator = parser.getEmailMessageIterator(); if (pstMsgIterator != null) { - processEmails(parser.getPartialEmailMessages(), pstMsgIterator , abstractFile); + processEmails(parser.getPartialEmailMessages(), pstMsgIterator, abstractFile); } else { // sometimes parser returns ParseResult=OK but there are no messages postErrorMessage( @@ -273,7 +274,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { String fileName; try { fileName = getTempPath() + File.separator + abstractFile.getName() - + "-" + String.valueOf(abstractFile.getId()); + + "-" + String.valueOf(abstractFile.getId()); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS return ProcessResult.ERROR; @@ -298,16 +299,16 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { return ProcessResult.OK; } - MboxParser emailIterator = MboxParser.getEmailIterator( emailFolder, file, abstractFile.getId()); + MboxParser emailIterator = MboxParser.getEmailIterator(emailFolder, file, abstractFile.getId()); List emails = new ArrayList<>(); - if(emailIterator != null) { - while(emailIterator.hasNext()) { + if (emailIterator != null) { + while (emailIterator.hasNext()) { EmailMessage emailMessage = emailIterator.next(); - if(emailMessage != null) { + if (emailMessage != null) { emails.add(emailMessage); } } - + String errors = emailIterator.getErrors(); if (!errors.isEmpty()) { postErrorMessage( @@ -315,7 +316,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { abstractFile.getName()), errors); } } - processEmails(emails, MboxParser.getEmailIterator( emailFolder, file, abstractFile.getId()), abstractFile); + processEmails(emails, MboxParser.getEmailIterator(emailFolder, file, abstractFile.getId()), abstractFile); if (file.delete() == false) { logger.log(Level.INFO, "Failed to delete temp file: {0}", file.getName()); //NON-NLS @@ -323,7 +324,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { return ProcessResult.OK; } - + /** * Parse and extract data from a vCard file. * @@ -347,8 +348,8 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { } return ProcessResult.OK; } - - private ProcessResult processEMLFile(AbstractFile abstractFile) { + + private ProcessResult processEMLFile(AbstractFile abstractFile) { try { EmailMessage message = EMLParser.parse(abstractFile); @@ -400,7 +401,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { /** * Get a module output folder. - * + * * @throws NoCurrentCaseException if there is no open case. * * @return the module output folder @@ -435,38 +436,40 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { * @param abstractFile */ private void processEmails(List partialEmailsForThreading, Iterator fullMessageIterator, AbstractFile abstractFile) { - + // Putting try/catch around this to catch any exception and still allow // the creation of the artifacts to continue. - try{ + try { EmailMessageThreader.threadMessages(partialEmailsForThreading); - } catch(Exception ex) { + } catch (Exception ex) { logger.log(Level.WARNING, String.format("Exception thrown parsing emails from %s", abstractFile.getName()), ex); } - + List derivedFiles = new ArrayList<>(); int msgCnt = 0; - while(fullMessageIterator.hasNext()) { + while (fullMessageIterator.hasNext()) { EmailMessage current = fullMessageIterator.next(); - - if(current == null) { + + if (current == null) { continue; } - if(partialEmailsForThreading.size() > msgCnt) { + if (partialEmailsForThreading.size() > msgCnt) { EmailMessage threaded = partialEmailsForThreading.get(msgCnt++); - - if(threaded.getMessageID().equals(current.getMessageID()) && - threaded.getSubject().equals(current.getSubject())) { - current.setMessageThreadID(threaded.getMessageThreadID()); + + if (threaded.getMessageID() != null) { + if (threaded.getMessageID().equals(current.getMessageID()) + && threaded.getSubject().equals(current.getSubject())) { + current.setMessageThreadID(threaded.getMessageThreadID()); + } } } - + BlackboardArtifact msgArtifact = addEmailArtifact(current, abstractFile); - - if ((msgArtifact != null) && (current.hasAttachment())) { - derivedFiles.addAll(handleAttachments(current.getAttachments(), abstractFile, msgArtifact )); + + if ((msgArtifact != null) && (current.hasAttachment())) { + derivedFiles.addAll(handleAttachments(current.getAttachments(), abstractFile, msgArtifact)); } } @@ -477,6 +480,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { } context.addFilesToJob(derivedFiles); } + /** * Add the given attachments as derived files and reschedule them for * ingest. @@ -517,29 +521,30 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { } /** - * Finds and returns a set of unique email addresses found in the input string + * Finds and returns a set of unique email addresses found in the input + * string * * @param input - input string, like the To/CC line from an email header - * + * * @return Set: set of email addresses found in the input string */ private Set findEmailAddresess(String input) { Pattern p = Pattern.compile("\\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}\\b", - Pattern.CASE_INSENSITIVE); + Pattern.CASE_INSENSITIVE); Matcher m = p.matcher(input); Set emailAddresses = new HashSet<>(); while (m.find()) { - emailAddresses.add( m.group()); + emailAddresses.add(m.group()); } return emailAddresses; } - + /** * Add a blackboard artifact for the given e-mail message. * * @param email The e-mail message. * @param abstractFile The associated file. - * + * * @return The generated e-mail message artifact. */ @Messages({"ThunderbirdMboxFileIngestModule.addArtifact.indexError.message=Failed to index email message detected artifact for keyword search."}) @@ -563,73 +568,69 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { List senderAddressList = new ArrayList<>(); String senderAddress; senderAddressList.addAll(findEmailAddresess(from)); - + AccountFileInstance senderAccountInstance = null; if (senderAddressList.size() == 1) { senderAddress = senderAddressList.get(0); try { senderAccountInstance = currentCase.getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, senderAddress, EmailParserModuleFactory.getModuleName(), abstractFile); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Failed to create account for email address " + senderAddress, ex); //NON-NLS } - catch(TskCoreException ex) { - logger.log(Level.WARNING, "Failed to create account for email address " + senderAddress, ex); //NON-NLS - } + } else { + logger.log(Level.WARNING, "Failed to find sender address, from = {0}", from); //NON-NLS } - else { - logger.log(Level.WARNING, "Failed to find sender address, from = {0}", from); //NON-NLS - } - + List recipientAddresses = new ArrayList<>(); recipientAddresses.addAll(findEmailAddresess(to)); recipientAddresses.addAll(findEmailAddresess(cc)); recipientAddresses.addAll(findEmailAddresess(bcc)); - + List recipientAccountInstances = new ArrayList<>(); recipientAddresses.forEach((addr) -> { try { - AccountFileInstance recipientAccountInstance = - currentCase.getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, addr, - EmailParserModuleFactory.getModuleName(), abstractFile); + AccountFileInstance recipientAccountInstance + = currentCase.getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, addr, + EmailParserModuleFactory.getModuleName(), abstractFile); recipientAccountInstances.add(recipientAccountInstance); - } - catch(TskCoreException ex) { + } catch (TskCoreException ex) { logger.log(Level.WARNING, "Failed to create account for email address " + addr, ex); //NON-NLS } }); - + addArtifactAttribute(headers, ATTRIBUTE_TYPE.TSK_HEADERS, bbattributes); addArtifactAttribute(from, ATTRIBUTE_TYPE.TSK_EMAIL_FROM, bbattributes); addArtifactAttribute(to, ATTRIBUTE_TYPE.TSK_EMAIL_TO, bbattributes); addArtifactAttribute(subject, ATTRIBUTE_TYPE.TSK_SUBJECT, bbattributes); - + addArtifactAttribute(dateL, ATTRIBUTE_TYPE.TSK_DATETIME_RCVD, bbattributes); addArtifactAttribute(dateL, ATTRIBUTE_TYPE.TSK_DATETIME_SENT, bbattributes); - + addArtifactAttribute(body, ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_PLAIN, bbattributes); - - addArtifactAttribute(((id < 0L) ? NbBundle.getMessage(this.getClass(), "ThunderbirdMboxFileIngestModule.notAvail") : String.valueOf(id)), + + addArtifactAttribute(((id < 0L) ? NbBundle.getMessage(this.getClass(), "ThunderbirdMboxFileIngestModule.notAvail") : String.valueOf(id)), ATTRIBUTE_TYPE.TSK_MSG_ID, bbattributes); - - addArtifactAttribute(((localPath.isEmpty() == false) ? localPath : ""), + + addArtifactAttribute(((localPath.isEmpty() == false) ? localPath : ""), ATTRIBUTE_TYPE.TSK_PATH, bbattributes); - + addArtifactAttribute(cc, ATTRIBUTE_TYPE.TSK_EMAIL_CC, bbattributes); addArtifactAttribute(bodyHTML, ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_HTML, bbattributes); addArtifactAttribute(rtf, ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_RTF, bbattributes); addArtifactAttribute(threadID, ATTRIBUTE_TYPE.TSK_THREAD_ID, bbattributes); - - + try { - + bbart = abstractFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG); bbart.addAttributes(bbattributes); // Add account relationships - currentCase.getSleuthkitCase().getCommunicationsManager().addRelationships(senderAccountInstance, recipientAccountInstances, bbart,Relationship.Type.MESSAGE, dateL); - + currentCase.getSleuthkitCase().getCommunicationsManager().addRelationships(senderAccountInstance, recipientAccountInstances, bbart, Relationship.Type.MESSAGE, dateL); + try { // index the artifact for keyword search - blackboard.postArtifact(bbart, EmailParserModuleFactory.getModuleName()); + blackboard.postArtifact(bbart, EmailParserModuleFactory.getModuleName()); } catch (Blackboard.BlackboardException ex) { logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bbart.getArtifactID(), ex); //NON-NLS MessageNotifyUtil.Notify.error(Bundle.ThunderbirdMboxFileIngestModule_addArtifact_indexError_message(), bbart.getDisplayName()); @@ -640,11 +641,11 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { return bbart; } - + /** * Add an attribute of a specified type to a supplied Collection. - * - * @param stringVal The attribute value. + * + * @param stringVal The attribute value. * @param attrType The type of attribute to be added. * @param bbattributes The Collection to which the attribute will be added. */ @@ -656,7 +657,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { /** * Add an attribute of a specified type to a supplied Collection. - * + * * @param stringVal The attribute value. * @param attrType The type of attribute to be added. * @param bbattributes The Collection to which the attribute will be added. @@ -666,10 +667,10 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { bbattributes.add(new BlackboardAttribute(attrType, EmailParserModuleFactory.getModuleName(), stringVal)); } } - + /** * Add an attribute of a specified type to a supplied Collection. - * + * * @param longVal The attribute value. * @param attrType The type of attribute to be added. * @param bbattributes The Collection to which the attribute will be added. @@ -679,10 +680,10 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { bbattributes.add(new BlackboardAttribute(attrType, EmailParserModuleFactory.getModuleName(), longVal)); } } - + /** * Post an error message for the user. - * + * * @param subj The error subject. * @param details The error details. */ @@ -693,7 +694,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { /** * Get the IngestServices object. - * + * * @return The IngestServices object. */ IngestServices getServices() { From 9f3e258837e9826fb8e14cea95049dd93d01c9e7 Mon Sep 17 00:00:00 2001 From: Mark McKinnon Date: Thu, 17 Oct 2019 02:13:46 -0400 Subject: [PATCH 033/134] Update ThunderbirdMboxFileIngestModule.java Make codacy happy. --- .../thunderbirdparser/ThunderbirdMboxFileIngestModule.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java index b27f045398..91bf391e63 100644 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java @@ -458,12 +458,11 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { if (partialEmailsForThreading.size() > msgCnt) { EmailMessage threaded = partialEmailsForThreading.get(msgCnt++); - if (threaded.getMessageID() != null) { - if (threaded.getMessageID().equals(current.getMessageID()) - && threaded.getSubject().equals(current.getSubject())) { + if ((threaded.getMessageID() != null) && + (threaded.getMessageID().equals(current.getMessageID()) + && threaded.getSubject().equals(current.getSubject()))) { current.setMessageThreadID(threaded.getMessageThreadID()); } - } } BlackboardArtifact msgArtifact = addEmailArtifact(current, abstractFile); From a1477dc45e5ee966c8b83c1c04472b4b81940c7a Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Thu, 17 Oct 2019 10:30:12 -0400 Subject: [PATCH 034/134] Updated the seek to be more accurate --- .../contentviewers/MediaPlayerPanel.java | 58 ++++++++++++++----- 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java index 1d02dfcd2b..b9a14df207 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java @@ -24,6 +24,7 @@ import java.awt.event.ActionListener; import java.io.File; import java.io.IOException; import java.util.Arrays; +import java.util.EnumSet; import java.util.List; import java.util.SortedSet; import java.util.TreeSet; @@ -53,7 +54,10 @@ import org.sleuthkit.datamodel.TskData; import javafx.embed.swing.JFXPanel; import javax.swing.event.ChangeListener; import org.freedesktop.gstreamer.ClockTime; +import org.freedesktop.gstreamer.Format; import org.freedesktop.gstreamer.GstException; +import org.freedesktop.gstreamer.event.SeekFlags; +import org.freedesktop.gstreamer.event.SeekType; /** * This is a video player that is part of the Media View layered pane. It uses @@ -175,6 +179,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie private Bus.ERROR errorListener; private Bus.STATE_CHANGED stateChangeListener; private Bus.EOS endOfStreamListener; + private double playBackRate; //Update progress bar and time label during video playback private final Timer timer = new Timer(75, new VideoPanelUpdater()); @@ -196,6 +201,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie progressSlider.setMinimum(0); progressSlider.setMaximum(PROGRESS_SLIDER_SIZE); progressSlider.setValue(0); + playBackRate = 1.0; //Manage the gstreamer video position when a user is dragging the slider in the panel. progressSlider.addChangeListener(new ChangeListener() { @Override @@ -203,11 +209,19 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie if (progressSlider.getValueIsAdjusting()) { long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS); double relativePosition = progressSlider.getValue() * 1.0 / PROGRESS_SLIDER_SIZE; - long newPos = (long) (relativePosition * duration); - gstPlayBin.seek(newPos, TimeUnit.NANOSECONDS); + long newStartTime = (long) (relativePosition * duration); + gstPlayBin.seek(playBackRate, + Format.TIME, + //FLUSH - flushes the pipeline + //ACCURATE - video will seek exactly to the position requested + EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE), + //Set the start position to newTime + SeekType.SET, newStartTime, + //Do nothing for the end position + SeekType.NONE, -1); //Keep constantly updating the time label so users have a sense of //where the slider they are dragging is in relation to the video time - updateTimeLabel(newPos, duration); + updateTimeLabel(newStartTime, duration); } } }); @@ -241,11 +255,11 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie endOfStreamListener = new Bus.EOS() { @Override public void endOfStream(GstObject go) { - gstPlayBin.seek(ClockTime.ZERO); - progressSlider.setValue(0); - /** - * Keep the video from automatically playing - */ + gstPlayBin.seek(ClockTime.ZERO); + progressSlider.setValue(0); + /** + * Keep the video from automatically playing + */ Gst.getExecutor().submit(() -> gstPlayBin.pause()); } }; @@ -674,20 +688,36 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie private void rewindButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_rewindButtonActionPerformed long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); //Skip 30 seconds. - long skipBehind = TimeUnit.NANOSECONDS.convert(SKIP_IN_SECONDS, TimeUnit.SECONDS); + long rewindDelta = TimeUnit.NANOSECONDS.convert(SKIP_IN_SECONDS, TimeUnit.SECONDS); //Ensure new video position is within bounds - long newTime = Math.max(currentTime - skipBehind, 0); - gstPlayBin.seek(newTime, TimeUnit.NANOSECONDS); + long newTime = Math.max(currentTime - rewindDelta, 0); + gstPlayBin.seek(playBackRate, + Format.TIME, + //FLUSH - flushes the pipeline + //ACCURATE - video will seek exactly to the position requested + EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE), + //Set the start position to newTime + SeekType.SET, newTime, + //Do nothing for the end position + SeekType.NONE, -1); }//GEN-LAST:event_rewindButtonActionPerformed private void fastForwardButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_fastForwardButtonActionPerformed long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS); long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); //Skip 30 seconds. - long skipAhead = TimeUnit.NANOSECONDS.convert(SKIP_IN_SECONDS, TimeUnit.SECONDS); + long fastForwardDelta = TimeUnit.NANOSECONDS.convert(SKIP_IN_SECONDS, TimeUnit.SECONDS); //Ensure new video position is within bounds - long newTime = Math.min(currentTime + skipAhead, duration - 1); - gstPlayBin.seek(newTime, TimeUnit.NANOSECONDS); + long newTime = Math.min(currentTime + fastForwardDelta, duration); + gstPlayBin.seek(playBackRate, + Format.TIME, + //FLUSH - flushes the pipeline + //ACCURATE - video will seek exactly to the position requested + EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE), + //Set the start position to newTime + SeekType.SET, newTime, + //Do nothing for the end position + SeekType.NONE, -1); }//GEN-LAST:event_fastForwardButtonActionPerformed private void playButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_playButtonActionPerformed From 134f2946103bf55c8e6d78c69df8be0c1412563f Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Thu, 17 Oct 2019 11:08:27 -0400 Subject: [PATCH 035/134] Cleaned up kmlreport.java to use new classes --- .../datamodel/BlackboardArtifactPoint.java | 82 ++++++++++--- .../geolocation/datamodel/DefaultPoint.java | 113 +++++++++++++++++- .../datamodel/EXIFMetadataPoint.java | 14 ++- .../datamodel/GeolocationManager.java | 98 ++++++++++----- .../autopsy/geolocation/datamodel/Route.java | 50 ++++---- .../geolocation/datamodel/RoutePoint.java | 25 ++-- .../geolocation/datamodel/SimplePoint.java | 26 +++- .../autopsy/report/modules/kml/KMLReport.java | 100 +++++++++++----- 8 files changed, 381 insertions(+), 127 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/BlackboardArtifactPoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/BlackboardArtifactPoint.java index a93cb49f89..10549cef92 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/BlackboardArtifactPoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/BlackboardArtifactPoint.java @@ -1,5 +1,4 @@ /* - * * Autopsy Forensic Browser * * Copyright 2019 Basis Technology Corp. @@ -23,27 +22,82 @@ import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.TskCoreException; /** + * Interface to be implemented by all ArtifactPoint objects. * - * */ public interface BlackboardArtifactPoint { - BlackboardArtifact getArtifact() ; - - Long getTimestamp() ; - - String getLabel() ; - + + /** + * Get the BlackboardArtifact for which this point represents. + * + * @return BlackboardArtifact for this point. + */ + BlackboardArtifact getArtifact(); + + /** + * Get the timestamp for this BlackboardArtifact. + * + * @return Timestamp in epoch seconds or null if none was set. + */ + Long getTimestamp(); + + /** + * Get the label for this point object. + * + * @return String label for the point or null if none was set + */ + String getLabel(); + + /** + * Get the latitude for this point. + * + * @return Returns the latitude for the point or null if none was set + */ Double getLatitude(); - + + /** + * Get the longitude for this point. + * + * @return Returns the longitude for the point or null if none was set + */ Double getLongitude(); - + + /** + * Get the Altitude for this point. + * + * @return Returns the Altitude for the point or null if none was set + */ Double getAltitude(); + /** + * Get the details of this point as an HTML formated string. + * + * @return The details string or empty string if none was set. + */ String getDetails(); - - void initPosition() throws TskCoreException; - + + /** + * initPoint is the function where the work of setting the attributes the + * point. + * + * @throws TskCoreException + */ + void initPoint() throws TskCoreException; + + /** + * Get the latitude and longitude as a formatted string. + * + * @return Formatted String. + */ String getFormattedCoordinates(); - + + /** + * Get the latitude and longitude formatted as given. + * + * @param format The format String should assume that latitude will be + * passed first and that both are number values. + * + * @return Formatted string. + */ String getFormattedCoordinates(String format); } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/DefaultPoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/DefaultPoint.java index 3bf1e56a82..8fdf463363 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/DefaultPoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/DefaultPoint.java @@ -25,13 +25,12 @@ import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.TskCoreException; /** - * - * + * Abstract class representing the basics of a BlackboardARtifacrPoint. */ public abstract class DefaultPoint implements BlackboardArtifactPoint{ final private BlackboardArtifact artifact; - private String label = null; + private String label = ""; private Long timestamp = null; private String details = null; private Double longitude = null; @@ -40,11 +39,31 @@ public abstract class DefaultPoint implements BlackboardArtifactPoint{ private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX"); private static final String DEFAULT_COORD_FORMAT = "%.2f, %.2f"; - + + /** + * Construct a new point with the given artifact. + * + * @param artifact + */ public DefaultPoint(BlackboardArtifact artifact) { this.artifact = artifact; } + /** + * Construct a new point with the given parameters. + * + * @param artifact The BlackboardArtifact object that this point represents + * @param latitude The latitude for the given artifact + * @param longitude The longitude for the given artifact + * @param label A label for the point + */ + public DefaultPoint(BlackboardArtifact artifact, Double latitude, Double longitude, String label) { + this.artifact = artifact; + this.latitude = latitude; + this.longitude = longitude; + this.label = label; + } + @Override public BlackboardArtifact getArtifact() { return artifact; @@ -55,6 +74,11 @@ public abstract class DefaultPoint implements BlackboardArtifactPoint{ return timestamp; } + /** + * Set the timestamp for this point. + * + * @param timestamp Epoch seconds + */ protected void setTimestamp(Long timestamp) { this.timestamp = timestamp; } @@ -64,6 +88,11 @@ public abstract class DefaultPoint implements BlackboardArtifactPoint{ return label; } + /** + * Set the label for this point. + * + * @param label String label for the point. + */ protected void setLabel(String label) { this.label = label; } @@ -73,6 +102,11 @@ public abstract class DefaultPoint implements BlackboardArtifactPoint{ return details; } + /** + * Set the details information for the point. + * + * @param details Formatted detail information for display. + */ protected void setDetails(String details) { this.details = details; } @@ -82,6 +116,11 @@ public abstract class DefaultPoint implements BlackboardArtifactPoint{ return latitude; } + /** + * Set the latitude for this point. + * + * @param latitude Double latitude value + */ protected void setLatitude(Double latitude) { this.latitude = latitude; } @@ -91,6 +130,11 @@ public abstract class DefaultPoint implements BlackboardArtifactPoint{ return longitude; } + /** + * Set the longitude for this point. + * + * @param longitude Double longitude value + */ protected void setLongitude(Double longitude) { this.longitude = longitude; } @@ -100,6 +144,11 @@ public abstract class DefaultPoint implements BlackboardArtifactPoint{ return altitude; } + /** + * Set the altitude for the point. + * + * @param altitude Double altitude value + */ protected void setAltitude(Double altitude) { this.altitude = altitude; } @@ -115,6 +164,8 @@ public abstract class DefaultPoint implements BlackboardArtifactPoint{ } /** + * This function with its formatting is from the original KMLReport. + * * This method creates a text description for a map feature using all the * geospatial and time data we can for the Artifact. It queries the * following attributes: @@ -136,7 +187,7 @@ public abstract class DefaultPoint implements BlackboardArtifactPoint{ StringBuilder result = new StringBuilder(); //NON-NLS result.append("

"); - result.append(getArtifact().getArtifactTypeName()); + result.append(BlackboardArtifact.ARTIFACT_TYPE.fromLabel(getArtifact().getArtifactTypeName()).getDisplayName()); result.append("

"); String name = getString(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); @@ -237,27 +288,77 @@ public abstract class DefaultPoint implements BlackboardArtifactPoint{ return result.toString(); } + /** + * Helper function for getting a String attribute from an artifact. + * + * @param type BlackboardAttribute type + * + * @return String value for the given attribute. + * + * @throws TskCoreException + */ String getString(BlackboardAttribute.ATTRIBUTE_TYPE type) throws TskCoreException{ BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(type)); return (attribute != null ? attribute.getValueString() : null); } + /** + * Helper function for getting a Double attribute from an artifact. + * + * @param type BlackboardAttribute type + * + * @return Double value for the given attribute. + * + * @throws TskCoreException + */ Double getDouble(BlackboardAttribute.ATTRIBUTE_TYPE type) throws TskCoreException{ BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(type)); return (attribute != null ? attribute.getValueDouble() : null); } + /** + * Helper function for getting a Long attribute from an artifact. + * + * @param type BlackboardAttribute type + * + * @return Long value for the given attribute. + * + * @throws TskCoreException + */ Long getLong(BlackboardAttribute.ATTRIBUTE_TYPE type) throws TskCoreException{ BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(type)); return (attribute != null ? attribute.getValueLong() : null); } + /** + * Helper function for getting a Integer attribute from an artifact. + * + * @param type BlackboardAttribute type + * + * @return Integer value for the given attribute. + * + * @throws TskCoreException + */ Integer getInteger(BlackboardAttribute.ATTRIBUTE_TYPE type) throws TskCoreException{ BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(type)); return (attribute != null ? attribute.getValueInt() : null); } + /** + * Helper function for consistently formatting the timestamp. + * + * @param type BlackboardAttribute type + * + * @return The timestamp value formatted as string, or empty string if no + * timestamp is available. + * + * @throws TskCoreException + */ String getTimeStamp(long timeStamp) { - return DATE_FORMAT.format(new java.util.Date(getTimestamp() * 1000)); + if(getTimestamp() != null) { + return DATE_FORMAT.format(new java.util.Date(getTimestamp() * 1000)); + } else { + return ""; + } } } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/EXIFMetadataPoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/EXIFMetadataPoint.java index ea444a9d06..67056fa2b3 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/EXIFMetadataPoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/EXIFMetadataPoint.java @@ -25,14 +25,15 @@ import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.TskCoreException; /** - * + * Extends SimplePoint for TSK_EXIF_METADATA artifacts. * */ public class EXIFMetadataPoint extends SimplePoint{ private AbstractFile imageFile; /** - * Construct a EXIF way point + * Construct a EXIF point + * * @param artifact */ EXIFMetadataPoint(BlackboardArtifact artifact) { @@ -40,8 +41,8 @@ public class EXIFMetadataPoint extends SimplePoint{ } @Override - public void initPosition() throws TskCoreException{ - super.initPosition(); + public void initPoint() throws TskCoreException{ + super.initPoint(); setTimestamp(getLong(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED)); @@ -53,6 +54,11 @@ public class EXIFMetadataPoint extends SimplePoint{ setLabel(imageFile.getName()); } + /** + * Get the image for this point. + * + * @return Return the AbstractFile image for the EXIF_METADATA artifact. + */ public AbstractFile getImage() { return imageFile; } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationManager.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationManager.java index 651ea22748..05334473ea 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationManager.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationManager.java @@ -26,41 +26,73 @@ import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; /** + * Static functions for the creations of geolocation points for artifacts. * - * */ public class GeolocationManager { - - static public List getPoints(SleuthkitCase skCase, boolean includeRoute) throws TskCoreException { + + /** + * Returns a list of points 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 + * + * Optionally TSK_GPS_ROUTE points can also be added to the list. + * + * @param skCase Currently open SleuthkitCase + * @param includeRoutes True to include the points at are in TSK_GPS_ROUTE + * objects + * + * @return List of BlackboardArtifactPoints + * + * @throws TskCoreException + */ + static public List getPoints(SleuthkitCase skCase, boolean includeRoutes) throws TskCoreException { List points = new ArrayList<>(); - + points.addAll(getSimplePoints(skCase)); points.addAll(getEXIFPoints(skCase)); - - if(includeRoute) { - points.addAll(getGPSRouteWaypoints(skCase)); + + if (includeRoutes) { + points.addAll(getGPSRoutePoints(skCase)); } - - + return points; } - - static public List getGPSRoutes(SleuthkitCase skCase) throws TskCoreException{ + + /** + * Gets the list of Routes from the TSK_GPS_ROUTE artifacts. + * + * @param skCase Currently open SleuthkitCase + * + * @return List of Route objects, empty list will be returned if no Routes + * where found + * + * @throws TskCoreException + */ + static public List getGPSRoutes(SleuthkitCase skCase) throws TskCoreException { List routes = new ArrayList<>(); List artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE); - for (BlackboardArtifact artifact : artifacts) { + for (BlackboardArtifact artifact : artifacts) { Route route = new Route(artifact); route.initRoute(); routes.add(route); - } + } return routes; } /** + * Get a list of BlackboardArtifactPoints for the "simple" GPS artifacts. + * Artifacts that will be included: TSK_GPS_TRACKPOINT TSK_GPS_SEARCH + * TSK_GPS_LAST_KNOWN_LOCATION TSK_GPS_BOOKMARK * - * @param skCase + * BlackboardArtifactPoint objects will be created and added to the list + * only for artifacts with TSK_GEO_LONGITUDE and TSK_LATITUDE attributes. * - * @return + * @param skCase Currently open SleuthkitCase + * + * @return List of BlackboardArtifactPoints for above artifacts or empty + * list if none where found. * * @throws TskCoreException */ @@ -68,9 +100,6 @@ public class GeolocationManager { List points = new ArrayList<>(); - // TSK_GPS_TRACKPOINT, TSK_GPS_SEARCH, TSK_GPS_LAST_KNOWN_LOCATION - // and TSK_GPS_BOOKMARK have similar attributes and can be processed - // similarly List artifacts = new ArrayList<>(); artifacts.addAll(skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT)); artifacts.addAll(skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH)); @@ -79,9 +108,10 @@ public class GeolocationManager { for (BlackboardArtifact artifact : artifacts) { BlackboardArtifactPoint point = new SimplePoint(artifact); - point.initPosition(); - // Good point only if it has the location - if(point.getLatitude() != null && point.getLongitude() != null) { + point.initPoint(); + // Only add to the list if the point has a valid latitude + // and longitude. + if (point.getLatitude() != null && point.getLongitude() != null) { points.add(point); } } @@ -90,10 +120,15 @@ public class GeolocationManager { } /** + * Get a list of BlackboardArtifactPoints for TSK_METADATA_EXIF artifacts. * - * @param skCase + * BlackboardArtifactPoint objects will be created and added to the list + * only for artifacts with TSK_GEO_LONGITUDE and TSK_LATITUDE attributes. * - * @return + * @param skCase Currently open SleuthkitCase + * + * @return List of BlackboardArtifactPoints for above artifacts or empty + * list if none where found. * * @throws TskCoreException */ @@ -103,24 +138,25 @@ public class GeolocationManager { for (BlackboardArtifact artifact : artifacts) { BlackboardArtifactPoint point = new EXIFMetadataPoint(artifact); - point.initPosition(); - if(point.getLatitude() != null && point.getLongitude() != null) { + point.initPoint(); + if (point.getLatitude() != null && point.getLongitude() != null) { points.add(point); } } return points; } - - /** + + /** + * Get a list of BlackboardArtifactPoints from the list of routes. * - * @param skCase + * @param skCase Currently open SleuthkitCase * - * @return + * @return A list of route points, or empty list if none were found. * * @throws TskCoreException */ - static private List getGPSRouteWaypoints(SleuthkitCase skCase) throws TskCoreException { + static private List getGPSRoutePoints(SleuthkitCase skCase) throws TskCoreException { List points = new ArrayList<>(); for (Route route : getGPSRoutes(skCase)) { @@ -129,5 +165,5 @@ public class GeolocationManager { return points; } - + } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java index fc17096570..47e5ccbea3 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java @@ -26,12 +26,14 @@ import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.TskCoreException; /** - * + * A Route represents a TSK_GPS_ROUTE artifact which has a start and end point + * however the class was written with the assumption that some routes may have + * more that two points. * */ public class Route { private final BlackboardArtifact artifact; - private final List waypoints; + private final List points; private long timestamp; private String details; private Double altitude = null; @@ -43,7 +45,7 @@ public class Route { */ protected Route(BlackboardArtifact artifact) { this.artifact = artifact; - waypoints = new ArrayList<>(); + points = new ArrayList<>(); } /** @@ -52,7 +54,7 @@ public class Route { * @return List of ArtifactWaypoints for this route */ public List getRoute() { - return waypoints; + return points; } public String getDetails() { @@ -81,36 +83,38 @@ public class Route { Double latitude; Double longitude; + BlackboardAttribute attribute; + + attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START)); + latitude = attribute != null ? attribute.getValueDouble() : null; - latitude = getDoubleAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START); - longitude = getDoubleAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START); + attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START)); + longitude = attribute != null ? attribute.getValueDouble() : null; if (latitude != null && longitude != null) { - waypoints.add(new RoutePoint(this, latitude, longitude, "Start")); + RoutePoint point = new RoutePoint(artifact, this, latitude, longitude, "Start"); + point.initPoint(); + points.add(point); } - latitude = getDoubleAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END); - longitude = getDoubleAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END); + attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END)); + latitude = attribute != null ? attribute.getValueDouble() : null; + + attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END)); + longitude = attribute != null ? attribute.getValueDouble() : null; if (latitude != null && longitude != null) { - waypoints.add(new RoutePoint(this, latitude, longitude, "End")); + RoutePoint point = new RoutePoint(artifact, this, latitude, longitude, "End"); + point.initPoint(); + points.add(point); } - altitude = getDoubleAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE); + attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE)); + altitude = attribute != null ? attribute.getValueDouble() : null; // Get the creation date - BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME)); - Long dateTime = attribute != null ? attribute.getValueLong() : null; - if (dateTime != null) { - timestamp = dateTime * 1000; - } - } - - private Double getDoubleAttribute(BlackboardAttribute.ATTRIBUTE_TYPE type) throws TskCoreException{ - BlackboardAttribute attribute; - - attribute = artifact.getAttribute(new BlackboardAttribute.Type(type)); - return attribute != null ? attribute.getValueDouble() : null; + attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME)); + timestamp = attribute != null ? attribute.getValueLong() : null; } } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/RoutePoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/RoutePoint.java index 784b0ea4f1..aa830a2581 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/RoutePoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/RoutePoint.java @@ -19,40 +19,35 @@ */ package org.sleuthkit.autopsy.geolocation.datamodel; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.TskCoreException; + public class RoutePoint extends DefaultPoint { private final Route parent; /** - * Construct a route way point. + * Construct a route for a route. * * @param parent The parent route object. - * @param latitude Latitude for waypoint - * @param longitude Longitude for waypoint + * @param latitude Latitude for point + * @param longitude Longitude for point * @param label Way point label. */ - protected RoutePoint(Route parent, double latitude, double longitude, String label) { - super(parent.getArtifact()); - + protected RoutePoint(BlackboardArtifact artifact, Route parent, double latitude, double longitude, String label) { + super(artifact, latitude, longitude, label); this.parent = parent; - setLabel(label); - setLongitude(longitude); - setLatitude(latitude); } @Override - public void initPosition() { + public void initPoint() throws TskCoreException{ + setDetails(getDetailsFromArtifact()); } @Override public Long getTimestamp() { return parent.getTimestamp(); } - - @Override - public String getDetails() { - return parent.getDetails(); - } @Override public Double getAltitude() { diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/SimplePoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/SimplePoint.java index e75443af51..db091b37f0 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/SimplePoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/SimplePoint.java @@ -25,18 +25,28 @@ import org.sleuthkit.datamodel.TskCoreException; /** * + * A basic Artifact point for artifacts that use the following attributes: + * TSK_GEO_LONGITUDE + * TSK_GEO_LATITUDE + * TSK_GEO_ALTITUDE + * TSK_DATETIME * */ public class SimplePoint extends DefaultPoint{ + /** + * Construct a simple point object. + * + * @param artifact + */ SimplePoint(BlackboardArtifact artifact) { super(artifact); } @Override - public void initPosition() throws TskCoreException{ - setLongitude(getDouble(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE)); - setLatitude(getDouble(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE)); + public void initPoint() throws TskCoreException{ + setLongitude(getDouble(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE)); + setLatitude(getDouble(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE)); setAltitude(getDouble(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE)); setTimestamp(getLong(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME)); @@ -44,6 +54,16 @@ public class SimplePoint extends DefaultPoint{ setLabel(getLabelBasedOnType()); } + /** + * Create the point label based on the artifact type. + * + * This code needs to be revisited to make sure these back up labels make + * sense. + * + * @return A label for the point. + * + * @throws TskCoreException + */ String getLabelBasedOnType() throws TskCoreException{ String label = getString(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); 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 fe5ab34556..c494ab612b 100644 --- a/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java +++ b/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java @@ -201,7 +201,11 @@ class KMLReport implements GeneralReportModule { progressPanel.complete(result, errorMessage); } - + /** + * Do all of the setting up of elements needed for the report. + * + * @return The report document object. + */ private Document setupReportDocument() { ns = Namespace.getNamespace("", "http://www.opengis.net/kml/2.2"); //NON-NLS @@ -274,15 +278,22 @@ class KMLReport implements GeneralReportModule { return kmlDocument; } - - void addExifMetadataContent(EXIFMetadataPoint location, String baseReportDirectory) throws IOException{ - Element point = makePoint(location); - if(point == null) { + /** + * For the given point, create the data needed for the EXIF_METADATA + * + * @param location The geolocation of the data + * @param baseReportDirectory The report directory where the image will be created. + * + * @throws IOException + */ + void addExifMetadataContent(EXIFMetadataPoint point, String baseReportDirectory) throws IOException{ + Element mapPoint = makePoint(point); + if(mapPoint == null) { return; } - AbstractFile abstractFile = location.getImage(); - String details = "

EXIF Metadata With Locations

" + location.getDetails(); + AbstractFile abstractFile = point.getImage(); + String details = point.getDetails(); Path path; copyFileUsingStream(abstractFile, Paths.get(baseReportDirectory, abstractFile.getName()).toFile()); @@ -295,9 +306,17 @@ class KMLReport implements GeneralReportModule { path = Paths.get(abstractFile.getName()); } - gpsExifMetadataFolder.addContent(makePlacemarkWithPicture(abstractFile.getName(), FeatureColor.RED, details, location.getTimestamp(), point, path, location.getFormattedCoordinates())); + gpsExifMetadataFolder.addContent(makePlacemarkWithPicture(abstractFile.getName(), FeatureColor.RED, details, point.getTimestamp(), mapPoint, path, point.getFormattedCoordinates())); } + /** + * Add the new location to the correct folder based on artifact type. + * @param skCase Currently open case + * @param baseReportDir Output directory for the report. + * + * @throws TskCoreException + * @throws IOException + */ void addLocationsToReport(SleuthkitCase skCase, String baseReportDir) throws TskCoreException, IOException { List points = GeolocationManager.getPoints(skCase, false); @@ -321,6 +340,13 @@ class KMLReport implements GeneralReportModule { } } + /** + * Add the route to the route folder in the document. + * + * @param skCase Currently open case. + * + * @throws TskCoreException + */ void makeRoutes(SleuthkitCase skCase) throws TskCoreException { List routes = GeolocationManager.getGPSRoutes(skCase); @@ -349,28 +375,47 @@ class KMLReport implements GeneralReportModule { String formattedCoordinates = String.format("%s to %s", start.getFormattedCoordinates(), end.getFormattedCoordinates()); - gpsRouteFolder.addContent(makePlacemark("As-the-crow-flies Route", FeatureColor.GREEN, route.getDetails(), route.getTimestamp(), reportRoute, formattedCoordinates)); //NON-NLS - gpsRouteFolder.addContent(makePlacemark(start.getLabel(), FeatureColor.GREEN, start.getDetails(), start.getTimestamp(), startingPoint, start.getFormattedCoordinates())); //NON-NLS - gpsRouteFolder.addContent(makePlacemark(end.getLabel(), FeatureColor.GREEN, end.getDetails(), end.getTimestamp(), endingPoint, end.getFormattedCoordinates())); //NON-NLS + if(reportRoute != null) { + gpsRouteFolder.addContent(makePlacemark("As-the-crow-flies Route", FeatureColor.GREEN, "", route.getTimestamp(), reportRoute, formattedCoordinates)); //NON-NLS + } + + if(startingPoint != null) { + gpsRouteFolder.addContent(makePlacemark(start.getLabel(), FeatureColor.GREEN, start.getDetails(), start.getTimestamp(), startingPoint, start.getFormattedCoordinates())); //NON-NLS + } + + if(endingPoint != null) { + gpsRouteFolder.addContent(makePlacemark(end.getLabel(), FeatureColor.GREEN, end.getDetails(), end.getTimestamp(), endingPoint, end.getFormattedCoordinates())); //NON-NLS + } } } } - + /** + * Format a point time stamp (in seconds) to the report format. + * + * @param timeStamp The timestamp in epoch seconds. + * + * @return The formatted timestamp + */ private String getTimeStamp(long timeStamp) { return kmlDateFormat.format(new java.util.Date(timeStamp * 1000)); } - private Element makePoint(BlackboardArtifactPoint location) { - return makePoint(location.getLatitude(), location.getLongitude(), location.getAltitude()); + /** + * Create the point for the given artifact. + * + * @param point Artifact point. + * + * @return point element. + */ + private Element makePoint(BlackboardArtifactPoint point) { + return makePoint(point.getLatitude(), point.getLongitude(), point.getAltitude()); } /** * Create a Point for use in a Placemark. Note in this method altitude is * ignored, as Google Earth apparently has trouble using altitudes for - * LineStrings, though the parameters are still in the call. Also note that - * any null value passed in will be set to 0.0, under the idea that it is - * better to show some data with gaps, than to show nothing at all. + * LineStrings, though the parameters are still in the call. * * @param latitude point latitude * @param longitude point longitude @@ -411,9 +456,10 @@ class KMLReport implements GeneralReportModule { * Create a LineString for use in a Placemark. Note in this method, start * and stop altitudes get ignored, as Google Earth apparently has trouble * using altitudes for LineStrings, though the parameters are still in the - * call. Also note that any null value passed in will be set to 0.0, under - * the idea that it is better to show some data with gaps, than to show - * nothing at all. + * call. + * + * If null values are pass for the latitudes or longitudes a line will not be + * drawn. * * @param startLatitude Starting latitude * @param startLongitude Starting longitude @@ -425,21 +471,13 @@ class KMLReport implements GeneralReportModule { * @return the Line as an Element */ private Element makeLineString(Double startLatitude, Double startLongitude, Double startAltitude, Double stopLatitude, Double stopLongitude, Double stopAltitude) { - if (startLatitude == null) { - startLatitude = 0.0; - } - if (startLongitude == null) { - startLongitude = 0.0; + if(startLatitude == null || startLongitude == null || stopLatitude == null || stopLongitude == null) { + return null; } + if (startAltitude == null) { startAltitude = 0.0; } - if (stopLatitude == null) { - stopLatitude = 0.0; - } - if (stopLongitude == null) { - stopLongitude = 0.0; - } if (stopAltitude == null) { stopAltitude = 0.0; } From 7833596424b34167fb6290ee1d25f21851f2c262 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Thu, 17 Oct 2019 17:08:39 -0400 Subject: [PATCH 036/134] Implemented seeks with configurable playback rates --- .../autopsy/contentviewers/Bundle.properties | 3 +- .../contentviewers/Bundle.properties-MERGED | 3 +- .../contentviewers/MediaPlayerPanel.form | 95 +++++++++- .../contentviewers/MediaPlayerPanel.java | 162 +++++++++++++++--- 4 files changed, 228 insertions(+), 35 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties index 5734df98d9..facc16426f 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties @@ -91,4 +91,5 @@ MediaPlayerPanel.rewindButton.text=\u2bc7\u2bc7 MediaPlayerPanel.fastForwardButton.text=\u25ba\u25ba MediaPlayerPanel.playButton.text=\u25ba MediaPlayerPanel.infoLabel.text=No Errors -MediaPlayerPanel.VolumeIcon.text=\ \ \ \ \ Volume +MediaPlayerPanel.VolumeIcon.text=Volume +MediaPlayerPanel.playBackSpeedLabel.text=Speed: diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED index 1209c49953..b90b0d5d61 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED @@ -161,7 +161,8 @@ MediaPlayerPanel.rewindButton.text=\u2bc7\u2bc7 MediaPlayerPanel.fastForwardButton.text=\u25ba\u25ba MediaPlayerPanel.playButton.text=\u25ba MediaPlayerPanel.infoLabel.text=No Errors -MediaPlayerPanel.VolumeIcon.text=\ \ \ \ \ Volume +MediaPlayerPanel.VolumeIcon.text=Volume +MediaPlayerPanel.playBackSpeedLabel.text=Speed: # {0} - tableName SQLiteViewer.readTable.errorText=Error getting rows for table: {0} # {0} - tableName diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form index 230254020d..8fc346b491 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form @@ -41,7 +41,7 @@ - +
@@ -55,14 +55,20 @@ - - - - + + + + + + + + + + - +
@@ -74,9 +80,12 @@ - - - + + + + + + @@ -202,6 +211,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java index b9a14df207..369fa19fec 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java @@ -52,8 +52,8 @@ import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.TskData; import javafx.embed.swing.JFXPanel; +import javax.swing.SwingUtilities; import javax.swing.event.ChangeListener; -import org.freedesktop.gstreamer.ClockTime; import org.freedesktop.gstreamer.Format; import org.freedesktop.gstreamer.GstException; import org.freedesktop.gstreamer.event.SeekFlags; @@ -179,7 +179,6 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie private Bus.ERROR errorListener; private Bus.STATE_CHANGED stateChangeListener; private Bus.EOS endOfStreamListener; - private double playBackRate; //Update progress bar and time label during video playback private final Timer timer = new Timer(75, new VideoPanelUpdater()); @@ -201,7 +200,6 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie progressSlider.setMinimum(0); progressSlider.setMaximum(PROGRESS_SLIDER_SIZE); progressSlider.setValue(0); - playBackRate = 1.0; //Manage the gstreamer video position when a user is dragging the slider in the panel. progressSlider.addChangeListener(new ChangeListener() { @Override @@ -210,6 +208,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS); double relativePosition = progressSlider.getValue() * 1.0 / PROGRESS_SLIDER_SIZE; long newStartTime = (long) (relativePosition * duration); + double playBackRate = getPlayBackRate(); gstPlayBin.seek(playBackRate, Format.TIME, //FLUSH - flushes the pipeline @@ -235,10 +234,12 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie errorListener = new Bus.ERROR() { @Override public void errorMessage(GstObject go, int i, String string) { - enableComponents(false); - infoLabel.setText(String.format( - "%s", - MEDIA_PLAYER_ERROR_STRING)); + SwingUtilities.invokeLater(() -> { + enableComponents(false); + infoLabel.setText(String.format( + "%s", + MEDIA_PLAYER_ERROR_STRING)); + }); timer.stop(); } }; @@ -246,17 +247,34 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie @Override public void stateChanged(GstObject go, State oldState, State currentState, State pendingState) { if (State.PLAYING.equals(currentState)) { - playButton.setText("||"); + SwingUtilities.invokeLater(() -> { + playButton.setText("||"); + }); } else { - playButton.setText("►"); + SwingUtilities.invokeLater(() -> { + playButton.setText("►"); + }); } } }; endOfStreamListener = new Bus.EOS() { @Override public void endOfStream(GstObject go) { - gstPlayBin.seek(ClockTime.ZERO); - progressSlider.setValue(0); + double playBackRate = getPlayBackRate(); + gstPlayBin.seek(playBackRate, + Format.TIME, + //FLUSH - flushes the pipeline + //ACCURATE - video will seek exactly to the position requested + EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE), + //Set the start position to newTime + SeekType.SET, 0, + //Do nothing for the end position + SeekType.NONE, -1); + + SwingUtilities.invokeLater(() -> { + progressSlider.setValue(0); + }); + /** * Keep the video from automatically playing */ @@ -334,6 +352,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie audioSlider.setEnabled(isEnabled); rewindButton.setEnabled(isEnabled); fastForwardButton.setEnabled(isEnabled); + playBackSpeedComboBox.setEnabled(isEnabled); } @Override @@ -392,6 +411,17 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie private void updateTimeLabel(long start, long total) { progressLabel.setText(formatTime(start) + "/" + formatTime(total)); } + + /** + * Reads the current selected playback rate from the speed combo box. + * + * @return The selected rate. + */ + private double getPlayBackRate() { + int selectIndex = playBackSpeedComboBox.getSelectedIndex(); + String selectText = playBackSpeedComboBox.getItemAt(selectIndex); + return Double.valueOf(selectText.substring(0, selectText.length()-1)); + } /** * Convert nanoseconds into an HH:MM:SS format. @@ -433,7 +463,11 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie protected Void doInBackground() throws Exception { if (!tempFile.exists() || tempFile.length() < sourceFile.getSize()) { progress = ProgressHandle.createHandle(NbBundle.getMessage(MediaPlayerPanel.class, "GstVideoPanel.ExtractMedia.progress.buffering", sourceFile.getName()), () -> this.cancel(true)); - progressLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progress.buffering")); + + SwingUtilities.invokeLater(() -> { + progressLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progress.buffering")); + }); + progress.start(100); try { Files.createParentDirs(tempFile); @@ -517,12 +551,14 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie * pipeline. We start this updater when data-flow has just been * initiated so buffering may still be in progress. */ - if (duration > 0 && position > 0) { + if (duration >= 0 && position >= 0) { double relativePosition = (double) position / duration; progressSlider.setValue((int) (relativePosition * PROGRESS_SLIDER_SIZE)); } - updateTimeLabel(position, duration); + SwingUtilities.invokeLater(()-> { + updateTimeLabel(position, duration); + }); } } } @@ -548,6 +584,9 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie VolumeIcon = new javax.swing.JLabel(); audioSlider = new javax.swing.JSlider(); infoLabel = new javax.swing.JLabel(); + playBackPanel = new javax.swing.JPanel(); + playBackSpeedComboBox = new javax.swing.JComboBox<>(); + playBackSpeedLabel = new javax.swing.JLabel(); javax.swing.GroupLayout videoPanelLayout = new javax.swing.GroupLayout(videoPanel); videoPanel.setLayout(videoPanelLayout); @@ -557,7 +596,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie ); videoPanelLayout.setVerticalGroup( videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 124, Short.MAX_VALUE) + .addGap(0, 125, Short.MAX_VALUE) ); progressSlider.setValue(0); @@ -641,6 +680,39 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie org.openide.awt.Mnemonics.setLocalizedText(infoLabel, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.infoLabel.text")); // NOI18N infoLabel.setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR)); + playBackSpeedComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "0.25x", "0.50x", "0.75x", "1x", "1.25x", "1.50x", "1.75x", "2x" })); + playBackSpeedComboBox.setSelectedIndex(3); + playBackSpeedComboBox.setMaximumSize(new java.awt.Dimension(53, 23)); + playBackSpeedComboBox.setMinimumSize(new java.awt.Dimension(53, 23)); + playBackSpeedComboBox.setPreferredSize(new java.awt.Dimension(53, 23)); + playBackSpeedComboBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + playBackSpeedComboBoxActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(playBackSpeedLabel, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.playBackSpeedLabel.text")); // NOI18N + + javax.swing.GroupLayout playBackPanelLayout = new javax.swing.GroupLayout(playBackPanel); + playBackPanel.setLayout(playBackPanelLayout); + playBackPanelLayout.setHorizontalGroup( + playBackPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(playBackPanelLayout.createSequentialGroup() + .addComponent(playBackSpeedLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(playBackSpeedComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(13, 13, 13)) + ); + playBackPanelLayout.setVerticalGroup( + playBackPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(playBackPanelLayout.createSequentialGroup() + .addGap(6, 6, 6) + .addGroup(playBackPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(playBackSpeedComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(playBackSpeedLabel)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + javax.swing.GroupLayout controlPanelLayout = new javax.swing.GroupLayout(controlPanel); controlPanel.setLayout(controlPanelLayout); controlPanelLayout.setHorizontalGroup( @@ -649,12 +721,16 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie .addContainerGap() .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(infoLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(buttonPanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 738, Short.MAX_VALUE) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, controlPanelLayout.createSequentialGroup() - .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(buttonPanel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 623, Short.MAX_VALUE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(progressLabel))) - .addContainerGap()) + .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(progressLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(playBackPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE)) + .addGap(10, 10, 10))) + .addGap(0, 0, 0)) ); controlPanelLayout.setVerticalGroup( controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -663,8 +739,10 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) .addComponent(progressLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(progressSlider, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(buttonPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGap(5, 5, 5) + .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(buttonPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(playBackPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(infoLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 28, Short.MAX_VALUE)) ); @@ -691,6 +769,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie long rewindDelta = TimeUnit.NANOSECONDS.convert(SKIP_IN_SECONDS, TimeUnit.SECONDS); //Ensure new video position is within bounds long newTime = Math.max(currentTime - rewindDelta, 0); + double playBackRate = getPlayBackRate(); gstPlayBin.seek(playBackRate, Format.TIME, //FLUSH - flushes the pipeline @@ -707,8 +786,14 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); //Skip 30 seconds. long fastForwardDelta = TimeUnit.NANOSECONDS.convert(SKIP_IN_SECONDS, TimeUnit.SECONDS); - //Ensure new video position is within bounds - long newTime = Math.min(currentTime + fastForwardDelta, duration); + + //Ignore any fast forward requests if we are nearing the end. + if(currentTime + fastForwardDelta >= duration) { + return; + } + + long newTime = currentTime + fastForwardDelta; + double playBackRate = getPlayBackRate(); gstPlayBin.seek(playBackRate, Format.TIME, //FLUSH - flushes the pipeline @@ -724,10 +809,36 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie if (gstPlayBin.isPlaying()) { gstPlayBin.pause(); } else { + double playBackRate = getPlayBackRate(); + long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); + //Set playback rate before play. + gstPlayBin.seek(playBackRate, + Format.TIME, + //FLUSH - flushes the pipeline + //ACCURATE - video will seek exactly to the position requested + EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE), + //Set the start position to newTime + SeekType.SET, currentTime, + //Do nothing for the end position + SeekType.NONE, -1); gstPlayBin.play(); - } + } }//GEN-LAST:event_playButtonActionPerformed + private void playBackSpeedComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_playBackSpeedComboBoxActionPerformed + double playBackRate = getPlayBackRate(); + long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); + gstPlayBin.seek(playBackRate, + Format.TIME, + //FLUSH - flushes the pipeline + //ACCURATE - video will seek exactly to the position requested + EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE), + //Set the position to the currentTime, we are only adjusting the + //playback rate. + SeekType.SET, currentTime, + SeekType.NONE, 0); + }//GEN-LAST:event_playBackSpeedComboBoxActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JLabel VolumeIcon; private javax.swing.JSlider audioSlider; @@ -735,6 +846,9 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie private javax.swing.JPanel controlPanel; private javax.swing.JButton fastForwardButton; private javax.swing.JLabel infoLabel; + private javax.swing.JPanel playBackPanel; + private javax.swing.JComboBox playBackSpeedComboBox; + private javax.swing.JLabel playBackSpeedLabel; private javax.swing.JButton playButton; private javax.swing.JLabel progressLabel; private javax.swing.JSlider progressSlider; From 8be5b01e0c2f8dfdac398b3db1a48a270d8e7f8d Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Fri, 18 Oct 2019 09:48:46 -0400 Subject: [PATCH 037/134] ignored ffw requests with less than 30s left, fixed bug with progress updating --- .../autopsy/contentviewers/MediaPlayerPanel.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java index b9a14df207..cce6d3687f 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java @@ -517,7 +517,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie * pipeline. We start this updater when data-flow has just been * initiated so buffering may still be in progress. */ - if (duration > 0 && position > 0) { + if (duration >= 0 && position >= 0) { double relativePosition = (double) position / duration; progressSlider.setValue((int) (relativePosition * PROGRESS_SLIDER_SIZE)); } @@ -707,8 +707,13 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); //Skip 30 seconds. long fastForwardDelta = TimeUnit.NANOSECONDS.convert(SKIP_IN_SECONDS, TimeUnit.SECONDS); - //Ensure new video position is within bounds - long newTime = Math.min(currentTime + fastForwardDelta, duration); + + //Ignore fast forward requests if there are less than 30 seconds left. + if(currentTime + fastForwardDelta >= duration) { + return; + } + + long newTime = currentTime + fastForwardDelta; gstPlayBin.seek(playBackRate, Format.TIME, //FLUSH - flushes the pipeline From e4299efff1116c279921fb8098df871c276441ff Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Fri, 18 Oct 2019 11:24:36 -0400 Subject: [PATCH 038/134] Finished second and third tutorials. --- docs/doxygen/Doxyfile | 5 +- docs/doxygen/images/reports_select.png | Bin 0 -> 23486 bytes docs/doxygen/main.dox | 2 +- docs/doxygen/modDSIngestTutorial.dox | 166 +++++++++++++++++++++++ docs/doxygen/modFileIngestTutorial.dox | 42 +++--- docs/doxygen/modReportModuleTutorial.dox | 123 +++++++++++++++++ 6 files changed, 314 insertions(+), 24 deletions(-) create mode 100644 docs/doxygen/images/reports_select.png create mode 100644 docs/doxygen/modReportModuleTutorial.dox diff --git a/docs/doxygen/Doxyfile b/docs/doxygen/Doxyfile index 0037b15063..d73868792a 100644 --- a/docs/doxygen/Doxyfile +++ b/docs/doxygen/Doxyfile @@ -772,8 +772,9 @@ INPUT = main.dox \ regressionTesting.dox \ native_libs.dox \ modDevPython.dox \ - modFileIngestTutorial.dox \ - modDSIngestTutorial.dox \ + modFileIngestTutorial.dox \ + modDSIngestTutorial.dox \ + modReportModuleTutorial.dox \ debugTsk.dox \ ../../Core/src \ ../../CoreLibs/src \ diff --git a/docs/doxygen/images/reports_select.png b/docs/doxygen/images/reports_select.png new file mode 100644 index 0000000000000000000000000000000000000000..fe4f1f10bd7f8622ca31f334fba3c323488db5d7 GIT binary patch literal 23486 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 literal 0 HcmV?d00001 diff --git a/docs/doxygen/main.dox b/docs/doxygen/main.dox index 094c61f612..230be1fd03 100644 --- a/docs/doxygen/main.dox +++ b/docs/doxygen/main.dox @@ -11,7 +11,7 @@ If these pages don't answer your question, then send the question to the Python Plugins menu item. That will open a subfolder in your AppData folder, such as "C:\Users\JDoe\AppData\Roaming\Autopsy\python_modules". + +Make a folder inside of there to store your module. Call it "DemoScript2". Copy the dataSourcengestModule.py sample file from github into the this new folder and rename it to FindContactsDb.py. + +\subsection python_tutorial2_script Writing The Script + +We are going to write a script that: +
    +
  • Queries the backend database for files of a given name
  • +
  • Opens the database
  • +
  • Queries data from the database and makes an artifact for each row
  • +
+ +Open the FindContactsDb.py script in your favorite text editor. The sample Autopsy Python modules all have TODO entries in them to let you know what you should change. The below steps jump from one TODO to the next. +
    +
  1. Factory Class Name: The first thing to do is rename the sample class name from "SampleJythonDataSourceIngestModuleFactory" to "ContactsDbIngestModuleFactory". In the sample module, there are several uses of this class name, so you should search and replace for these strings.
  2. +
  3. Name and Description: The next TODO entries are for names and descriptions. These are shown to users. For this example, we'll name it "Contacts Db Analyzer". The description can be anything you want. Note that Autopsy requires that modules have unique names, so don't make it too generic.
  4. +
  5. Ingest Module Class Name: The next thing to do is rename the ingest module class from "SampleJythonDataSourceIngestModule" to "ContactsDbIngestModule". Our usual naming convention is that this class is the same as the factory class with "Factory" removed from the end. There are a couple of places where this name is used, so do a search and replace in your code.
  6. +
  7. startUp() method: The startUp() method is where each module initializes. For our example, we don't need to do anything special in here except save a reference to the passed in context object. This is used later on to see if the module has been cancelled.
  8. +
  9. process() method: This is where we do our analysis and we'll focus on this more in the next section.
  10. +
+ +That's it. In the file-level ingest module, we had a shutdown() method, but we do not need that with data source-level ingest modules. When their process method is finished, it can shut itself down. The process() method will be called only once. + +\subsection python_tutorial2_process The process() Method + +The process method in a data source-level ingest module is passed in reference to the data source as a Content object and a Progress Bar class to update our progress.

+

For this tutorial, you can start by deleting the contents of the existing process() method in the sample module. The full source code is linked to at the end of this blog and shows more detail about a fully fledged module. We'll just cover the analytics in the blog.

+ +\subsubsection python_tutorial2_getting_files Getting Files +Because data source-level ingest modules are not passed in specific files to analyze, nearly all of these types of modules will need to use the org.sleuthkit.autopsy.casemodule.services.FileManager service to find relevant files. Check out the methods on that class to see the different ways that you can find files. + +NOTE: See the \ref python_tutorial2_running_exes section for an example of when you simply want to run a command line tool on a disk image instead of querying for files to analyze. + +For our example, we want to find all files named "contacts.db". The org.sleuthkit.autopsy.casemodule.services.FileManager class contains several findFiles() methods to help. You can search for all files with a given name or files with a given name in a particular folder. You can also use SQL syntax to match file patterns, such as "%.jpg" to find all files with a JPEG extension. + +Our example needs these two lines to get the FileManager for the current case and to find the files. +\verbatim +fileManager = Case.getCurrentCase().getServices().getFileManager() +files = fileManager.findFiles(dataSource, "contacts.db")\endverbatim + +findFiles() returns a list of AbstractFile objects. This gives you access to the file's metadata and content. + +For our example, we are going to open these SQLite files. That means that we need to save them to disk. This is less than ideal because it wastes time writing the data to disk and then reading it back in, but it is the only option with many libraries. If you are doing some other type analysis on the content, then you do not need to write it to disk. You can read directly from the AbstractFile (see the sample modules for specific code to do this). + +The org.sleuthkit.autopsy.datamodel.ContentUtils class provides a utility to save file content to disk. We'll make a path in the temp folder of our case directory. To prevent naming collisions, we'll name the file based on its unique ID. The following two lines save the file to lclDbPath. + +\verbatim +lclDbPath = os.path.join(Case.getCurrentCase().getTempDirectory(), str(file.getId()) + ".db") +ContentUtils.writeToFile(file, File(lclDbPath))\endverbatim + +\subsubsection python_tutorial2_analyzing_sqlite Analyzing SQLite +Next, we need to open the SQLite database. We are going to use the Java JDBC infrastructure for this. JDBC is Java's generic way of dealing with different types of databases. To open the database, we do this: +\verbatim +Class.forName("org.sqlite.JDBC").newInstance() +dbConn = DriverManager.getConnection("jdbc:sqlite:%s" % lclDbPath)\endverbatim + +With our connection in hand, we can do some queries. In our sample database, we have a single table named "contacts", which has columns for name, email, and phone. We first start by querying for all rows in our simple table: +\verbatim +stmt = dbConn.createStatement() +resultSet = stmt.executeQuery("SELECT * FROM contacts")\endverbatim + +For each row, we are going to get the values for the name, e-mail, and phone number and make a TSK_CONTACT artifact. Recall from the first tutorial that posting artifacts to the blackboard allows modules to communicate with each other and also allows you to easily display data to the user. The TSK_CONTACT artifact is for storing contact information. + +The basic approach in our example is to make an artifact of a given type (TSK_CONTACT) and have it be associated with the database it came from. We then make attributes for the name, email, and phone. The following code does this for each row in the database: +\verbatim +while resultSet.next(): + + # Make an artifact on the blackboard and give it attributes + art = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT) + + name = resultSet.getString("name") + art.addAttribute(BlackboardAttribute( + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME_PERSON.getTypeID(), + ContactsDbIngestModuleFactory.moduleName, name)) + + email = resultSet.getString("email") + art.addAttribute(BlackboardAttribute( + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL.getTypeID(), + ContactsDbIngestModuleFactory.moduleName, email)) + + phone = resultSet.getString("phone") + art.addAttribute(BlackboardAttribute( + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER.getTypeID(), + ContactsDbIngestModuleFactory.moduleName, phone))\endverbatim + +That's it. We've just found the databases, queried them, and made artifacts for the user to see. There are some final things though. First, we should fire off an event so that the UI updates and refreshes with the new artifacts. We can fire just one event after each database is parsed (or you could fire one for each artifact - it's up to you). + +\verbatim +IngestServices.getInstance().fireModuleDataEvent( + ModuleDataEvent(ContactsDbIngestModuleFactory.moduleName, + BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT, None))\endverbatim + +And the final thing is to clean up. We should close the database connections and delete our temporary file. +\verbatim +stmt.close() +dbConn.close() +os.remove(lclDbPath)\endverbatim + +\subsection python_tutorial2_niceties Niceties + +Data source-level ingest modules can run for quite some time. Therefore, data source-level ingest modules should do some additional things that file-level ingest modules do not need to. +
    +
  • Progress bars: Each data source-level ingest module will have its own progress bar in the lower right. A reference to it is passed into the process() method. You should update it to provide user feedback.
  • +
  • Cancellation: A user could cancel ingest while your module is running. You should periodically check if that occurred so that you can bail out as soon as possible. You can do that with a check of: +\verbatim if self.context.isJobCancelled():\endverbatim
  • +
+ +\subsection python_tutorial2_tips Debugging and Development Tips + +You can find the full file along with a small sample database on github. To use the database, add it as a logical file and run your module on it. + +Whenever you have syntax errors or other errors in your script, you will get some form of dialog from Autopsy when you try to run ingest modules. If that happens, fix the problem and run ingest modules again. You don't need to restart Autopsy each time! + +The sample module has some log statements in there to help debug what is going on since we don't know of better ways to debug the scripts while running in Autopsy. + +\section python_tutorial2_running_exes Running Executables +While the above example outlined using the FileManager to find files to analyze, the other common use of data source-level ingest modules is to wrap a command line tool that takes a disk image as input. A sample program (RunExe.py) that does that can be found on github. I'll cover the big topics of that program in this section. There are more details in the script about error checking and such. + +\subsection python_tutorial2_finding_exe Finding The Executable + +To write this kind of data source-level ingest module, put the executable in your module's folder (the DemoScript2 folder we previously made). Use "__file__" to get the path to where your script is and then use some os.path methods to get to the executable in the same folder. +\verbatim +path_to_exe = os.path.join(os.path.dirname(os.path.abspath(__file__)), "img_stat.exe")\endverbatim + +In our sample program, we do this and verify we can find it in the startup() method so that if we don't, then ingest never starts. + +\subsection python_tutorial2_running_the_exe Running The Executable + +Data sources can be disk images, but they can also be a folder of files. We only want to run our executable on a disk image. So, verify that: +\verbatim +if not isinstance(dataSource, Image): + self.log(Level.INFO, "Ignoring data source. Not an image") + return IngestModule.ProcessResult.OK \endverbatim + +You can get the path to the disk image using dataSource.getPaths(). + +Once you have the EXE and the disk image, you can use the various subprocess methods to run them. + +\subsection python_tutorial2_showing_results Showing the User Results + +After the command line tool runs, you have the option of either showing the user the raw output of the tool or parsing it into individual artifacts. Refer to previous sections of this tutorial and the previous tutorial for making artifacts. If you want to simply show the user the output of the tool, then save the output to the Reports folder in the Case directory: +\verbatim +reportPath = os.path.join(Case.getCurrentCase().getCaseDirectory(), + "Reports", "img_stat-" + str(dataSource.getId()) + ".txt") \endverbatim + +Then you can add the report to the case so that it shows up in the tree in the main UI panel. +\verbatim Case.getCurrentCase().addReport(reportPath, "Run EXE", "img_stat output")\endverbatim + +\section python_tutorial2_conclusion Conclusion + +Data source-level ingest modules allow you to query for a subset of files by name or to run on an entire disk image. This tutorial has shown an example of both use cases and shown how to use SQLite in Jython. + */ \ No newline at end of file diff --git a/docs/doxygen/modFileIngestTutorial.dox b/docs/doxygen/modFileIngestTutorial.dox index def7e91c2a..7873513a4f 100644 --- a/docs/doxygen/modFileIngestTutorial.dox +++ b/docs/doxygen/modFileIngestTutorial.dox @@ -3,32 +3,32 @@ \section python_tutorial1_why Why Write a File Ingest Module?
    -
  • Autopsy hides the fact that a file is coming from a file system, was carved, was from inside of a ZIP file, or was part of a local file. So, you don’t need to spend time supporting all of the ways that your user may want to get data to you. You just need to worry about analyzing the content.
  • -
  • Autopsy displays files automatically and can include them in reports if you use standard blackboard artifacts (described later). That means you don’t need to worry about UIs and reports.
  • +
  • Autopsy hides the fact that a file is coming from a file system, was carved, was from inside of a ZIP file, or was part of a local file. So, you don't need to spend time supporting all of the ways that your user may want to get data to you. You just need to worry about analyzing the content.
  • +
  • Autopsy displays files automatically and can include them in reports if you use standard blackboard artifacts (described later). That means you don't need to worry about UIs and reports.
  • Autopsy gives you access to results from other modules. So, you can build on top of their results instead of duplicating them.
\section python_tutorial1_ingest_modules Ingest Modules -For our first example, we’re going to write an ingest module. Ingest modules in Autopsy run on the data sources that are added to a case. When you add a disk image (or local drive or logical folder) in Autopsy, you’ll be presented with a list of modules to run (such as hash lookup and keyword search). +For our first example, we're going to write an ingest module. Ingest modules in Autopsy run on the data sources that are added to a case. When you add a disk image (or local drive or logical folder) in Autopsy, you'll be presented with a list of modules to run (such as hash lookup and keyword search). \image html ingest-modules.PNG -Those are all ingest modules. We’re going to write one of those. There are two types of ingest modules that we can build: +Those are all ingest modules. We're going to write one of those. There are two types of ingest modules that we can build:
  • File Ingest Modules are the easiest to write. During their lifetime, they will get passed in each file in the data source. This includes files that are found via carving or inside of ZIP files (if those modules are also enabled).
  • -
  • Data Source Ingest Modules require slightly more work because you have to query the database for the files of interest. If you only care about a small number of files, know their name, and know they won’t be inside of ZIP files, then these are your best bet.
  • +
  • Data Source Ingest Modules require slightly more work because you have to query the database for the files of interest. If you only care about a small number of files, know their name, and know they won't be inside of ZIP files, then these are your best bet.
-For this first tutorial, we’re going to write a file ingest module. The \ref mod_python_ds_ingest_tutorial_page "second tutorial" will focus on data source ingest modules. Regardless of the type of ingest module you are writing, you will need to work with two classes: +For this first tutorial, we're going to write a file ingest module. The \ref mod_python_ds_ingest_tutorial_page "second tutorial" will focus on data source ingest modules. Regardless of the type of ingest module you are writing, you will need to work with two classes:
  • The factory class provides Autopsy with module information such as display name and version. It also creates instances of ingest modules as needed.
  • -
  • The ingest module class will do the actual analysis. One of these will be created per thread. For file ingest modules, Autopsy will typically create two or more of these at a time so that it can analyze files in parallel. If you keep things simple, and don’t use static variables, then you don’t have to think about anything multithreaded.
  • +
  • The ingest module class will do the actual analysis. One of these will be created per thread. For file ingest modules, Autopsy will typically create two or more of these at a time so that it can analyze files in parallel. If you keep things simple, and don't use static variables, then you don't have to think about anything multithreaded.
\section python_tutorial1_getting_started Getting Started -To write your first file ingest module, you’ll need: +To write your first file ingest module, you'll need:
  • An installed copy of Autopsy available from SleuthKit
  • A text editor.
  • @@ -37,8 +37,8 @@ To write your first file ingest module, you’ll need: Some other general notes are that you will be writing in Jython, which converts Python-looking code into Java. It has some limitations, including:
      -
    • You can’t use Python 3 (you are limited to Python 2.7)
    • -
    • You can’t use libraries that use native code
    • +
    • You can't use Python 3 (you are limited to Python 2.7)
    • +
    • You can't use libraries that use native code
    But, Jython will give you access to all of the Java classes and services that Autopsy provides. So, if you want to stray from this example, then refer to the Developer docs on what classes and methods you have access to. The comments in the sample file will identify what type of object is being passed in along with a URL to its documentation. @@ -53,21 +53,21 @@ Every Python module in Autopsy gets its own folder. This reduces naming collisio \subsection python_tutorial1_writing Writing the Script -We are going to write a script that flags any file that is larger than 10MB and whose size is a multiple of 4096. We’ll call these big and round files. This kind of technique could be useful for finding encrypted files. An additional check would be for entropy of the file, but we’ll keep the example simple. +We are going to write a script that flags any file that is larger than 10MB and whose size is a multiple of 4096. We'll call these big and round files. This kind of technique could be useful for finding encrypted files. An additional check would be for entropy of the file, but we'll keep the example simple. Open the FindBigRoundFiles.py file in your favorite python text editor. The sample Autopsy Python modules all have TODO entries in them to let you know what you should change. The below steps jump from one TODO to the next.
      -
    1. Factory Class Name: The first thing to do is rename the sample class name from “SampleJythonFileIngestModuleFactory” to “FindBigRoundFilesIngestModuleFactory”. In the sample module, there are several uses of this class name, so you should search and replace for these strings.
    2. -
    3. Name and Description: The next TODO entries are for names and descriptions. These are shown to users. For this example, we’ll name it “Big and Round File Finder”. The description can be anything you want. Note that Autopsy requires that modules have unique names, so don’t make it too generic.
    4. -
    5. Ingest Module Class Name: The next thing to do is rename the ingest module class from “SampleJythonFileIngestModule” to “FindBigRoundFilesIngestModule”. Our usual naming convention is that this class is the same as the factory class with “Factory” removed from the end.
    6. -
    7. startUp() method: The startUp() method is where each module initializes. For our example, we don’t need to do anything special in here. Typically though, this is where you want to do stuff that could fail because throwing an exception here causes the entire ingest to stop.
    8. -
    9. process() method: This is where we do our analysis. The sample module is well documented with what it does. It ignores non-files, looks at the file name, and makes a blackboard artifact for “.txt” files. There are also a bunch of other things that it does to show examples for easy copy and pasting, but we don’t need them in our module. We’ll cover what goes into this method in the next section.
    10. +
    11. Factory Class Name: The first thing to do is rename the sample class name from "SampleJythonFileIngestModuleFactory" to "FindBigRoundFilesIngestModuleFactory". In the sample module, there are several uses of this class name, so you should search and replace for these strings.
    12. +
    13. Name and Description: The next TODO entries are for names and descriptions. These are shown to users. For this example, we'll name it "Big and Round File Finder". The description can be anything you want. Note that Autopsy requires that modules have unique names, so don't make it too generic.
    14. +
    15. Ingest Module Class Name: The next thing to do is rename the ingest module class from "SampleJythonFileIngestModule" to "FindBigRoundFilesIngestModule". Our usual naming convention is that this class is the same as the factory class with "Factory" removed from the end.
    16. +
    17. startUp() method: The startUp() method is where each module initializes. For our example, we don't need to do anything special in here. Typically though, this is where you want to do stuff that could fail because throwing an exception here causes the entire ingest to stop.
    18. +
    19. process() method: This is where we do our analysis. The sample module is well documented with what it does. It ignores non-files, looks at the file name, and makes a blackboard artifact for ".txt" files. There are also a bunch of other things that it does to show examples for easy copy and pasting, but we don't need them in our module. We'll cover what goes into this method in the next section.
    20. shutdown() method: The shutDown() method either frees resources that were allocated or sends summary messages. For our module, it will do nothing.
    \subsection python_tutorial1_process The process() Method -The process() method is passed in a reference to an AbstractFile Object. With this, you have access to all of a file’s contents and metadata. We want to flag files that are larger than 10MB and that are a multiple of 4096 bytes. The following code does that: +The process() method is passed in a reference to an AbstractFile Object. With this, you have access to all of a file's contents and metadata. We want to flag files that are larger than 10MB and that are a multiple of 4096 bytes. The following code does that: \verbatim if ((file.getSize() > 10485760) and ((file.getSize() % 4096) == 0)): \endverbatim @@ -92,7 +92,7 @@ The above code adds the artifact and a single attribute to the blackboard in the ModuleDataEvent(FindBigRoundFilesIngestModuleFactory.moduleName, BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT, None))\endverbatim -That’s it. Your process() method should look something like this: +That's it. Your process() method should look something like this: \verbatim def process(self, file): @@ -140,15 +140,15 @@ That’s it. Your process() method should look something like this: return IngestModule.ProcessResult.OK\endverbatim -Save this file and run the module on some of your data. If you have any big and round files, you should see an entry under the “Interesting Items” node in the tree. +Save this file and run the module on some of your data. If you have any big and round files, you should see an entry under the "Interesting Items" node in the tree. \image html bigAndRoundFiles.png \subsection python_tutorial1_debug Debugging and Development Tips -Whenever you have syntax errors or other errors in your script, you will get some form of dialog from Autopsy when you try to run ingest modules. If that happens, fix the problem and run ingest modules again. You don’t need to restart Autopsy each time! +Whenever you have syntax errors or other errors in your script, you will get some form of dialog from Autopsy when you try to run ingest modules. If that happens, fix the problem and run ingest modules again. You don't need to restart Autopsy each time! -The sample module has some log statements in there to help debug what is going on since we don’t know of better ways to debug the scripts while running in Autopsy. +The sample module has some log statements in there to help debug what is going on since we don't know of better ways to debug the scripts while running in Autopsy. */ diff --git a/docs/doxygen/modReportModuleTutorial.dox b/docs/doxygen/modReportModuleTutorial.dox new file mode 100644 index 0000000000..26cb14564e --- /dev/null +++ b/docs/doxygen/modReportModuleTutorial.dox @@ -0,0 +1,123 @@ +/*! \page mod_python_report_tutorial_page Python Tutorial #3: Writing a Report Module + +In our last two tutorials, we built a Python Autopsy \ref mod_python_file_ingest_tutorial_page "file ingest modules" and \ref mod_python_ds_ingest_tutorial_page "data source ingest modules" that analyzed the data sources as they were added to cases. In our third post, we're going to make an entirely different kind of module, a report module. + +Report modules are typically run after the user has completed their analysis. Autopsy comes with report modules to generate HTML, Excel, KML, and other types of reports. We're going to make a report module that outputs data in CSV. + +Like in the second tutorial, we are going to assume that you've read at least the \ref mod_python_file_ingest_tutorial_page "first tutorial" to know how to get your environment set up. As a reminder, Python modules in Autopsy are written in Jython and have access to all of the Java classes (which is why we have links to Java documentation below). + +\section python_tutorial3_report_modules Report Modules + +Autopsy report modules are often run after the user has run some ingest modules, reviewed the results, and tagged some files of interest. The user will be given a list of report modules to choose from. + +\image html reports_select.png + +The main reasons for writing an Autopsy report module are: +
      +
    • You need the results in a custom output format, such as XML or JSON.
    • +
    • You want to upload results to a central location.
    • +
    • You want to perform additional analysis after all ingest modules have run. While the modules have the word "report" in them, there is no actual requirement that they produce a report or export data. The module can simply perform data analysis and post artifacts to the blackboard like ingest modules do.
    • +
    + +As we dive into the details, you will notice that the report module API is fairly generic. This is because reports are created at a case level, not a data source level. So, when a user chooses to run a report module, all Autopsy does is tell it to run and gives it a path to a directory to store its results in. The report module can store whatever it wants in the directory. + +Note that if you look at the \ref mod_report_page "full developer docs", there are other report module types that are supported in Java. These are not supported though in Python. + +\subsection python_tutorial3_getting_content Getting Content + +With report modules, it is up to you to find the content that you want to include in your report or analysis. Generally, you will want to access some or all of the files, tagged files, or blackboard artifacts. As you may recall from the previous tutorials, blackboard artifacts are how ingest modules in Autopsy store their results so that they can be shown in the UI, used by other modules, and included in the final report. In this tutorial, we will introduce the SleuthkitCase class, which we generally don't introduce to module writers because it has lots of methods, many of which are low-level, and there are other classes, such as FileManager, that are more focused and easier to use. + +\subsubsection python_tutorial3_getting_files Getting Files + +You have three choices for getting files to report on. You can use the FileManager, which we used in \ref mod_python_ds_ingest_tutorial_page "the last Data Source-level Ingest Module tutorial". The only change is that you will need to call it multiple times, one for each data source in the case. You will have code that looks something like this: +\verbatim +dataSources = Case.getCurrentCase().getDataSources() +fileManager = Case.getCurrentCase().getServices().getFileManager() + +for dataSource in dataSources: + files = fileManager.findFiles(dataSource, "%.txt")\endverbatim + +Another approach is to use the SleuthkitCase.findAllFilesWhere() method that allows you to specify a SQL query. To use this method, you must know the schema of the database (which makes this a bit more challenging, but more powerful). The schema is defined on the wiki. + +Usually, you just need to focus on the tsk_files table. You may run into memory problems and you can also use SleuthkitCase.findAllFileIdsWhere() to get just the IDs and then call SleuthkitCase.getAbstractFileById() to get files as needed. + +A third approach is to call org.sleuthkit.autopsy.casemodule.Case.getDataSources(), and then recursively call getChildren() on each Content object. This will traverse all of the folders and files in the case. This is the most memory efficient, but also more complex to code. + +\subsubsection python_tutorial3_getting_artifacts Getting Blackboard Artifacts + +The blackboard is where modules store their analysis results. If you want to include them in your report, then there are several methods that you could use. If you want all artifacts of a given type, then you can use SleuthkitCase.getBlackboardArtifacts(). There are many variations of this method that take different arguments. Look at them to find the one that is most convenient for you. + +\subsubsection python_tutorial3_getting_tags Getting Tagged Files or Artifacts + +If you want to find files or artifacts that are tagged, then you can use the org.sleuthkit.autopsy.casemodule.services.TagsManager. It has methods to get all tags of a given name, such as org.sleuthkit.autopsy.casemodule.services.TagsManager.getContentTagsByTagName(). + +\section python_tutorial3_getting_started Getting Started + +\subsection python_tutorial3_making_the_folder Making the Folder + +We'll start by making our module folder. As we learned in \ref mod_python_file_ingest_tutorial_page "the first tutorial", every Python module in Autopsy gets its own folder. To find out where you should put your Python module, launch Autopsy and choose the Tools->Python Plugins menu item. That will open a subfolder in your AppData folder, such as "C:\Users\JDoe\AppData\Roaming\Autopsy\python_modules". + +Make a folder inside of there to store your module. Call it "DemoScript3". Copy the reportmodule.py sample file into the this new folder and rename it to CSVReport.py. + +\subsection python_tutorial3_writing_script Writing the Script + +We are going to write a script that makes some basic CSV output: file name and MD5 hash. Open the CSVReport.py file in your favorite Python text editor. The sample Autopsy Python modules all have TODO entries in them to let you know what you should change. The below steps jump from one TODO to the next. + +
      +
    1. Factory Class Name: The first thing to do is rename the sample class name from "SampleGeneralReportModule" to "CSVReportModule". In the sample module, there are several uses of this class name, so you should search and replace for these strings.
    2. +
    3. Name and Description: The next TODO entries are for names and descriptions. These are shown to users. For this example, we'll name it "CSV Hash Report Module". The description can be anything you want. Note that Autopsy requires that modules have unique names, so don't make it too generic.
    4. +
    5. Relative File Path: The next step is to specify the filename that your module is going to use for the report. Autopsy will later provide you with a folder name to save your report in. If you have multiple file names, then pick the main one. This path will be shown to the user after the report has been generated so that they can open it. For this example, we'll call it "hashes.csv" in the getRelativeFilePath() method.
    6. +
    7. generateReport() Method: This method is what is called when the user wants to run the module. It gets passed in the base directory to store the results in and a progress bar. It is responsible for making the report and calling Case.addReport() so that it will be shown in the tree. We'll cover the details of this method in a later section.
    8. +
    + +\subsection python_tutorial3_generate_report The generateReport() method + +The generateReport() method is where the work is done. The baseReportDir argument is a string for the base directory to store results in. The progressBar argument is a org.sleuthkit.autopsy.report.ReportProgressPanel +that shows the user progress while making long reports and to make the progress bar red if an error occurs. + +We'll use one of the basic ideas from the sample, so you can copy and paste from that as you see fit to make this method. Our general approach is going to be this: +
      +
    1. Open the CSV file.
    2. +
    3. Query for all files.
    4. +
    5. Cycle through each of the files and print a line of text.
    6. +
    7. Add the report to the Case database.
    8. +
    + +To focus on the essential code, we'll skip the progress bar details. However, the final solution that we'll link to at the end contains the progress bar code. + +To open the report file in the right folder, we'll need a line such as this: +\verbatim +fileName = os.path.join(baseReportDir, self.getRelativeFilePath()) +report = open(fileName, 'w')\endverbatim + +Next we need to query for the files. In our case, we want all of the files, but can skip the directories. We'll use lines such as this to get the current case and then call the SleuthkitCase.findAllFilesWhere() method. +\verbatim +sleuthkitCase = Case.getCurrentCase().getSleuthkitCase() +files = sleuthkitCase.findAllFilesWhere("NOT meta_type = " + + str(TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_DIR.getValue()))\endverbatim + +Now, we want to print a line for each file. To do this, you'll need something like: +\verbatim +for file in files: + md5 = file.getMd5Hash() + + if md5 is None: + md5 = "" + + report.write(file.getParentPath() + file.getName() + "," + md5 + "n")\endverbatim + +Note that the file will only have an MD5 value if the Hash Lookup ingest module was run on the data source. + +Lastly, we want to add the report to the case database so that the user can later find it from the tree and we want to report that we completed successfully. +\verbatim +Case.getCurrentCase().addReport(fileName, self.moduleName, "Hashes CSV") +progressBar.complete(ReportStatus.COMPLETE)\endverbatim + +That's it. The final code can be found on github. + +\subsection python_tutorial3_conclusions Conclusions + +In this tutorial, we made a basic report module that creates a custom CSV file. The most challenging part of writing a report module is knowing how to get all of the data that you need. Hopefully, the \ref python_tutorial3_getting_content section above covered what you need, but if not, then go on the Sleuthkit forum and we'll try to point you in the right direction.

    + + +*/ \ No newline at end of file From e21a620a0dc66a6ba27e89db952c0b61f717a024 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Fri, 18 Oct 2019 11:29:04 -0400 Subject: [PATCH 039/134] Update links on Python page --- docs/doxygen/modDevPython.dox | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/doxygen/modDevPython.dox b/docs/doxygen/modDevPython.dox index 8878d97cdb..85b420a4be 100644 --- a/docs/doxygen/modDevPython.dox +++ b/docs/doxygen/modDevPython.dox @@ -15,11 +15,10 @@ Using it is very easy though in Autopsy and it allows you to access all of the J To develop a module, you should follow this section to get your environment setup and then read the later sections on the different types of modules. -There are also a set of tutorials that Basis Technology published on their blog. While not as thorough as this documentation, they are an easy introduction to the general ideas. - -- File Ingest Modules: http://www.basistech.com/python-autopsy-module-tutorial-1-the-file-ingest-module/ -- Data Source Ingest Modules: http://www.basistech.com/python-autopsy-module-tutorial-2-the-data-source-ingest-module/ -- Report Modules: http://www.basistech.com/python-autopsy-module-tutorial-3-the-report-module/ +There are also a set of tutorials that provide an easy introduction to the general ideas. +- File Ingest Modules: \subpage mod_python_file_ingest_tutorial_page +- Data Source Ingest Modules: \subpage mod_python_ds_ingest_tutorial_page +- Report Modules: \subpage mod_python_report_tutorial_page \section mod_dev_py_setup Basic Setup From bb2335ec0ee72ca7766d62e1b3b53b7d19a9d7c4 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Fri, 18 Oct 2019 14:17:33 -0400 Subject: [PATCH 040/134] Made progress bar refresh at 60FPS, fixed button text sizes --- .../autopsy/contentviewers/Bundle.properties | 4 +- .../contentviewers/Bundle.properties-MERGED | 4 +- .../contentviewers/MediaPlayerPanel.form | 6 +- .../contentviewers/MediaPlayerPanel.java | 66 ++++++++----------- 4 files changed, 34 insertions(+), 46 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties index facc16426f..b3a384ce48 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties @@ -87,8 +87,8 @@ HtmlPanel.showImagesToggleButton.text=Download Images MediaViewImagePanel.tagsMenu.text_1=Tags Menu MediaPlayerPanel.progressLabel.text=00:00:00/00:00:00 MediaPlayerPanel.audioSlider.toolTipText= -MediaPlayerPanel.rewindButton.text=\u2bc7\u2bc7 -MediaPlayerPanel.fastForwardButton.text=\u25ba\u25ba +MediaPlayerPanel.rewindButton.text=\u2bc7\u2bc7 +MediaPlayerPanel.fastForwardButton.text=\u2bc8\u2bc8 MediaPlayerPanel.playButton.text=\u25ba MediaPlayerPanel.infoLabel.text=No Errors MediaPlayerPanel.VolumeIcon.text=Volume diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED index b90b0d5d61..ba39d1d419 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED @@ -157,8 +157,8 @@ HtmlPanel.showImagesToggleButton.text=Download Images MediaViewImagePanel.tagsMenu.text_1=Tags Menu MediaPlayerPanel.progressLabel.text=00:00:00/00:00:00 MediaPlayerPanel.audioSlider.toolTipText= -MediaPlayerPanel.rewindButton.text=\u2bc7\u2bc7 -MediaPlayerPanel.fastForwardButton.text=\u25ba\u25ba +MediaPlayerPanel.rewindButton.text=\u2bc7\u2bc7 +MediaPlayerPanel.fastForwardButton.text=\u2bc8\u2bc8 MediaPlayerPanel.playButton.text=\u25ba MediaPlayerPanel.infoLabel.text=No Errors MediaPlayerPanel.VolumeIcon.text=Volume diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form index 8fc346b491..605ed93697 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form @@ -41,7 +41,7 @@ - + @@ -85,8 +85,8 @@ - - + + diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java index ec5b90b79e..dec8bf55b4 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java @@ -54,6 +54,7 @@ import org.sleuthkit.datamodel.TskData; import javafx.embed.swing.JFXPanel; import javax.swing.SwingUtilities; import javax.swing.event.ChangeListener; +import org.freedesktop.gstreamer.ClockTime; import org.freedesktop.gstreamer.Format; import org.freedesktop.gstreamer.GstException; import org.freedesktop.gstreamer.event.SeekFlags; @@ -181,7 +182,8 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie private Bus.EOS endOfStreamListener; //Update progress bar and time label during video playback - private final Timer timer = new Timer(75, new VideoPanelUpdater()); + //Updating every 16 MS = 62.5 FPS. + private final Timer timer = new Timer(16, new VideoPanelUpdater()); private static final int PROGRESS_SLIDER_SIZE = 2000; private static final int SKIP_IN_SECONDS = 30; @@ -260,21 +262,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie endOfStreamListener = new Bus.EOS() { @Override public void endOfStream(GstObject go) { - double playBackRate = getPlayBackRate(); - gstPlayBin.seek(playBackRate, - Format.TIME, - //FLUSH - flushes the pipeline - //ACCURATE - video will seek exactly to the position requested - EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE), - //Set the start position to newTime - SeekType.SET, 0, - //Do nothing for the end position - SeekType.NONE, -1); - - SwingUtilities.invokeLater(() -> { - progressSlider.setValue(0); - }); - + gstPlayBin.seek(ClockTime.ZERO); /** * Keep the video from automatically playing */ @@ -411,16 +399,16 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie private void updateTimeLabel(long start, long total) { progressLabel.setText(formatTime(start) + "/" + formatTime(total)); } - + /** * Reads the current selected playback rate from the speed combo box. - * + * * @return The selected rate. */ private double getPlayBackRate() { int selectIndex = playBackSpeedComboBox.getSelectedIndex(); String selectText = playBackSpeedComboBox.getItemAt(selectIndex); - return Double.valueOf(selectText.substring(0, selectText.length()-1)); + return Double.valueOf(selectText.substring(0, selectText.length() - 1)); } /** @@ -463,11 +451,11 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie protected Void doInBackground() throws Exception { if (!tempFile.exists() || tempFile.length() < sourceFile.getSize()) { progress = ProgressHandle.createHandle(NbBundle.getMessage(MediaPlayerPanel.class, "GstVideoPanel.ExtractMedia.progress.buffering", sourceFile.getName()), () -> this.cancel(true)); - + SwingUtilities.invokeLater(() -> { progressLabel.setText(NbBundle.getMessage(this.getClass(), "GstVideoPanel.progress.buffering")); }); - + progress.start(100); try { Files.createParentDirs(tempFile); @@ -556,7 +544,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie progressSlider.setValue((int) (relativePosition * PROGRESS_SLIDER_SIZE)); } - SwingUtilities.invokeLater(()-> { + SwingUtilities.invokeLater(() -> { updateTimeLabel(position, duration); }); } @@ -596,7 +584,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie ); videoPanelLayout.setVerticalGroup( videoPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 125, Short.MAX_VALUE) + .addGap(0, 131, Short.MAX_VALUE) ); progressSlider.setValue(0); @@ -743,8 +731,8 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie .addGroup(controlPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) .addComponent(buttonPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(playBackPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(infoLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 28, Short.MAX_VALUE)) + .addGap(14, 14, 14) + .addComponent(infoLabel)) ); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); @@ -786,12 +774,12 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); //Skip 30 seconds. long fastForwardDelta = TimeUnit.NANOSECONDS.convert(SKIP_IN_SECONDS, TimeUnit.SECONDS); - + //Ignore fast forward requests if there are less than 30 seconds left. - if(currentTime + fastForwardDelta >= duration) { + if (currentTime + fastForwardDelta >= duration) { return; } - + long newTime = currentTime + fastForwardDelta; double playBackRate = getPlayBackRate(); gstPlayBin.seek(playBackRate, @@ -813,29 +801,29 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); //Set playback rate before play. gstPlayBin.seek(playBackRate, - Format.TIME, - //FLUSH - flushes the pipeline - //ACCURATE - video will seek exactly to the position requested - EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE), - //Set the start position to newTime - SeekType.SET, currentTime, - //Do nothing for the end position - SeekType.NONE, -1); + Format.TIME, + //FLUSH - flushes the pipeline + //ACCURATE - video will seek exactly to the position requested + EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE), + //Set the start position to newTime + SeekType.SET, currentTime, + //Do nothing for the end position + SeekType.NONE, -1); gstPlayBin.play(); - } + } }//GEN-LAST:event_playButtonActionPerformed private void playBackSpeedComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_playBackSpeedComboBoxActionPerformed double playBackRate = getPlayBackRate(); long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); - gstPlayBin.seek(playBackRate, + gstPlayBin.seek(playBackRate, Format.TIME, //FLUSH - flushes the pipeline //ACCURATE - video will seek exactly to the position requested EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE), //Set the position to the currentTime, we are only adjusting the //playback rate. - SeekType.SET, currentTime, + SeekType.SET, currentTime, SeekType.NONE, 0); }//GEN-LAST:event_playBackSpeedComboBoxActionPerformed From c41b8a83f6ebdeb0998f6e0f8c9d02c250aaf396 Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Mon, 21 Oct 2019 14:11:15 -0400 Subject: [PATCH 041/134] Simplified the model --- .../datamodel/BlackboardArtifactPoint.java | 103 ----- .../datamodel/BlackboardArtifactWaypoint.java | 238 ++++++++++++ .../datamodel/Bundle.properties-MERGED | 7 + .../geolocation/datamodel/DefaultPoint.java | 364 ------------------ .../datamodel/EXIFMetadataPoint.java | 65 ---- .../datamodel/GeolocationManager.java | 92 ++--- .../datamodel/GeolocationUtils.java | 192 +++++++++ .../autopsy/geolocation/datamodel/Route.java | 201 +++++++--- .../geolocation/datamodel/RoutePoint.java | 56 ++- .../geolocation/datamodel/SimplePoint.java | 92 ----- .../geolocation/datamodel/Waypoint.java | 134 +++++++ .../autopsy/report/modules/kml/KMLReport.java | 259 +++++++++---- 12 files changed, 972 insertions(+), 831 deletions(-) delete mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/datamodel/BlackboardArtifactPoint.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/datamodel/BlackboardArtifactWaypoint.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Bundle.properties-MERGED delete mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/datamodel/DefaultPoint.java delete mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/datamodel/EXIFMetadataPoint.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtils.java delete mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/datamodel/SimplePoint.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/BlackboardArtifactPoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/BlackboardArtifactPoint.java deleted file mode 100755 index 10549cef92..0000000000 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/BlackboardArtifactPoint.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * 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 org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * Interface to be implemented by all ArtifactPoint objects. - * - */ -public interface BlackboardArtifactPoint { - - /** - * Get the BlackboardArtifact for which this point represents. - * - * @return BlackboardArtifact for this point. - */ - BlackboardArtifact getArtifact(); - - /** - * Get the timestamp for this BlackboardArtifact. - * - * @return Timestamp in epoch seconds or null if none was set. - */ - Long getTimestamp(); - - /** - * Get the label for this point object. - * - * @return String label for the point or null if none was set - */ - String getLabel(); - - /** - * Get the latitude for this point. - * - * @return Returns the latitude for the point or null if none was set - */ - Double getLatitude(); - - /** - * Get the longitude for this point. - * - * @return Returns the longitude for the point or null if none was set - */ - Double getLongitude(); - - /** - * Get the Altitude for this point. - * - * @return Returns the Altitude for the point or null if none was set - */ - Double getAltitude(); - - /** - * Get the details of this point as an HTML formated string. - * - * @return The details string or empty string if none was set. - */ - String getDetails(); - - /** - * initPoint is the function where the work of setting the attributes the - * point. - * - * @throws TskCoreException - */ - void initPoint() throws TskCoreException; - - /** - * Get the latitude and longitude as a formatted string. - * - * @return Formatted String. - */ - String getFormattedCoordinates(); - - /** - * Get the latitude and longitude formatted as given. - * - * @param format The format String should assume that latitude will be - * passed first and that both are number values. - * - * @return Formatted string. - */ - String getFormattedCoordinates(String format); -} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/BlackboardArtifactWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/BlackboardArtifactWaypoint.java new file mode 100755 index 0000000000..e46509cf80 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/BlackboardArtifactWaypoint.java @@ -0,0 +1,238 @@ +/* + * 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.util.Map; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Representation of a Waypoint created from a BlackboardArtifact. + * + */ +public class BlackboardArtifactWaypoint implements Waypoint { + + final private Long timestamp; + final private Double longitude; + final private Double latitude; + final private Double altitude; + final private String label; + final private AbstractFile image; + final private BlackboardArtifact artifact; + final private Waypoint.Type type; + final private Map otherAttributesMap; + + /** + * Constructs a Waypoint from a BlackboardArtifact + * + * @param artifact BlackboardArtifact with which to create the waypoint + * + * @throws TskCoreException + */ + protected BlackboardArtifactWaypoint(BlackboardArtifact artifact) throws TskCoreException { + this.artifact = artifact; + timestamp = getTimestampFromArtifact(artifact); + longitude = GeolocationUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE); + latitude = GeolocationUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE); + altitude = GeolocationUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE); + image = getImageFromArtifact(artifact); + label = getLabelFromArtifact(artifact); + type = getTypeFromArtifact(artifact); + otherAttributesMap = GeolocationUtils.getOtherGeolocationAttributes(artifact); + } + + /** + * Get the BlackboardArtifact that this waypoint represents. + * + * @return BlackboardArtifact for this waypoint. + */ + BlackboardArtifact getArtifact() { + return artifact; + } + + @Override + public Long getTimestamp() { + return timestamp; + } + + @Override + public String getLabel() { + return label; + } + + @Override + public Double getLatitude() { + return latitude; + } + + @Override + public Double getLongitude() { + return longitude; + } + + @Override + public Double getAltitude() { + return altitude; + } + + @Override + public AbstractFile getImage() { + return image; + } + + @Override + public Waypoint.Type getType() { + return type; + } + + @Override + public Map getOtherProperties() { + return otherAttributesMap; + } + + /** + * Get the timestamp attribute based on type for the given artifact. + * + * @param artifact BlackboardArtifact for waypoint + * + * @return Long timestamp or null if a value was not found. + * + * @throws TskCoreException + */ + private Long getTimestampFromArtifact(BlackboardArtifact artifact) throws TskCoreException { + if (artifact == null) { + return null; + } + + BlackboardArtifact.ARTIFACT_TYPE artifactType = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID()); + + switch (artifactType) { + case TSK_METADATA_EXIF: + return GeolocationUtils.getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED); + default: + return GeolocationUtils.getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME); + + } + } + + /** + * Gets the image from the given artifact, this is really only applicable to + * the artifact type TSK_METADATA_EXIF + * + * @param artifact BlackboardArtifact for waypoint + * + * @return AbstractFile image for this waypoint or null if one is not + * available + * + * @throws TskCoreException + */ + private AbstractFile getImageFromArtifact(BlackboardArtifact artifact) throws TskCoreException { + if (artifact == null) { + return null; + } + + BlackboardArtifact.ARTIFACT_TYPE artifactType = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID()); + + switch (artifactType) { + case TSK_METADATA_EXIF: + return artifact.getSleuthkitCase().getAbstractFileById(artifact.getObjectID()); + default: + return null; + } + } + + /** + * Gets the label for this waypoint based on the artifact type. + * + * This is the original waypoint naming code from the KML report, we may + * what to thinki about better ways to name some of the point. + * + * @param artifact BlackboardArtifact for waypoint + * + * @return Returns a label for the waypoint based on artifact type, or empty + * string if no label was found. + * + * @throws TskCoreException + */ + private String getLabelFromArtifact(BlackboardArtifact artifact) throws TskCoreException { + BlackboardArtifact.ARTIFACT_TYPE artifactType = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID()); + + if (artifactType == BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF) { + if (getImage() != null) { + return getImage().getName(); + } + } + + String typeLabel = GeolocationUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); + if (typeLabel == null || typeLabel.isEmpty()) { + switch (artifactType) { + case TSK_GPS_SEARCH: + typeLabel = GeolocationUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION); + if (typeLabel == null || typeLabel.isEmpty()) { + typeLabel = "GPS Search"; + } + break; + case TSK_GPS_TRACKPOINT: + typeLabel = GeolocationUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME); + if (typeLabel == null || typeLabel.isEmpty()) { + typeLabel = GeolocationUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FLAG); + } + if (typeLabel == null || typeLabel.isEmpty()) { + typeLabel = "GPS Trackpoint"; + } + break; + case TSK_GPS_LAST_KNOWN_LOCATION: + typeLabel = "Last Known Location"; + break; + default: + typeLabel = ""; + break; + } + } + return typeLabel; + } + + /** + * Gets the type of waypoint based on the artifact. + * + * @param artifact BlackboardArtifact for waypoint + * + * @return A waypoint type. + */ + private Waypoint.Type getTypeFromArtifact(BlackboardArtifact artifact) { + BlackboardArtifact.ARTIFACT_TYPE artifactType = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID()); + + switch (artifactType) { + case TSK_GPS_LAST_KNOWN_LOCATION: + return Waypoint.Type.LAST_KNOWN_LOCATION; + case TSK_GPS_TRACKPOINT: + return Waypoint.Type.TRACKPOINT; + case TSK_GPS_SEARCH: + return Waypoint.Type.SEARCH; + case TSK_GPS_BOOKMARK: + return Waypoint.Type.BOOKMARK; + case TSK_METADATA_EXIF: + return Waypoint.Type.METADATA_EXIF; + default: + return Waypoint.Type.UNKNOWN; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Bundle.properties-MERGED new file mode 100755 index 0000000000..512dd5f416 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Bundle.properties-MERGED @@ -0,0 +1,7 @@ +Route_Label=As-the-crow-flies Route +Waypoint_Bookmark_Display_String=GPS Bookmark +Waypoint_EXIF_Display_String=EXIF Metadata With Location +Waypoint_Last_Known_Display_String=GPS Last Known Location +Waypoint_Route_Point_Display_String=GPS Individual Route Point +Waypoint_Search_Display_String=GPS Search +Waypoint_Trackpoint_Display_String=GPS Trackpoint diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/DefaultPoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/DefaultPoint.java deleted file mode 100755 index 8fdf463363..0000000000 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/DefaultPoint.java +++ /dev/null @@ -1,364 +0,0 @@ -/* - * - * 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.text.SimpleDateFormat; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * Abstract class representing the basics of a BlackboardARtifacrPoint. - */ -public abstract class DefaultPoint implements BlackboardArtifactPoint{ - final private BlackboardArtifact artifact; - - private String label = ""; - private Long timestamp = null; - private String details = null; - private Double longitude = null; - private Double latitude = null; - private Double altitude = null; - - private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX"); - private static final String DEFAULT_COORD_FORMAT = "%.2f, %.2f"; - - /** - * Construct a new point with the given artifact. - * - * @param artifact - */ - public DefaultPoint(BlackboardArtifact artifact) { - this.artifact = artifact; - } - - /** - * Construct a new point with the given parameters. - * - * @param artifact The BlackboardArtifact object that this point represents - * @param latitude The latitude for the given artifact - * @param longitude The longitude for the given artifact - * @param label A label for the point - */ - public DefaultPoint(BlackboardArtifact artifact, Double latitude, Double longitude, String label) { - this.artifact = artifact; - this.latitude = latitude; - this.longitude = longitude; - this.label = label; - } - - @Override - public BlackboardArtifact getArtifact() { - return artifact; - } - - @Override - public Long getTimestamp() { - return timestamp; - } - - /** - * Set the timestamp for this point. - * - * @param timestamp Epoch seconds - */ - protected void setTimestamp(Long timestamp) { - this.timestamp = timestamp; - } - - @Override - public String getLabel() { - return label; - } - - /** - * Set the label for this point. - * - * @param label String label for the point. - */ - protected void setLabel(String label) { - this.label = label; - } - - @Override - public String getDetails() { - return details; - } - - /** - * Set the details information for the point. - * - * @param details Formatted detail information for display. - */ - protected void setDetails(String details) { - this.details = details; - } - - @Override - public Double getLatitude() { - return latitude; - } - - /** - * Set the latitude for this point. - * - * @param latitude Double latitude value - */ - protected void setLatitude(Double latitude) { - this.latitude = latitude; - } - - @Override - public Double getLongitude() { - return longitude; - } - - /** - * Set the longitude for this point. - * - * @param longitude Double longitude value - */ - protected void setLongitude(Double longitude) { - this.longitude = longitude; - } - - @Override - public Double getAltitude() { - return altitude; - } - - /** - * Set the altitude for the point. - * - * @param altitude Double altitude value - */ - protected void setAltitude(Double altitude) { - this.altitude = altitude; - } - - @Override - public String getFormattedCoordinates() { - return getFormattedCoordinates(DEFAULT_COORD_FORMAT); - } - - @Override - public String getFormattedCoordinates(String format) { - return String.format(format, getLatitude(), getLongitude()); - } - - /** - * This function with its formatting is from the original KMLReport. - * - * This method creates a text description for a map feature using all the - * geospatial and time data we can for the Artifact. It queries the - * following attributes: - * - * TSK_GEO_LATITUDE 54; TSK_GEO_LONGITUDE 55; TSK_GEO_LATITUDE_START 98; - * TSK_GEO_LATITUDE_END 99; TSK_GEO_LONGITUDE_START 100; - * TSK_GEO_LONGITUDE_END 101; TSK_GEO_VELOCITY 56; TSK_GEO_ALTITUDE 57; - * TSK_GEO_BEARING 58; TSK_GEO_HPRECISION 59; TSK_GEO_VPRECISION 60; - * TSK_GEO_MAPDATUM 61; TSK_DATETIME_START 83; TSK_DATETIME_END 84; - * TSK_LOCATION 86; TSK_PATH_SOURCE 94; - * - * @param artifact the artifact to query. - * @param featureType the type of Artifact we're working on. - * - * @return a String with the information we have available - */ - String getDetailsFromArtifact() throws TskCoreException{ - final String SEP = "
    "; - StringBuilder result = new StringBuilder(); //NON-NLS - - result.append("

    "); - result.append(BlackboardArtifact.ARTIFACT_TYPE.fromLabel(getArtifact().getArtifactTypeName()).getDisplayName()); - result.append("

    "); - - String name = getString(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); - if (name != null && !name.isEmpty()) { - result.append("Name: ").append(name).append(SEP); //NON-NLS - } - - String location = getString(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION); - if (location != null && !location.isEmpty()) { - result.append("Location: ").append(location).append(SEP); //NON-NLS - } - - if (timestamp != null) { - result.append("Timestamp: ").append(getTimeStamp(timestamp)).append(SEP); //NON-NLS - result.append("Unix timestamp: ").append(timestamp).append(SEP); //NON-NLS - } - - Long startingTimestamp = getLong( BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START); - if (startingTimestamp != null) { - result.append("Starting Timestamp: ").append(getTimeStamp(startingTimestamp)).append(SEP); //NON-NLS - result.append("Starting Unix timestamp: ").append(startingTimestamp).append(SEP); //NON-NLS - } - - Long endingTimestamp = getLong(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_END); - if (endingTimestamp != null) { - result.append("Ending Timestamp: ").append(getTimeStamp(endingTimestamp)).append(SEP); //NON-NLS - result.append("Ending Unix timestamp: ").append(endingTimestamp).append(SEP); //NON-NLS - } - - Long createdTimestamp = getLong(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED); - if (createdTimestamp != null) { - result.append("Created Timestamp: ").append(getTimeStamp(createdTimestamp)).append(SEP); //NON-NLS - result.append("Created Unix timestamp: ").append(createdTimestamp).append(SEP); //NON-NLS - } - - if (latitude != null) { - result.append("Latitude: ").append(latitude).append(SEP); //NON-NLS - } - - if (longitude != null) { - result.append("Longitude: ").append(longitude).append(SEP); //NON-NLS - } - - Double velocity = getDouble(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_VELOCITY); - if (velocity != null) { - result.append("Velocity: ").append(velocity).append(SEP); //NON-NLS - } - - if (altitude != null) { - result.append("Altitude: ").append(altitude).append(SEP); //NON-NLS - } - - Double bearing = getDouble(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_BEARING); - if (bearing != null) { - result.append("Bearing: ").append(bearing).append(SEP); //NON-NLS - } - - Integer hPrecision = getInteger(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_HPRECISION); - if (hPrecision != null) { - result.append("Horizontal Precision Figure of Merit: ").append(hPrecision).append(SEP); //NON-NLS - } - - Integer vPrecision = getInteger(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_VPRECISION); - if (vPrecision != null) { - result.append("Vertical Precision Figure of Merit: ").append(vPrecision).append(SEP); //NON-NLS - } - - String mapDatum = getString(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_MAPDATUM); - if (mapDatum != null && !mapDatum.isEmpty()) { - result.append("Map Datum: ").append(mapDatum).append(SEP); //NON-NLS - } - - String programName = getString(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME); - if (programName != null && !programName.isEmpty()) { - result.append("Reported by: ").append(programName).append(SEP); //NON-NLS - } - - String flag = getString(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FLAG); - if (flag != null && !flag.isEmpty()) { - result.append("Flag: ").append(flag).append(SEP); //NON-NLS - } - - String pathSource = getString(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_SOURCE); - if (pathSource != null && !pathSource.isEmpty()) { - result.append("Source: ").append(pathSource).append(SEP); //NON-NLS - } - - String deviceMake = getString(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_MAKE); - if (deviceMake != null && !deviceMake.isEmpty()) { - result.append("Device Make: ").append(deviceMake).append(SEP); //NON-NLS - } - - String deviceModel = getString(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_MODEL); - if (deviceModel != null && !deviceModel.isEmpty()) { - result.append("Device Model: ").append(deviceModel).append(SEP); //NON-NLS - } - - return result.toString(); - } - - /** - * Helper function for getting a String attribute from an artifact. - * - * @param type BlackboardAttribute type - * - * @return String value for the given attribute. - * - * @throws TskCoreException - */ - String getString(BlackboardAttribute.ATTRIBUTE_TYPE type) throws TskCoreException{ - BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(type)); - return (attribute != null ? attribute.getValueString() : null); - } - - /** - * Helper function for getting a Double attribute from an artifact. - * - * @param type BlackboardAttribute type - * - * @return Double value for the given attribute. - * - * @throws TskCoreException - */ - Double getDouble(BlackboardAttribute.ATTRIBUTE_TYPE type) throws TskCoreException{ - BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(type)); - return (attribute != null ? attribute.getValueDouble() : null); - } - - /** - * Helper function for getting a Long attribute from an artifact. - * - * @param type BlackboardAttribute type - * - * @return Long value for the given attribute. - * - * @throws TskCoreException - */ - Long getLong(BlackboardAttribute.ATTRIBUTE_TYPE type) throws TskCoreException{ - BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(type)); - return (attribute != null ? attribute.getValueLong() : null); - } - - /** - * Helper function for getting a Integer attribute from an artifact. - * - * @param type BlackboardAttribute type - * - * @return Integer value for the given attribute. - * - * @throws TskCoreException - */ - Integer getInteger(BlackboardAttribute.ATTRIBUTE_TYPE type) throws TskCoreException{ - BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(type)); - return (attribute != null ? attribute.getValueInt() : null); - } - - /** - * Helper function for consistently formatting the timestamp. - * - * @param type BlackboardAttribute type - * - * @return The timestamp value formatted as string, or empty string if no - * timestamp is available. - * - * @throws TskCoreException - */ - String getTimeStamp(long timeStamp) { - if(getTimestamp() != null) { - return DATE_FORMAT.format(new java.util.Date(getTimestamp() * 1000)); - } else { - return ""; - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/EXIFMetadataPoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/EXIFMetadataPoint.java deleted file mode 100755 index 67056fa2b3..0000000000 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/EXIFMetadataPoint.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * - * 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 org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * Extends SimplePoint for TSK_EXIF_METADATA artifacts. - * - */ -public class EXIFMetadataPoint extends SimplePoint{ - private AbstractFile imageFile; - - /** - * Construct a EXIF point - * - * @param artifact - */ - EXIFMetadataPoint(BlackboardArtifact artifact) { - super(artifact); - } - - @Override - public void initPoint() throws TskCoreException{ - super.initPoint(); - - setTimestamp(getLong(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED)); - - BlackboardArtifact artifact = getArtifact(); - if(artifact != null) { - imageFile = artifact.getSleuthkitCase().getAbstractFileById(artifact.getObjectID()); - } - - setLabel(imageFile.getName()); - } - - /** - * Get the image for this point. - * - * @return Return the AbstractFile image for the EXIF_METADATA artifact. - */ - public AbstractFile getImage() { - return imageFile; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationManager.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationManager.java index 05334473ea..fe5cdd1884 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationManager.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationManager.java @@ -32,13 +32,21 @@ import org.sleuthkit.datamodel.TskCoreException; public class GeolocationManager { /** - * Returns a list of points for the artifacts with geolocation information. + * Add a private constructor to silence codacy warning about making this + * class a utility class as I suspect this class may grow when filtering is + * added. + */ + private GeolocationManager() { + + } + + /** + * 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 * - * Optionally TSK_GPS_ROUTE points can also be added to the list. - * * @param skCase Currently open SleuthkitCase * @param includeRoutes True to include the points at are in TSK_GPS_ROUTE * objects @@ -47,15 +55,10 @@ public class GeolocationManager { * * @throws TskCoreException */ - static public List getPoints(SleuthkitCase skCase, boolean includeRoutes) throws TskCoreException { - List points = new ArrayList<>(); + static public List getWaypoints(SleuthkitCase skCase) throws TskCoreException { + List points = new ArrayList<>(); - points.addAll(getSimplePoints(skCase)); - points.addAll(getEXIFPoints(skCase)); - - if (includeRoutes) { - points.addAll(getGPSRoutePoints(skCase)); - } + points.addAll(getBasicPoints(skCase)); return points; } @@ -75,19 +78,18 @@ public class GeolocationManager { List artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE); for (BlackboardArtifact artifact : artifacts) { Route route = new Route(artifact); - route.initRoute(); routes.add(route); } return routes; } /** - * Get a list of BlackboardArtifactPoints for the "simple" GPS artifacts. - * Artifacts that will be included: TSK_GPS_TRACKPOINT TSK_GPS_SEARCH - * TSK_GPS_LAST_KNOWN_LOCATION TSK_GPS_BOOKMARK + * Get a list of Waypoints for the GPS artifacts. Artifacts that will be + * included: TSK_GPS_TRACKPOINT TSK_GPS_SEARCH TSK_GPS_LAST_KNOWN_LOCATION + * TSK_GPS_BOOKMARK * - * BlackboardArtifactPoint objects will be created and added to the list - * only for artifacts with TSK_GEO_LONGITUDE and TSK_LATITUDE attributes. + * Waypoint objects will be created and added to the list only for artifacts + * with TSK_GEO_LONGITUDE and TSK_LATITUDE attributes. * * @param skCase Currently open SleuthkitCase * @@ -96,19 +98,19 @@ public class GeolocationManager { * * @throws TskCoreException */ - static private List getSimplePoints(SleuthkitCase skCase) throws TskCoreException { + static private List getBasicPoints(SleuthkitCase skCase) throws TskCoreException { - List points = new ArrayList<>(); + List points = new ArrayList<>(); List artifacts = new ArrayList<>(); artifacts.addAll(skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT)); artifacts.addAll(skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH)); artifacts.addAll(skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION)); artifacts.addAll(skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK)); + artifacts.addAll(skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF)); for (BlackboardArtifact artifact : artifacts) { - BlackboardArtifactPoint point = new SimplePoint(artifact); - point.initPoint(); + BlackboardArtifactWaypoint point = new BlackboardArtifactWaypoint(artifact); // Only add to the list if the point has a valid latitude // and longitude. if (point.getLatitude() != null && point.getLongitude() != null) { @@ -118,52 +120,4 @@ public class GeolocationManager { return points; } - - /** - * Get a list of BlackboardArtifactPoints for TSK_METADATA_EXIF artifacts. - * - * BlackboardArtifactPoint objects will be created and added to the list - * only for artifacts with TSK_GEO_LONGITUDE and TSK_LATITUDE attributes. - * - * @param skCase Currently open SleuthkitCase - * - * @return List of BlackboardArtifactPoints for above artifacts or empty - * list if none where found. - * - * @throws TskCoreException - */ - static private List getEXIFPoints(SleuthkitCase skCase) throws TskCoreException { - List points = new ArrayList<>(); - List artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF); - for (BlackboardArtifact artifact : artifacts) { - BlackboardArtifactPoint point = new EXIFMetadataPoint(artifact); - - point.initPoint(); - if (point.getLatitude() != null && point.getLongitude() != null) { - points.add(point); - } - } - - return points; - } - - /** - * Get a list of BlackboardArtifactPoints from the list of routes. - * - * @param skCase Currently open SleuthkitCase - * - * @return A list of route points, or empty list if none were found. - * - * @throws TskCoreException - */ - static private List getGPSRoutePoints(SleuthkitCase skCase) throws TskCoreException { - List points = new ArrayList<>(); - - for (Route route : getGPSRoutes(skCase)) { - points.addAll(route.getRoute()); - } - - return points; - } - } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtils.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtils.java new file mode 100755 index 0000000000..20e8b6ab7b --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtils.java @@ -0,0 +1,192 @@ +/* + * 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.datamodel; + +import java.util.HashMap; +import java.util.Map; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * + * + */ +public class GeolocationUtils { + + private static final String DEFAULT_COORD_FORMAT = "%.2f, %.2f"; + + /** + * This is a list of attributes that are related to a geolocation waypoint + * but are for information\artifact properties purpose. They are not needed + * for the placement of a point on a map; + */ + private static final BlackboardAttribute.ATTRIBUTE_TYPE[] OTHER_GEO_ATTRIBUTES = { + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_END, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_VELOCITY, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_BEARING, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_HPRECISION, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_VPRECISION, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_MAPDATUM, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_SOURCE, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FLAG, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_MAKE, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_MODEL + }; + + static public String getFormattedCoordinates(Double latitude, Double longitude) { + if (latitude == null || longitude == null) { + return ""; + } + + return String.format(DEFAULT_COORD_FORMAT, latitude, longitude); + } + + /** + * Helper function for getting a String attribute from an artifact. This + * will work for all attributes + * + * @param artifact The BlackboardArtifact to get the attributeType + * @param attributeType BlackboardAttribute attributeType + * + * @return String value for the given attribute or null if attribute was not + * set for the given artifact + * + * @throws TskCoreException + */ + static protected String getString(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) throws TskCoreException { + if (artifact == null) { + return null; + } + + BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(attributeType)); + return (attribute != null ? attribute.getDisplayString() : null); + } + + /** + * Helper function for getting a Double attribute from an artifact. + * + * @param artifact The BlackboardArtifact to get the attributeType + * @param attributeType BlackboardAttribute attributeType + * + * @return Double value for the given attribute. + * + * @throws TskCoreException + */ + static protected Double getDouble(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) throws TskCoreException { + if (artifact == null) { + return null; + } + + if (attributeType.getValueType() != BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DOUBLE) { + return null; + } + + BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(attributeType)); + return (attribute != null ? attribute.getValueDouble() : null); + } + + /** + * Helper function for getting a Long attribute from an artifact. + * + * @param artifact The BlackboardArtifact to get the attributeType + * @param attributeType BlackboardAttribute attributeType + * + * @return Long value for the given attribute. + * + * @throws TskCoreException + */ + static protected Long getLong(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) throws TskCoreException { + if (artifact == null) { + return null; + } + + if (attributeType.getValueType() != BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.LONG + || attributeType.getValueType() != BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME) { + return null; + } + + BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(attributeType)); + return (attribute != null ? attribute.getValueLong() : null); + } + + /** + * Helper function for getting a Integer attribute from an artifact. + * + * @param artifact The BlackboardArtifact to get the attributeType + * @param attributeType BlackboardAttribute attributeType + * + * @return Integer value for the given attribute. + * + * @throws TskCoreException + */ + static protected Integer getInteger(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) throws TskCoreException { + if (artifact == null) { + return null; + } + + if (attributeType.getValueType() != BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.INTEGER) { + return null; + } + + BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(attributeType)); + return (attribute != null ? attribute.getValueInt() : null); + } + + /** + * Helper function for consistently formatting the timestamp. + * + * @param type BlackboardAttribute type + * + * @return The timestamp value formatted as string, or empty string if no + * timestamp is available. + * + * @throws TskCoreException + */ + static protected String getFormattedTimestamp(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) throws TskCoreException { + if (artifact == null) { + return null; + } + + if (attributeType.getValueType() != BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME) { + return null; + } + + BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(attributeType)); + return (attribute != null ? attribute.getDisplayString() : null); + } + + /** + * Returns a Map of formatted artifact attributes for geolocation artifacts. + * The map key will be the display string off the attribute, the value will + * be either a formatted value or a an empty string. + * + * @param artifact + * + * @return + */ + static protected Map getOtherGeolocationAttributes(BlackboardArtifact artifact) throws TskCoreException { + Map map = new HashMap<>(); + + for (BlackboardAttribute.ATTRIBUTE_TYPE type : OTHER_GEO_ATTRIBUTES) { + String key = type.getDisplayName(); + String value = getString(artifact, type); + + if (value == null) { + value = ""; + } + + map.put(key, value); + } + + return map; + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java index 47e5ccbea3..68d93509d5 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java @@ -21,31 +21,52 @@ package org.sleuthkit.autopsy.geolocation.datamodel; import java.util.ArrayList; import java.util.List; +import java.util.Map; +import org.openide.util.NbBundle.Messages; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.TskCoreException; /** * A Route represents a TSK_GPS_ROUTE artifact which has a start and end point - * however the class was written with the assumption that some routes may have + * however the class was written with the assumption that some routes may have * more that two points. - * + * */ +@Messages({ + "Route_Label=As-the-crow-flies Route", + "Route_Start_Label=Start", + "Route_End_Label=End" +}) public class Route { - private final BlackboardArtifact artifact; - private final List points; - private long timestamp; - private String details; - private Double altitude = null; + + private final List points; + private final Long timestamp; + private final Double altitude; + private final Map otherAttributesMap; /** * Construct a route for the given artifact. * * @param artifact TSK_GPS_ROUTE artifact object */ - protected Route(BlackboardArtifact artifact) { - this.artifact = artifact; + protected Route(BlackboardArtifact artifact) throws TskCoreException { points = new ArrayList<>(); + Waypoint point = getRouteStartPoint(artifact); + + if (point != null) { + points.add(point); + } + + point = getRouteEndPoint(artifact); + + if (point != null) { + points.add(point); + } + + altitude = getRouteAltitude(artifact); + timestamp = getRouteTimestamp(artifact); + otherAttributesMap = initalizeOtherAttributes(artifact); } /** @@ -53,68 +74,150 @@ public class Route { * * @return List of ArtifactWaypoints for this route */ - public List getRoute() { + public List getRoute() { return points; } - - public String getDetails() { - return details; - } - - public BlackboardArtifact getArtifact() { - return artifact; - } - + + /** + * Get the timestamp for this Route + * + * @return The timestamp (epoch seconds) or null if none was set. + */ public Long getTimestamp() { return timestamp; } - + + /** + * Get the altitude for this route. + * + * @return The Double altitude value or null if none was set. + */ public Double getAltitude() { return altitude; } /** - * Initialize the route. + * Get the "Other attributes" for this route. The map will contain display + * name, formatted value pairs. + * + * @return Map of key, value pairs. */ - protected void initRoute() throws TskCoreException { - if (artifact == null) { - return; - } - + public Map getOtherProperties() { + return otherAttributesMap; + } + + /** + * Get the route label. + * + * This will return the original hard coded label from the KML report: + * As-the-crow-flies Route + */ + public String getLabel() { + return Bundle.Route_Label(); + } + + /** + * Get the route start point. + * + * @param artifact The BlackboardARtifact object from which this route is + * created + * + * @return Start RoutePoint or null if valid longitude and latitude are not + * found + * + * @throws TskCoreException + */ + private Waypoint getRouteStartPoint(BlackboardArtifact artifact) throws TskCoreException { Double latitude; Double longitude; BlackboardAttribute attribute; + RoutePoint point = null; attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START)); latitude = attribute != null ? attribute.getValueDouble() : null; - + attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START)); longitude = attribute != null ? attribute.getValueDouble() : null; - + if (latitude != null && longitude != null) { - RoutePoint point = new RoutePoint(artifact, this, latitude, longitude, "Start"); - point.initPoint(); - points.add(point); - } - - attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END)); - latitude = attribute != null ? attribute.getValueDouble() : null; - - attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END)); - longitude = attribute != null ? attribute.getValueDouble() : null; - - if (latitude != null && longitude != null) { - RoutePoint point = new RoutePoint(artifact, this, latitude, longitude, "End"); - point.initPoint(); - points.add(point); + point = new RoutePoint(this, latitude, longitude, Bundle.Route_Start_Label()); } - attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE)); - altitude = attribute != null ? attribute.getValueDouble() : null; - - // Get the creation date - attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME)); - timestamp = attribute != null ? attribute.getValueLong() : null; + return point; } + /** + * Get the route End point. + * + * @param artifact The BlackboardARtifact object from which this route is + * created + * + * @return End RoutePoint or null if valid longitude and latitude are not + * found + * + * @throws TskCoreException + */ + private Waypoint getRouteEndPoint(BlackboardArtifact artifact) throws TskCoreException { + Double latitude; + Double longitude; + BlackboardAttribute attribute; + RoutePoint point = null; + + attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END)); + latitude = attribute != null ? attribute.getValueDouble() : null; + + attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END)); + longitude = attribute != null ? attribute.getValueDouble() : null; + + if (latitude != null && longitude != null) { + point = new RoutePoint(this, latitude, longitude, Bundle.Route_End_Label()); + } + + return point; + } + + /** + * Get the Altitude for this route. + * + * @param artifact The BlackboardARtifact object from which this route is + * created + * + * @return The Altitude, or null if none was found + * + * @throws TskCoreException + */ + private Double getRouteAltitude(BlackboardArtifact artifact) throws TskCoreException { + BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE)); + return attribute != null ? attribute.getValueDouble() : null; + } + + /** + * Get the timestamp for this route. + * + * @param artifact The BlackboardARtifact object from which this route is + * created + * + * @return The timestamp attribute, or null if none was found + * + * @throws TskCoreException + */ + private Long getRouteTimestamp(BlackboardArtifact artifact) throws TskCoreException { + BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME)); + return attribute != null ? attribute.getValueLong() : null; + } + + /** + * Retrieve the "Other attributes" for this route. The map will contain + * display name, formatted value pairs. + * + * @param artifact The BlackboardARtifact object from which this route is + * created + * + * @return A Map of other attributes for this route. + * + * @throws TskCoreException + */ + private Map initalizeOtherAttributes(BlackboardArtifact artifact) throws TskCoreException { + return GeolocationUtils.getOtherGeolocationAttributes(artifact); + } } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/RoutePoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/RoutePoint.java index aa830a2581..23cc4b8198 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/RoutePoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/RoutePoint.java @@ -19,12 +19,19 @@ */ package org.sleuthkit.autopsy.geolocation.datamodel; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.TskCoreException; +import java.util.Map; +import org.sleuthkit.datamodel.AbstractFile; -public class RoutePoint extends DefaultPoint { +/** + * A point in a Route. For future use this point will have a pointer to its + * parent route. + */ +public class RoutePoint implements Waypoint { private final Route parent; + private final Double longitude; + private final Double latitude; + private final String label; /** * Construct a route for a route. @@ -34,23 +41,50 @@ public class RoutePoint extends DefaultPoint { * @param longitude Longitude for point * @param label Way point label. */ - protected RoutePoint(BlackboardArtifact artifact, Route parent, double latitude, double longitude, String label) { - super(artifact, latitude, longitude, label); + protected RoutePoint(Route parent, double latitude, double longitude, String label) { + this.longitude = longitude; + this.latitude = latitude; + this.label = label; this.parent = parent; } - @Override - public void initPoint() throws TskCoreException{ - setDetails(getDetailsFromArtifact()); - } - @Override public Long getTimestamp() { return parent.getTimestamp(); } - + + @Override + public String getLabel() { + return label; + } + + @Override + public Double getLatitude() { + return latitude; + } + + @Override + public Double getLongitude() { + return longitude; + } + @Override public Double getAltitude() { return parent.getAltitude(); } + + @Override + public Map getOtherProperties() { + return parent.getOtherProperties(); + } + + @Override + public AbstractFile getImage() { + return null; + } + + @Override + public Waypoint.Type getType() { + return Waypoint.Type.ROUTE_POINT; + } } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/SimplePoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/SimplePoint.java deleted file mode 100755 index db091b37f0..0000000000 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/SimplePoint.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * - * 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 org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * - * A basic Artifact point for artifacts that use the following attributes: - * TSK_GEO_LONGITUDE - * TSK_GEO_LATITUDE - * TSK_GEO_ALTITUDE - * TSK_DATETIME - * - */ -public class SimplePoint extends DefaultPoint{ - - /** - * Construct a simple point object. - * - * @param artifact - */ - SimplePoint(BlackboardArtifact artifact) { - super(artifact); - } - - @Override - public void initPoint() throws TskCoreException{ - setLongitude(getDouble(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE)); - setLatitude(getDouble(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE)); - setAltitude(getDouble(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE)); - setTimestamp(getLong(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME)); - - setDetails(getDetailsFromArtifact()); - setLabel(getLabelBasedOnType()); - } - - /** - * Create the point label based on the artifact type. - * - * This code needs to be revisited to make sure these back up labels make - * sense. - * - * @return A label for the point. - * - * @throws TskCoreException - */ - String getLabelBasedOnType() throws TskCoreException{ - String label = getString(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); - - if(label == null || label.isEmpty()) { - if (getArtifact().getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH.getTypeID()) { - label = getString(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION); - if(label == null || label.isEmpty()) { - label = "GPS Search"; - } - } else if (getArtifact().getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT.getTypeID()) { - label = getString(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME); - if(label == null || label.isEmpty()) { - label = getString(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FLAG); - } - - if(label == null || label.isEmpty()) { - label = "GPS Trackpoint"; - } - } else if (getArtifact().getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION.getTypeID()) { - label = "Last Known Location"; - } - } - - return label; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java new file mode 100755 index 0000000000..736d0726a1 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java @@ -0,0 +1,134 @@ +/* + * 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.util.Map; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.datamodel.AbstractFile; + +/** + * The basic details of a waypoint. + * + */ +public interface Waypoint { + + /** + * Get the timestamp for this BlackboardArtifact. + * + * @return Timestamp in epoch seconds or null if none was set. + */ + Long getTimestamp(); + + /** + * Get the label for this point object. + * + * @return String label for the point or null if none was set + */ + String getLabel(); + + /** + * Get the latitude for this point. + * + * @return Returns the latitude for the point or null if none was set + */ + Double getLatitude(); + + /** + * Get the longitude for this point. + * + * @return Returns the longitude for the point or null if none was set + */ + Double getLongitude(); + + /** + * Get the Altitude for this point. + * + * @return Returns the Altitude for the point or null if none was set + */ + Double getAltitude(); + + /** + * Gets a Map of other properties that may be interesting to this way point. + * This map will not include properties for which there are getter functions + * for. + * + * The key is a "Display String", the value will be either an empty string + * or the formatted value. + * + * @return A Map of waypoint properties + */ + Map getOtherProperties(); + + /** + * Get the image for this waypoint. + * + * @return AbstractFile image + */ + AbstractFile getImage(); + + /** + * Get the type of waypoint + * + * @return WaypointType value + */ + Type getType(); + + // Display names are from the original KML Report + @Messages({ + "Waypoint_Bookmark_Display_String=GPS Bookmark", + "Waypoint_Last_Known_Display_String=GPS Last Known Location", + "Waypoint_EXIF_Display_String=EXIF Metadata With Location", + "Waypoint_Route_Point_Display_String=GPS Individual Route Point", + "Waypoint_Search_Display_String=GPS Search", + "Waypoint_Trackpoint_Display_String=GPS Trackpoint" + }) + + /** + * An enum to keep track of the type of a way point. + */ + enum Type { + BOOKMARK(Bundle.Waypoint_Bookmark_Display_String()), + LAST_KNOWN_LOCATION(Bundle.Waypoint_Last_Known_Display_String()), + METADATA_EXIF(Bundle.Waypoint_EXIF_Display_String()), + ROUTE_POINT(Bundle.Waypoint_Route_Point_Display_String()), + SEARCH(Bundle.Waypoint_Search_Display_String()), + TRACKPOINT(Bundle.Waypoint_Trackpoint_Display_String()), + UNKNOWN("Unknown"); + + private final String displayName; + + /** + * Constructs a Waypoint.Type enum value + * + * @param displayName String value title for enum + */ + private Type(String displayName) { + this.displayName = displayName; + } + + /** + * Returns the display name for the type + * + * @return String display name + */ + public String getDisplayName() { + return displayName; + } + } +} 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 c494ab612b..311a652335 100644 --- a/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java +++ b/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java @@ -27,7 +27,6 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.*; import org.sleuthkit.autopsy.ingest.IngestManager; -import org.sleuthkit.datamodel.BlackboardArtifact; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -37,6 +36,9 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.text.SimpleDateFormat; import java.util.List; +import java.util.Map; +import java.util.Iterator; +import java.util.Map.Entry; import java.util.logging.Level; import org.jdom2.Document; import org.jdom2.Element; @@ -46,13 +48,13 @@ import org.jdom2.output.XMLOutputter; import org.jdom2.CDATA; import org.openide.filesystems.FileUtil; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.geolocation.datamodel.EXIFMetadataPoint; +import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint; import org.sleuthkit.autopsy.geolocation.datamodel.GeolocationManager; +import org.sleuthkit.autopsy.geolocation.datamodel.GeolocationUtils; import org.sleuthkit.autopsy.geolocation.datamodel.Route; import org.sleuthkit.autopsy.report.ReportBranding; import org.sleuthkit.autopsy.report.ReportProgressPanel; import org.sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException; -import org.sleuthkit.autopsy.geolocation.datamodel.BlackboardArtifactPoint; /** * Generates a KML file based on geospatial information from the BlackBoard. @@ -68,8 +70,8 @@ class KMLReport implements GeneralReportModule { private SleuthkitCase skCase; private final SimpleDateFormat kmlDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX"); private Namespace ns; - private final String SEP = "
    "; - + String HTML_PROP_FORMAT = "%s: %s
    "; + private Element gpsExifMetadataFolder; private Element gpsBookmarksFolder; private Element gpsLastKnownLocationFolder; @@ -123,8 +125,8 @@ class KMLReport implements GeneralReportModule { "KMLReport.gpsRouteError=Could not extract GPS Route information.", "KMLReport.gpsRouteDatabaseError=Could not get GPS Routes from database.", "KMLReport.gpsSearchDatabaseError=Could not get GPS Searches from database.", - "KMLReport.trackpointError=Could not extract Trackpoint information.", - "KMLReport.trackpointDatabaseError=Could not get GPS Trackpoints from database.", + "KMLReport.trackpointError=Could not extract Trackpoint information.", + "KMLReport.trackpointDatabaseError=Could not get GPS Trackpoints from database.", "KMLReport.stylesheetError=Error placing KML stylesheet. The .KML file will not function properly.", "KMLReport.kmlFileWriteError=Could not write the KML file.", "# {0} - filePath", @@ -145,7 +147,7 @@ class KMLReport implements GeneralReportModule { progressPanel.updateStatusLabel(NbBundle.getMessage(this.getClass(), "ReportKML.progress.querying")); String kmlFileFullPath = baseReportDir + REPORT_KML; //NON-NLS String errorMessage = ""; - + skCase = currentCase.getSleuthkitCase(); progressPanel.updateStatusLabel(NbBundle.getMessage(this.getClass(), "ReportKML.progress.loading")); @@ -153,15 +155,15 @@ class KMLReport implements GeneralReportModule { Document kmlDocument = setupReportDocument(); ReportProgressPanel.ReportStatus result = ReportProgressPanel.ReportStatus.COMPLETE; - - try { - makeRoutes(skCase); - addLocationsToReport(skCase, baseReportDir); - } catch(TskCoreException | IOException ex) { - errorMessage = "Failed to complete report."; + + try { + makeRoutes(skCase); + addLocationsToReport(skCase, baseReportDir); + } catch (TskCoreException | IOException ex) { + errorMessage = "Failed to complete report."; logger.log(Level.SEVERE, errorMessage, ex); //NON-NLS result = ReportProgressPanel.ReportStatus.ERROR; - } + } // Copy the style sheet try { @@ -200,10 +202,10 @@ class KMLReport implements GeneralReportModule { progressPanel.complete(result, errorMessage); } - + /** * Do all of the setting up of elements needed for the report. - * + * * @return The report document object. */ private Document setupReportDocument() { @@ -274,26 +276,27 @@ class KMLReport implements GeneralReportModule { document.addContent(gpsRouteFolder); document.addContent(gpsSearchesFolder); document.addContent(gpsTrackpointsFolder); - + return kmlDocument; } - + /** * For the given point, create the data needed for the EXIF_METADATA - * - * @param location The geolocation of the data - * @param baseReportDirectory The report directory where the image will be created. - * - * @throws IOException + * + * @param location The geolocation of the data + * @param baseReportDirectory The report directory where the image will be + * created. + * + * @throws IOException */ - void addExifMetadataContent(EXIFMetadataPoint point, String baseReportDirectory) throws IOException{ + void addExifMetadataContent(Waypoint point, String baseReportDirectory) throws IOException { Element mapPoint = makePoint(point); - if(mapPoint == null) { + if (mapPoint == null) { return; } - + AbstractFile abstractFile = point.getImage(); - String details = point.getDetails(); + String details = getFormattedDetails(point); Path path; copyFileUsingStream(abstractFile, Paths.get(baseReportDirectory, abstractFile.getName()).toFile()); @@ -304,57 +307,66 @@ class KMLReport implements GeneralReportModule { } if (path == null) { path = Paths.get(abstractFile.getName()); - } - - gpsExifMetadataFolder.addContent(makePlacemarkWithPicture(abstractFile.getName(), FeatureColor.RED, details, point.getTimestamp(), mapPoint, path, point.getFormattedCoordinates())); + } + + gpsExifMetadataFolder.addContent(makePlacemarkWithPicture(abstractFile.getName(), FeatureColor.RED, details, point.getTimestamp(), mapPoint, path, GeolocationUtils.getFormattedCoordinates(point.getLatitude(), point.getLongitude()))); } - + /** * Add the new location to the correct folder based on artifact type. - * @param skCase Currently open case + * + * @param skCase Currently open case * @param baseReportDir Output directory for the report. - * + * * @throws TskCoreException - * @throws IOException + * @throws IOException */ void addLocationsToReport(SleuthkitCase skCase, String baseReportDir) throws TskCoreException, IOException { - List points = GeolocationManager.getPoints(skCase, false); + List points = GeolocationManager.getWaypoints(skCase); - for (BlackboardArtifactPoint point : points) { + for (Waypoint point : points) { Element reportPoint = makePoint(point); if (reportPoint == null) { continue; } - if (point.getArtifact().getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF.getTypeID()) { - addExifMetadataContent((EXIFMetadataPoint) point, baseReportDir); - } else if (point.getArtifact().getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK.getTypeID()) { - gpsBookmarksFolder.addContent(makePlacemark(point.getLabel(), FeatureColor.BLUE, point.getDetails(), point.getTimestamp(), reportPoint, point.getFormattedCoordinates())); - } else if (point.getArtifact().getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION.getTypeID()) { - gpsLastKnownLocationFolder.addContent(makePlacemark(point.getLabel(), FeatureColor.PURPLE, point.getDetails(), point.getTimestamp(), reportPoint, point.getFormattedCoordinates())); - } else if (point.getArtifact().getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH.getTypeID()) { - gpsSearchesFolder.addContent(makePlacemark(point.getLabel(), FeatureColor.WHITE, point.getDetails(), point.getTimestamp(), reportPoint, point.getFormattedCoordinates())); - } else if (point.getArtifact().getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT.getTypeID()) { - gpsTrackpointsFolder.addContent(makePlacemark(point.getLabel(), FeatureColor.WHITE, point.getDetails(), point.getTimestamp(), reportPoint, point.getFormattedCoordinates())); + String formattedCords = GeolocationUtils.getFormattedCoordinates(point.getLatitude(), point.getLongitude()); + + switch (point.getType()) { + case METADATA_EXIF: + addExifMetadataContent(point, baseReportDir); + break; + case BOOKMARK: + gpsBookmarksFolder.addContent(makePlacemark(point.getLabel(), FeatureColor.BLUE, getFormattedDetails(point), point.getTimestamp(), reportPoint, formattedCords)); + break; + case LAST_KNOWN_LOCATION: + gpsLastKnownLocationFolder.addContent(makePlacemark(point.getLabel(), FeatureColor.PURPLE, getFormattedDetails(point), point.getTimestamp(), reportPoint, formattedCords)); + break; + case SEARCH: + gpsSearchesFolder.addContent(makePlacemark(point.getLabel(), FeatureColor.WHITE, getFormattedDetails(point), point.getTimestamp(), reportPoint, formattedCords)); + break; + case TRACKPOINT: + gpsTrackpointsFolder.addContent(makePlacemark(point.getLabel(), FeatureColor.WHITE, getFormattedDetails(point), point.getTimestamp(), reportPoint, formattedCords)); + break; } } } - + /** * Add the route to the route folder in the document. - * + * * @param skCase Currently open case. - * - * @throws TskCoreException + * + * @throws TskCoreException */ void makeRoutes(SleuthkitCase skCase) throws TskCoreException { List routes = GeolocationManager.getGPSRoutes(skCase); if (routes != null) { for (Route route : routes) { - List routePoints = route.getRoute(); - BlackboardArtifactPoint start = null; - BlackboardArtifactPoint end = null; + List routePoints = route.getRoute(); + Waypoint start = null; + Waypoint end = null; // This is hardcoded knowledge that there is only two points // a start and end. In the long run it would be nice to // support the idea of a route with multiple points. The Route @@ -373,18 +385,21 @@ class KMLReport implements GeneralReportModule { Element startingPoint = makePoint(start.getLatitude(), start.getLongitude(), start.getAltitude()); Element endingPoint = makePoint(end.getLatitude(), end.getLongitude(), end.getAltitude()); - String formattedCoordinates = String.format("%s to %s", start.getFormattedCoordinates(), end.getFormattedCoordinates()); + String formattedEnd = GeolocationUtils.getFormattedCoordinates(end.getLatitude(), end.getLongitude()); + String formattedStart = GeolocationUtils.getFormattedCoordinates(start.getLatitude(), start.getLongitude()); - if(reportRoute != null) { - gpsRouteFolder.addContent(makePlacemark("As-the-crow-flies Route", FeatureColor.GREEN, "", route.getTimestamp(), reportRoute, formattedCoordinates)); //NON-NLS + String formattedCoordinates = String.format("%s to %s", formattedStart, formattedEnd); + + if (reportRoute != null) { + gpsRouteFolder.addContent(makePlacemark(route.getLabel(), FeatureColor.GREEN, getFormattedDetails(route), route.getTimestamp(), reportRoute, formattedCoordinates)); //NON-NLS } - - if(startingPoint != null) { - gpsRouteFolder.addContent(makePlacemark(start.getLabel(), FeatureColor.GREEN, start.getDetails(), start.getTimestamp(), startingPoint, start.getFormattedCoordinates())); //NON-NLS + + if (startingPoint != null) { + gpsRouteFolder.addContent(makePlacemark(start.getLabel(), FeatureColor.GREEN, getFormattedDetails(start), start.getTimestamp(), startingPoint, formattedStart)); //NON-NLS } - - if(endingPoint != null) { - gpsRouteFolder.addContent(makePlacemark(end.getLabel(), FeatureColor.GREEN, end.getDetails(), end.getTimestamp(), endingPoint, end.getFormattedCoordinates())); //NON-NLS + + if (endingPoint != null) { + gpsRouteFolder.addContent(makePlacemark(end.getLabel(), FeatureColor.GREEN, getFormattedDetails(end), end.getTimestamp(), endingPoint, formattedEnd)); //NON-NLS } } } @@ -392,23 +407,23 @@ class KMLReport implements GeneralReportModule { /** * Format a point time stamp (in seconds) to the report format. - * + * * @param timeStamp The timestamp in epoch seconds. - * + * * @return The formatted timestamp */ private String getTimeStamp(long timeStamp) { return kmlDateFormat.format(new java.util.Date(timeStamp * 1000)); } - + /** * Create the point for the given artifact. - * + * * @param point Artifact point. - * - * @return point element. + * + * @return point element. */ - private Element makePoint(BlackboardArtifactPoint point) { + private Element makePoint(Waypoint point) { return makePoint(point.getLatitude(), point.getLongitude(), point.getAltitude()); } @@ -425,9 +440,9 @@ class KMLReport implements GeneralReportModule { */ private Element makePoint(Double latitude, Double longitude, Double altitude) { if (latitude == null || longitude == null) { - return null; + return null; } - + if (altitude == null) { altitude = 0.0; } @@ -456,10 +471,10 @@ class KMLReport implements GeneralReportModule { * Create a LineString for use in a Placemark. Note in this method, start * and stop altitudes get ignored, as Google Earth apparently has trouble * using altitudes for LineStrings, though the parameters are still in the - * call. - * - * If null values are pass for the latitudes or longitudes a line will not be - * drawn. + * call. + * + * If null values are pass for the latitudes or longitudes a line will not + * be drawn. * * @param startLatitude Starting latitude * @param startLongitude Starting longitude @@ -471,10 +486,10 @@ class KMLReport implements GeneralReportModule { * @return the Line as an Element */ private Element makeLineString(Double startLatitude, Double startLongitude, Double startAltitude, Double stopLatitude, Double stopLongitude, Double stopAltitude) { - if(startLatitude == null || startLongitude == null || stopLatitude == null || stopLongitude == null) { + if (startLatitude == null || startLongitude == null || stopLatitude == null || stopLongitude == null) { return null; } - + if (startAltitude == null) { startAltitude = 0.0; } @@ -649,4 +664,92 @@ class KMLReport implements GeneralReportModule { } return strbuf.toString(); } + + private String getFormattedDetails(Waypoint point) { + StringBuilder result = new StringBuilder(); //NON-NLS + result.append(getDetailsHeader(point)); + result.append(formatAttribute("Name", point.getLabel())); + + Long timestamp = point.getTimestamp(); + if (timestamp != null) { + result.append(formatAttribute("Timestamp", getTimeStamp(timestamp))); + } + + result.append(formatAttribute("Latitude", point.getLatitude().toString())); + result.append(formatAttribute("Longitude", point.getLongitude().toString())); + if (point.getAltitude() != null) { + result.append(formatAttribute("Altitude", point.getAltitude().toString())); + } + + Map otherProps = point.getOtherProperties(); + Iterator> iterator = otherProps.entrySet().iterator(); + while (iterator.hasNext()) { + Entry entry = iterator.next(); + String value = entry.getValue(); + if (value != null && !value.isEmpty()) { + result.append(formatAttribute(entry.getKey(), value)); + } + } + + return result.toString(); + } + + private String formatAttribute(String title, String value) { + return String.format(HTML_PROP_FORMAT, title, value); + } + + /* + * This current retains the headers from the original report. There is + * probably a better way to do this using the display value of the + * blackboard artifact. + */ + private String getDetailsHeader(Waypoint point) { + return String.format("

    %s

    ", point.getType().getDisplayName()); + } + + /** + * Returns an HTML formatted string of all the + * + * @param route + * + * @return + */ + private String getFormattedDetails(Route route) { + List points = route.getRoute(); + StringBuilder result = new StringBuilder(); //NON-NLS + + result.append(String.format("

    %s

    ", "GPS Route")); + result.append(formatAttribute("Name", route.getLabel())); + + Long timestamp = route.getTimestamp(); + if (timestamp != null) { + result.append(formatAttribute("Timestamp", getTimeStamp(timestamp))); + } + + if (points.size() > 1) { + Waypoint start = points.get(0); + Waypoint end = points.get(1); + + result.append(formatAttribute("Start Latitude", start.getLatitude().toString())); + result.append(formatAttribute("Start Longitude", start.getLongitude().toString())); + result.append(formatAttribute("End Latitude", end.getLatitude().toString())); + result.append(formatAttribute("End Longitude", end.getLongitude().toString())); + } + + if (route.getAltitude() != null) { + result.append(formatAttribute("Altitude", route.getAltitude().toString())); + } + + Map otherProps = route.getOtherProperties(); + Iterator> iterator = otherProps.entrySet().iterator(); + while (iterator.hasNext()) { + Entry entry = iterator.next(); + String value = entry.getValue(); + if (value != null && !value.isEmpty()) { + result.append(formatAttribute(entry.getKey(), value)); + } + } + + return result.toString(); + } } From 8da92b21356f8cb21340d2b4c7a8ea247ee88388 Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Mon, 21 Oct 2019 14:49:21 -0400 Subject: [PATCH 042/134] Updated after codacy changes --- .../datamodel/BlackboardArtifactWaypoint.java | 107 +++++++++++++----- .../datamodel/Bundle.properties-MERGED | 5 + .../datamodel/GeolocationManager.java | 2 +- ...tionUtils.java => GeolocationUtility.java} | 2 +- .../autopsy/geolocation/datamodel/Route.java | 2 +- .../geolocation/datamodel/Waypoint.java | 2 +- .../autopsy/report/modules/kml/KMLReport.java | 102 +++++++++-------- 7 files changed, 143 insertions(+), 79 deletions(-) rename Core/src/org/sleuthkit/autopsy/geolocation/datamodel/{GeolocationUtils.java => GeolocationUtility.java} (99%) diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/BlackboardArtifactWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/BlackboardArtifactWaypoint.java index e46509cf80..761e085b32 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/BlackboardArtifactWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/BlackboardArtifactWaypoint.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.geolocation.datamodel; import java.util.Map; +import org.openide.util.NbBundle.Messages; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -39,6 +40,12 @@ public class BlackboardArtifactWaypoint implements Waypoint { final private BlackboardArtifact artifact; final private Waypoint.Type type; final private Map otherAttributesMap; + + @Messages({ + "BlackboardArtifactWaypoint_Last_Known_Label=Last Known Location", + "BlackboardArtifactWaypoint_GPS_Trackpoint=GPS Trackpoint", + "BlackboardArtifactWaypoint_GPS_Search=GPS Search" + }) /** * Constructs a Waypoint from a BlackboardArtifact @@ -50,13 +57,13 @@ public class BlackboardArtifactWaypoint implements Waypoint { protected BlackboardArtifactWaypoint(BlackboardArtifact artifact) throws TskCoreException { this.artifact = artifact; timestamp = getTimestampFromArtifact(artifact); - longitude = GeolocationUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE); - latitude = GeolocationUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE); - altitude = GeolocationUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE); + longitude = GeolocationUtility.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE); + latitude = GeolocationUtility.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE); + altitude = GeolocationUtility.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE); image = getImageFromArtifact(artifact); label = getLabelFromArtifact(artifact); type = getTypeFromArtifact(artifact); - otherAttributesMap = GeolocationUtils.getOtherGeolocationAttributes(artifact); + otherAttributesMap = GeolocationUtility.getOtherGeolocationAttributes(artifact); } /** @@ -124,13 +131,11 @@ public class BlackboardArtifactWaypoint implements Waypoint { BlackboardArtifact.ARTIFACT_TYPE artifactType = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID()); - switch (artifactType) { - case TSK_METADATA_EXIF: - return GeolocationUtils.getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED); - default: - return GeolocationUtils.getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME); - + if (artifactType == BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF) { + return GeolocationUtility.getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED); } + + return GeolocationUtility.getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME); } /** @@ -173,34 +178,23 @@ public class BlackboardArtifactWaypoint implements Waypoint { * @throws TskCoreException */ private String getLabelFromArtifact(BlackboardArtifact artifact) throws TskCoreException { - BlackboardArtifact.ARTIFACT_TYPE artifactType = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID()); - - if (artifactType == BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF) { - if (getImage() != null) { - return getImage().getName(); - } + String typeLabel = getLabelFromImage(); + if (typeLabel != null && !typeLabel.isEmpty()) { + return typeLabel; } - String typeLabel = GeolocationUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); + BlackboardArtifact.ARTIFACT_TYPE artifactType = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID()); + typeLabel = GeolocationUtility.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); if (typeLabel == null || typeLabel.isEmpty()) { switch (artifactType) { case TSK_GPS_SEARCH: - typeLabel = GeolocationUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION); - if (typeLabel == null || typeLabel.isEmpty()) { - typeLabel = "GPS Search"; - } + typeLabel = getLabelForSearch(artifact); break; case TSK_GPS_TRACKPOINT: - typeLabel = GeolocationUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME); - if (typeLabel == null || typeLabel.isEmpty()) { - typeLabel = GeolocationUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FLAG); - } - if (typeLabel == null || typeLabel.isEmpty()) { - typeLabel = "GPS Trackpoint"; - } + typeLabel = getLabelforTrackpoint(artifact); break; case TSK_GPS_LAST_KNOWN_LOCATION: - typeLabel = "Last Known Location"; + typeLabel = Bundle.BlackboardArtifactWaypoint_Last_Known_Label(); break; default: typeLabel = ""; @@ -210,6 +204,61 @@ public class BlackboardArtifactWaypoint implements Waypoint { return typeLabel; } + /** + * Returns a Label for a GPS_Trackpoint artifact. This function assumes the + * calling function has already checked TSK_NAME. + * + * @param artifact BlackboardArtifact for waypoint + * + * @return String label for the artifacts way point. + * + * @throws TskCoreException + */ + private String getLabelforTrackpoint(BlackboardArtifact artifact) throws TskCoreException { + String typeLabel = GeolocationUtility.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME); + if (typeLabel == null || typeLabel.isEmpty()) { + typeLabel = GeolocationUtility.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FLAG); + } + + if (typeLabel == null || typeLabel.isEmpty()) { + typeLabel = Bundle.BlackboardArtifactWaypoint_GPS_Trackpoint(); + } + + return typeLabel; + } + + /** + * Returns a Label for a GPS_SEARCH artifact. This function assumes the + * calling function has already checked TSK_NAME. + * + * @param artifact BlackboardArtifact for waypoint + * + * @return String label for the artifacts way point. + * + * @throws TskCoreException + */ + private String getLabelForSearch(BlackboardArtifact artifact) throws TskCoreException { + String typeLabel = GeolocationUtility.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION); + if (typeLabel == null || typeLabel.isEmpty()) { + typeLabel = Bundle.BlackboardArtifactWaypoint_GPS_Search(); + } + + return typeLabel; + } + + /** + * Returns a Label from an image. + * + * @return String label for waypoint with image or null. + */ + private String getLabelFromImage() { + if (getImage() != null) { + return getImage().getName(); + } else { + return null; + } + } + /** * Gets the type of waypoint based on the artifact. * diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Bundle.properties-MERGED index 512dd5f416..33e0ff3247 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Bundle.properties-MERGED @@ -1,4 +1,9 @@ +BlackboardArtifactWaypoint_GPS_Search=GPS Search +BlackboardArtifactWaypoint_GPS_Trackpoint=GPS Trackpoint +BlackboardArtifactWaypoint_Last_Known_Label=Last Known Location +Route_End_Label=End Route_Label=As-the-crow-flies Route +Route_Start_Label=Start Waypoint_Bookmark_Display_String=GPS Bookmark Waypoint_EXIF_Display_String=EXIF Metadata With Location Waypoint_Last_Known_Display_String=GPS Last Known Location diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationManager.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationManager.java index fe5cdd1884..a6a882caa9 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationManager.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationManager.java @@ -29,7 +29,7 @@ import org.sleuthkit.datamodel.TskCoreException; * Static functions for the creations of geolocation points for artifacts. * */ -public class GeolocationManager { +public final class GeolocationManager { /** * Add a private constructor to silence codacy warning about making this diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtils.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtility.java similarity index 99% rename from Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtils.java rename to Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtility.java index 20e8b6ab7b..f8a543af5c 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtils.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtility.java @@ -15,7 +15,7 @@ import org.sleuthkit.datamodel.TskCoreException; * * */ -public class GeolocationUtils { +public class GeolocationUtility { private static final String DEFAULT_COORD_FORMAT = "%.2f, %.2f"; diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java index 68d93509d5..7e9acaf38c 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java @@ -218,6 +218,6 @@ public class Route { * @throws TskCoreException */ private Map initalizeOtherAttributes(BlackboardArtifact artifact) throws TskCoreException { - return GeolocationUtils.getOtherGeolocationAttributes(artifact); + return GeolocationUtility.getOtherGeolocationAttributes(artifact); } } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java index 736d0726a1..edd3257391 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java @@ -118,7 +118,7 @@ public interface Waypoint { * * @param displayName String value title for enum */ - private Type(String displayName) { + Type(String displayName) { this.displayName = displayName; } 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 311a652335..105132770b 100644 --- a/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java +++ b/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java @@ -50,7 +50,7 @@ import org.openide.filesystems.FileUtil; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint; import org.sleuthkit.autopsy.geolocation.datamodel.GeolocationManager; -import org.sleuthkit.autopsy.geolocation.datamodel.GeolocationUtils; +import org.sleuthkit.autopsy.geolocation.datamodel.GeolocationUtility; import org.sleuthkit.autopsy.geolocation.datamodel.Route; import org.sleuthkit.autopsy.report.ReportBranding; import org.sleuthkit.autopsy.report.ReportProgressPanel; @@ -309,7 +309,7 @@ class KMLReport implements GeneralReportModule { path = Paths.get(abstractFile.getName()); } - gpsExifMetadataFolder.addContent(makePlacemarkWithPicture(abstractFile.getName(), FeatureColor.RED, details, point.getTimestamp(), mapPoint, path, GeolocationUtils.getFormattedCoordinates(point.getLatitude(), point.getLongitude()))); + gpsExifMetadataFolder.addContent(makePlacemarkWithPicture(abstractFile.getName(), FeatureColor.RED, details, point.getTimestamp(), mapPoint, path, GeolocationUtility.getFormattedCoordinates(point.getLatitude(), point.getLongitude()))); } /** @@ -330,7 +330,7 @@ class KMLReport implements GeneralReportModule { continue; } - String formattedCords = GeolocationUtils.getFormattedCoordinates(point.getLatitude(), point.getLongitude()); + String formattedCords = GeolocationUtility.getFormattedCoordinates(point.getLatitude(), point.getLongitude()); switch (point.getType()) { case METADATA_EXIF: @@ -348,6 +348,9 @@ class KMLReport implements GeneralReportModule { case TRACKPOINT: gpsTrackpointsFolder.addContent(makePlacemark(point.getLabel(), FeatureColor.WHITE, getFormattedDetails(point), point.getTimestamp(), reportPoint, formattedCords)); break; + default: + // default is here to make codacy happy. + break; } } } @@ -361,47 +364,53 @@ class KMLReport implements GeneralReportModule { */ void makeRoutes(SleuthkitCase skCase) throws TskCoreException { List routes = GeolocationManager.getGPSRoutes(skCase); + + if(routes == null) { + return; + } - if (routes != null) { - for (Route route : routes) { - List routePoints = route.getRoute(); - Waypoint start = null; - Waypoint end = null; - // This is hardcoded knowledge that there is only two points - // a start and end. In the long run it would be nice to - // support the idea of a route with multiple points. The Route - // class supports that idea. Would be nice to figure out how to support - // for report. - if (routePoints != null && routePoints.size() > 1) { - start = routePoints.get(0); - end = routePoints.get(1); - } + for (Route route : routes) { + addRouteToReport(route); + } + } + + void addRouteToReport(Route route) { + List routePoints = route.getRoute(); + Waypoint start = null; + Waypoint end = null; + // This is hardcoded knowledge that there is only two points + // a start and end. In the long run it would be nice to + // support the idea of a route with multiple points. The Route + // class supports that idea. Would be nice to figure out how to support + // for report. + if (routePoints != null && routePoints.size() > 1) { + start = routePoints.get(0); + end = routePoints.get(1); + } - if (start == null || end == null) { - continue; - } + if (start == null || end == null) { + return; + } - Element reportRoute = makeLineString(start.getLatitude(), start.getLongitude(), start.getAltitude(), end.getLatitude(), end.getLongitude(), end.getAltitude()); - Element startingPoint = makePoint(start.getLatitude(), start.getLongitude(), start.getAltitude()); - Element endingPoint = makePoint(end.getLatitude(), end.getLongitude(), end.getAltitude()); + Element reportRoute = makeLineString(start.getLatitude(), start.getLongitude(), start.getAltitude(), end.getLatitude(), end.getLongitude(), end.getAltitude()); + Element startingPoint = makePoint(start.getLatitude(), start.getLongitude(), start.getAltitude()); + Element endingPoint = makePoint(end.getLatitude(), end.getLongitude(), end.getAltitude()); - String formattedEnd = GeolocationUtils.getFormattedCoordinates(end.getLatitude(), end.getLongitude()); - String formattedStart = GeolocationUtils.getFormattedCoordinates(start.getLatitude(), start.getLongitude()); + String formattedEnd = GeolocationUtility.getFormattedCoordinates(end.getLatitude(), end.getLongitude()); + String formattedStart = GeolocationUtility.getFormattedCoordinates(start.getLatitude(), start.getLongitude()); - String formattedCoordinates = String.format("%s to %s", formattedStart, formattedEnd); + String formattedCoordinates = String.format("%s to %s", formattedStart, formattedEnd); - if (reportRoute != null) { - gpsRouteFolder.addContent(makePlacemark(route.getLabel(), FeatureColor.GREEN, getFormattedDetails(route), route.getTimestamp(), reportRoute, formattedCoordinates)); //NON-NLS - } + if (reportRoute != null) { + gpsRouteFolder.addContent(makePlacemark(route.getLabel(), FeatureColor.GREEN, getFormattedDetails(route), route.getTimestamp(), reportRoute, formattedCoordinates)); //NON-NLS + } - if (startingPoint != null) { - gpsRouteFolder.addContent(makePlacemark(start.getLabel(), FeatureColor.GREEN, getFormattedDetails(start), start.getTimestamp(), startingPoint, formattedStart)); //NON-NLS - } + if (startingPoint != null) { + gpsRouteFolder.addContent(makePlacemark(start.getLabel(), FeatureColor.GREEN, getFormattedDetails(start), start.getTimestamp(), startingPoint, formattedStart)); //NON-NLS + } - if (endingPoint != null) { - gpsRouteFolder.addContent(makePlacemark(end.getLabel(), FeatureColor.GREEN, getFormattedDetails(end), end.getTimestamp(), endingPoint, formattedEnd)); //NON-NLS - } - } + if (endingPoint != null) { + gpsRouteFolder.addContent(makePlacemark(end.getLabel(), FeatureColor.GREEN, getFormattedDetails(end), end.getTimestamp(), endingPoint, formattedEnd)); //NON-NLS } } @@ -667,16 +676,17 @@ class KMLReport implements GeneralReportModule { private String getFormattedDetails(Waypoint point) { StringBuilder result = new StringBuilder(); //NON-NLS - result.append(getDetailsHeader(point)); - result.append(formatAttribute("Name", point.getLabel())); + result.append(getDetailsHeader(point)) + .append(formatAttribute("Name", point.getLabel())); Long timestamp = point.getTimestamp(); if (timestamp != null) { result.append(formatAttribute("Timestamp", getTimeStamp(timestamp))); } - result.append(formatAttribute("Latitude", point.getLatitude().toString())); - result.append(formatAttribute("Longitude", point.getLongitude().toString())); + result.append(formatAttribute("Latitude", point.getLatitude().toString())) + .append(formatAttribute("Longitude", point.getLongitude().toString())); + if (point.getAltitude() != null) { result.append(formatAttribute("Altitude", point.getAltitude().toString())); } @@ -718,8 +728,8 @@ class KMLReport implements GeneralReportModule { List points = route.getRoute(); StringBuilder result = new StringBuilder(); //NON-NLS - result.append(String.format("

    %s

    ", "GPS Route")); - result.append(formatAttribute("Name", route.getLabel())); + result.append(String.format("

    %s

    ", "GPS Route")) + .append(formatAttribute("Name", route.getLabel())); Long timestamp = route.getTimestamp(); if (timestamp != null) { @@ -730,10 +740,10 @@ class KMLReport implements GeneralReportModule { Waypoint start = points.get(0); Waypoint end = points.get(1); - result.append(formatAttribute("Start Latitude", start.getLatitude().toString())); - result.append(formatAttribute("Start Longitude", start.getLongitude().toString())); - result.append(formatAttribute("End Latitude", end.getLatitude().toString())); - result.append(formatAttribute("End Longitude", end.getLongitude().toString())); + result.append(formatAttribute("Start Latitude", start.getLatitude().toString())) + .append(formatAttribute("Start Longitude", start.getLongitude().toString())) + .append(formatAttribute("End Latitude", end.getLatitude().toString())) + .append(formatAttribute("End Longitude", end.getLongitude().toString())); } if (route.getAltitude() != null) { From 7be1c9758f5567327242676046819845dc53290d Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Mon, 21 Oct 2019 15:01:10 -0400 Subject: [PATCH 043/134] More codacy related fixes --- .../datamodel/BlackboardArtifactWaypoint.java | 37 ++++++++----------- ...tionUtility.java => GeolocationUtils.java} | 9 ++++- .../autopsy/geolocation/datamodel/Route.java | 2 +- .../autopsy/report/modules/kml/KMLReport.java | 10 ++--- 4 files changed, 30 insertions(+), 28 deletions(-) rename Core/src/org/sleuthkit/autopsy/geolocation/datamodel/{GeolocationUtility.java => GeolocationUtils.java} (97%) diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/BlackboardArtifactWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/BlackboardArtifactWaypoint.java index 761e085b32..3487c61aba 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/BlackboardArtifactWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/BlackboardArtifactWaypoint.java @@ -57,13 +57,13 @@ public class BlackboardArtifactWaypoint implements Waypoint { protected BlackboardArtifactWaypoint(BlackboardArtifact artifact) throws TskCoreException { this.artifact = artifact; timestamp = getTimestampFromArtifact(artifact); - longitude = GeolocationUtility.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE); - latitude = GeolocationUtility.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE); - altitude = GeolocationUtility.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE); + longitude = GeolocationUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE); + latitude = GeolocationUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE); + altitude = GeolocationUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE); image = getImageFromArtifact(artifact); label = getLabelFromArtifact(artifact); type = getTypeFromArtifact(artifact); - otherAttributesMap = GeolocationUtility.getOtherGeolocationAttributes(artifact); + otherAttributesMap = GeolocationUtils.getOtherGeolocationAttributes(artifact); } /** @@ -132,10 +132,10 @@ public class BlackboardArtifactWaypoint implements Waypoint { BlackboardArtifact.ARTIFACT_TYPE artifactType = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID()); if (artifactType == BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF) { - return GeolocationUtility.getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED); + return GeolocationUtils.getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED); } - return GeolocationUtility.getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME); + return GeolocationUtils.getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME); } /** @@ -150,18 +150,13 @@ public class BlackboardArtifactWaypoint implements Waypoint { * @throws TskCoreException */ private AbstractFile getImageFromArtifact(BlackboardArtifact artifact) throws TskCoreException { - if (artifact == null) { - return null; - } - BlackboardArtifact.ARTIFACT_TYPE artifactType = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID()); - switch (artifactType) { - case TSK_METADATA_EXIF: + if (artifactType == BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF) { return artifact.getSleuthkitCase().getAbstractFileById(artifact.getObjectID()); - default: - return null; } + + return null; } /** @@ -178,13 +173,13 @@ public class BlackboardArtifactWaypoint implements Waypoint { * @throws TskCoreException */ private String getLabelFromArtifact(BlackboardArtifact artifact) throws TskCoreException { - String typeLabel = getLabelFromImage(); + String typeLabel = getLabelFromImage(artifact); if (typeLabel != null && !typeLabel.isEmpty()) { return typeLabel; } BlackboardArtifact.ARTIFACT_TYPE artifactType = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID()); - typeLabel = GeolocationUtility.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); + typeLabel = GeolocationUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); if (typeLabel == null || typeLabel.isEmpty()) { switch (artifactType) { case TSK_GPS_SEARCH: @@ -215,9 +210,9 @@ public class BlackboardArtifactWaypoint implements Waypoint { * @throws TskCoreException */ private String getLabelforTrackpoint(BlackboardArtifact artifact) throws TskCoreException { - String typeLabel = GeolocationUtility.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME); + String typeLabel = GeolocationUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME); if (typeLabel == null || typeLabel.isEmpty()) { - typeLabel = GeolocationUtility.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FLAG); + typeLabel = GeolocationUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FLAG); } if (typeLabel == null || typeLabel.isEmpty()) { @@ -238,7 +233,7 @@ public class BlackboardArtifactWaypoint implements Waypoint { * @throws TskCoreException */ private String getLabelForSearch(BlackboardArtifact artifact) throws TskCoreException { - String typeLabel = GeolocationUtility.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION); + String typeLabel = GeolocationUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION); if (typeLabel == null || typeLabel.isEmpty()) { typeLabel = Bundle.BlackboardArtifactWaypoint_GPS_Search(); } @@ -251,8 +246,8 @@ public class BlackboardArtifactWaypoint implements Waypoint { * * @return String label for waypoint with image or null. */ - private String getLabelFromImage() { - if (getImage() != null) { + private String getLabelFromImage(BlackboardArtifact artifact) throws TskCoreException{ + if (getImageFromArtifact(artifact) != null) { return getImage().getName(); } else { return null; diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtility.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtils.java similarity index 97% rename from Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtility.java rename to Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtils.java index f8a543af5c..cd24b8631d 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtility.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtils.java @@ -15,7 +15,7 @@ import org.sleuthkit.datamodel.TskCoreException; * * */ -public class GeolocationUtility { +public class GeolocationUtils { private static final String DEFAULT_COORD_FORMAT = "%.2f, %.2f"; @@ -40,6 +40,13 @@ public class GeolocationUtility { BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_MAKE, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_MODEL }; + + /** + * This is a Utility class that should not be constructed. + */ + private GeolocationUtils() { + + } static public String getFormattedCoordinates(Double latitude, Double longitude) { if (latitude == null || longitude == null) { diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java index 7e9acaf38c..68d93509d5 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java @@ -218,6 +218,6 @@ public class Route { * @throws TskCoreException */ private Map initalizeOtherAttributes(BlackboardArtifact artifact) throws TskCoreException { - return GeolocationUtility.getOtherGeolocationAttributes(artifact); + return GeolocationUtils.getOtherGeolocationAttributes(artifact); } } 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 105132770b..175819610f 100644 --- a/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java +++ b/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java @@ -50,7 +50,7 @@ import org.openide.filesystems.FileUtil; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint; import org.sleuthkit.autopsy.geolocation.datamodel.GeolocationManager; -import org.sleuthkit.autopsy.geolocation.datamodel.GeolocationUtility; +import org.sleuthkit.autopsy.geolocation.datamodel.GeolocationUtils; import org.sleuthkit.autopsy.geolocation.datamodel.Route; import org.sleuthkit.autopsy.report.ReportBranding; import org.sleuthkit.autopsy.report.ReportProgressPanel; @@ -309,7 +309,7 @@ class KMLReport implements GeneralReportModule { path = Paths.get(abstractFile.getName()); } - gpsExifMetadataFolder.addContent(makePlacemarkWithPicture(abstractFile.getName(), FeatureColor.RED, details, point.getTimestamp(), mapPoint, path, GeolocationUtility.getFormattedCoordinates(point.getLatitude(), point.getLongitude()))); + gpsExifMetadataFolder.addContent(makePlacemarkWithPicture(abstractFile.getName(), FeatureColor.RED, details, point.getTimestamp(), mapPoint, path, GeolocationUtils.getFormattedCoordinates(point.getLatitude(), point.getLongitude()))); } /** @@ -330,7 +330,7 @@ class KMLReport implements GeneralReportModule { continue; } - String formattedCords = GeolocationUtility.getFormattedCoordinates(point.getLatitude(), point.getLongitude()); + String formattedCords = GeolocationUtils.getFormattedCoordinates(point.getLatitude(), point.getLongitude()); switch (point.getType()) { case METADATA_EXIF: @@ -396,8 +396,8 @@ class KMLReport implements GeneralReportModule { Element startingPoint = makePoint(start.getLatitude(), start.getLongitude(), start.getAltitude()); Element endingPoint = makePoint(end.getLatitude(), end.getLongitude(), end.getAltitude()); - String formattedEnd = GeolocationUtility.getFormattedCoordinates(end.getLatitude(), end.getLongitude()); - String formattedStart = GeolocationUtility.getFormattedCoordinates(start.getLatitude(), start.getLongitude()); + String formattedEnd = GeolocationUtils.getFormattedCoordinates(end.getLatitude(), end.getLongitude()); + String formattedStart = GeolocationUtils.getFormattedCoordinates(start.getLatitude(), start.getLongitude()); String formattedCoordinates = String.format("%s to %s", formattedStart, formattedEnd); From 339de01305c05f1db82b8f0fad129a68b2a7a880 Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Mon, 21 Oct 2019 15:09:05 -0400 Subject: [PATCH 044/134] winning at codacy --- .../autopsy/geolocation/datamodel/GeolocationUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtils.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtils.java index cd24b8631d..411774b987 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtils.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtils.java @@ -15,7 +15,7 @@ import org.sleuthkit.datamodel.TskCoreException; * * */ -public class GeolocationUtils { +public final class GeolocationUtils { private static final String DEFAULT_COORD_FORMAT = "%.2f, %.2f"; From f9c838755e4b4666be229c86b38c5b604f553d14 Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Mon, 21 Oct 2019 15:16:11 -0400 Subject: [PATCH 045/134] Fixing more issues from codacy --- .../geolocation/datamodel/GeolocationUtils.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtils.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtils.java index 411774b987..ee8d917fce 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtils.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtils.java @@ -68,7 +68,7 @@ public final class GeolocationUtils { * * @throws TskCoreException */ - static protected String getString(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) throws TskCoreException { + static String getString(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) throws TskCoreException { if (artifact == null) { return null; } @@ -87,7 +87,7 @@ public final class GeolocationUtils { * * @throws TskCoreException */ - static protected Double getDouble(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) throws TskCoreException { + static Double getDouble(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) throws TskCoreException { if (artifact == null) { return null; } @@ -110,7 +110,7 @@ public final class GeolocationUtils { * * @throws TskCoreException */ - static protected Long getLong(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) throws TskCoreException { + static Long getLong(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) throws TskCoreException { if (artifact == null) { return null; } @@ -134,7 +134,7 @@ public final class GeolocationUtils { * * @throws TskCoreException */ - static protected Integer getInteger(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) throws TskCoreException { + static Integer getInteger(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) throws TskCoreException { if (artifact == null) { return null; } @@ -157,7 +157,7 @@ public final class GeolocationUtils { * * @throws TskCoreException */ - static protected String getFormattedTimestamp(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) throws TskCoreException { + static String getFormattedTimestamp(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) throws TskCoreException { if (artifact == null) { return null; } @@ -179,7 +179,7 @@ public final class GeolocationUtils { * * @return */ - static protected Map getOtherGeolocationAttributes(BlackboardArtifact artifact) throws TskCoreException { + static Map getOtherGeolocationAttributes(BlackboardArtifact artifact) throws TskCoreException { Map map = new HashMap<>(); for (BlackboardAttribute.ATTRIBUTE_TYPE type : OTHER_GEO_ATTRIBUTES) { From d39dc4232920963ecb8dc4ff5199eed964b8cd45 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Mon, 21 Oct 2019 17:19:26 -0400 Subject: [PATCH 046/134] Created a new View for the JSlider. Made the slider thumb easier to drag. Added track highlighting. Adding thread safety to the progress slider. --- .../contentviewers/MediaPlayerPanel.form | 10 +- .../contentviewers/MediaPlayerPanel.java | 249 ++++++++++++++++-- 2 files changed, 240 insertions(+), 19 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form index 605ed93697..d8433a907b 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form @@ -16,8 +16,8 @@ - + @@ -106,6 +106,9 @@ + + +
    @@ -172,7 +175,7 @@ - + @@ -192,6 +195,9 @@ + + + diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java index dec8bf55b4..be600a0ea6 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java @@ -19,8 +19,16 @@ package org.sleuthkit.autopsy.contentviewers; import com.google.common.io.Files; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.RenderingHints; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.MouseEvent; import java.io.File; import java.io.IOException; import java.util.Arrays; @@ -30,6 +38,7 @@ import java.util.SortedSet; import java.util.TreeSet; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; +import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import javax.swing.BoxLayout; @@ -52,13 +61,17 @@ import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.TskData; import javafx.embed.swing.JFXPanel; +import javax.swing.JComponent; +import javax.swing.JSlider; import javax.swing.SwingUtilities; import javax.swing.event.ChangeListener; +import javax.swing.plaf.basic.BasicSliderUI; import org.freedesktop.gstreamer.ClockTime; import org.freedesktop.gstreamer.Format; import org.freedesktop.gstreamer.GstException; import org.freedesktop.gstreamer.event.SeekFlags; import org.freedesktop.gstreamer.event.SeekType; +import org.openide.util.Exceptions; /** * This is a video player that is part of the Media View layered pane. It uses @@ -188,6 +201,11 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie private static final int SKIP_IN_SECONDS = 30; private ExtractMedia extractMediaWorker; + + //Serialize setting the value of the Video progress slider. + //The slider is a shared resource between the VideoPanelUpdater + //and the TrackListener of the JSliderUI. + private final Semaphore sliderLock; /** * Creates new form MediaViewVideoPanel @@ -195,6 +213,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie public MediaPlayerPanel() throws GstException, UnsatisfiedLinkError { initComponents(); customizeComponents(); + sliderLock = new Semaphore(1); } private void customizeComponents() { @@ -531,26 +550,220 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie @Override public void actionPerformed(ActionEvent e) { - if (!progressSlider.getValueIsAdjusting()) { - long position = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); - long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS); - /** - * Duration may not be known until there is video data in the - * pipeline. We start this updater when data-flow has just been - * initiated so buffering may still be in progress. - */ - if (duration >= 0 && position >= 0) { - double relativePosition = (double) position / duration; - progressSlider.setValue((int) (relativePosition * PROGRESS_SLIDER_SIZE)); - } + try { + if (!progressSlider.getValueIsAdjusting()) { + sliderLock.acquire(); + long position = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); + long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS); + /** + * Duration may not be known until there is video data in the + * pipeline. We start this updater when data-flow has just been + * initiated so buffering may still be in progress. + */ + if (duration >= 0 && position >= 0) { + double relativePosition = (double) position / duration; + progressSlider.setValue((int) (relativePosition * PROGRESS_SLIDER_SIZE)); + } - SwingUtilities.invokeLater(() -> { - updateTimeLabel(position, duration); - }); + SwingUtilities.invokeLater(() -> { + updateTimeLabel(position, duration); + }); + sliderLock.release(); + } + } catch (InterruptedException ex) { + } } } + /** + * Represents the default configuration for the circular JSliderUI. + */ + private class CircularJSliderConfiguration { + + //Thumb configurations + private final Color thumbColor; + private final Dimension thumbDimension; + + //Track configurations + //Progress bar can be bisected into a seen group + //and an unseen group. + private final Color unseen; + private final Color seen; + + /** + * Default configuration + * + * JSlider is light blue RGB(0,130,255). Seen track is light blue + * RGB(0,130,255). Unseen track is light grey RGB(192, 192, 192). + * + * @param thumbDimension Size of the oval thumb. + */ + public CircularJSliderConfiguration(Dimension thumbDimension) { + Color lightBlue = new Color(0, 130, 255); + + seen = lightBlue; + unseen = Color.LIGHT_GRAY; + + thumbColor = lightBlue; + + this.thumbDimension = new Dimension(thumbDimension); + } + + public Color getThumbColor() { + return thumbColor; + } + + public Color getUnseenTrackColor() { + return unseen; + } + + public Color getSeenTrackColor() { + return seen; + } + + public Dimension getThumbDimension() { + return new Dimension(thumbDimension); + } + } + + /** + * Custom view for the JSlider. + */ + private class CircularJSliderUI extends BasicSliderUI { + + private final CircularJSliderConfiguration config; + + /** + * Creates a custom view for the JSlider. This view draws a blue oval + * thumb at the given width and height. It also paints the track blue as + * the thumb progresses. + * + * @param b JSlider component + * @param width Width of the oval + * @param height Height of the oval. + */ + public CircularJSliderUI(JSlider b, CircularJSliderConfiguration config) { + super(b); + this.config = config; + } + + @Override + protected Dimension getThumbSize() { + return config.getThumbDimension(); + } + + /** + * Modifies the View to be an oval rather than the + * rectangle Controller. + */ + @Override + public void paintThumb(Graphics g) { + Rectangle thumb = this.thumbRect; + + Color original = g.getColor(); + + //Change the thumb view from the rectangle + //controller to an oval. + g.setColor(config.getThumbColor()); + Dimension thumbDimension = config.getThumbDimension(); + g.fillOval(thumb.x, thumb.y, thumbDimension.width, thumbDimension.height); + + //Preserve the graphics original color + g.setColor(original); + } + + @Override + public void paintTrack(Graphics g) { + //This rectangle is the bounding box for the progress bar + //portion of the slider. The track is painted in the middle + //of this rectangle and the thumb laid overtop. + Rectangle track = this.trackRect; + + //Get the location of the thumb, this point splits the + //progress bar into 2 line segments, seen and unseen. + Rectangle thumb = this.thumbRect; + int thumbX = thumb.x; + int thumbY = thumb.y; + + Color original = g.getColor(); + + //Paint the seen side + g.setColor(config.getSeenTrackColor()); + g.drawLine(track.x, track.y + track.height / 2, + thumbX, thumbY + track.height / 2); + + //Paint the unseen side + g.setColor(config.getUnseenTrackColor()); + g.drawLine(thumbX, thumbY + track.height / 2, + track.x + track.width, track.y + track.height / 2); + + //Preserve the graphics color. + g.setColor(original); + } + + @Override + protected TrackListener createTrackListener(JSlider slider) { + return new CustomTrackListener(); + } + + @Override + protected void scrollDueToClickInTrack(int direction) { + try { + //Set the thumb position to the mouse press location, as opposed + //to the closest "block" which is the default behavior. + Point mousePosition = slider.getMousePosition(); + if (mousePosition == null) { + return; + } + int value = this.valueForXPosition(mousePosition.x); + + //Lock the slider down, which is a shared resource. + //The VideoPanelUpdater (dedicated thread) keeps the + //slider in sync with the video position, so without + //proper locking our change could be overwritten. + sliderLock.acquire(); + slider.setValueIsAdjusting(true); + slider.setValue(value); + slider.setValueIsAdjusting(false); + sliderLock.release(); + } catch (InterruptedException ex) { + } + } + + /** + * Applies anti-aliasing if available. + */ + @Override + public void update(Graphics g, JComponent c) { + if (g instanceof Graphics2D) { + Graphics2D g2 = (Graphics2D) g; + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + } + + super.update(g, c); + } + + /** + * This track listener will force the thumb to be snapped to the + * mouse location. This makes grabbing and dragging the JSlider much + * easier. Using the default track listener, the user would have to + * click exactly on the slider thumb to drag it. Now the thumb positions + * itself under the mouse so that it can always be dragged. + */ + private class CustomTrackListener extends CircularJSliderUI.TrackListener { + @Override + public void mousePressed(MouseEvent e) { + //Snap the thumb to position of the mouse + scrollDueToClickInTrack(0); + + //Handle the event as normal. + super.mousePressed(e); + } + } + } + /** * 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 @@ -592,6 +805,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie progressSlider.setDoubleBuffered(true); progressSlider.setMinimumSize(new java.awt.Dimension(36, 21)); progressSlider.setPreferredSize(new java.awt.Dimension(200, 21)); + progressSlider.setUI(new CircularJSliderUI(progressSlider, new CircularJSliderConfiguration(new Dimension(18,18)))); org.openide.awt.Mnemonics.setLocalizedText(progressLabel, org.openide.util.NbBundle.getMessage(MediaPlayerPanel.class, "MediaPlayerPanel.progressLabel.text")); // NOI18N @@ -645,7 +859,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie gridBagConstraints.ipadx = 8; gridBagConstraints.ipady = 7; gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; - gridBagConstraints.insets = new java.awt.Insets(6, 6, 0, 0); + gridBagConstraints.insets = new java.awt.Insets(6, 14, 0, 0); buttonPanel.add(VolumeIcon, gridBagConstraints); audioSlider.setMajorTickSpacing(10); @@ -655,6 +869,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie audioSlider.setValue(25); audioSlider.setMinimumSize(new java.awt.Dimension(200, 21)); audioSlider.setPreferredSize(new java.awt.Dimension(200, 21)); + audioSlider.setUI(new CircularJSliderUI(audioSlider, new CircularJSliderConfiguration(new Dimension(15,15)))); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 4; gridBagConstraints.gridy = 0; @@ -739,8 +954,8 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(controlPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(videoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) From 0573fc4a15292f80a081dced2cdc6cc1e4dd0003 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Mon, 21 Oct 2019 17:32:13 -0400 Subject: [PATCH 047/134] Updated javadoc and cleared out all 1 letter variable names --- .../contentviewers/MediaPlayerPanel.java | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java index be600a0ea6..95634f0435 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java @@ -71,7 +71,6 @@ import org.freedesktop.gstreamer.Format; import org.freedesktop.gstreamer.GstException; import org.freedesktop.gstreamer.event.SeekFlags; import org.freedesktop.gstreamer.event.SeekType; -import org.openide.util.Exceptions; /** * This is a video player that is part of the Media View layered pane. It uses @@ -571,7 +570,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie sliderLock.release(); } } catch (InterruptedException ex) { - + //Video panel thread interrupted while waiting on lock. } } } @@ -640,11 +639,11 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie * the thumb progresses. * * @param b JSlider component - * @param width Width of the oval - * @param height Height of the oval. + * @param config Configuration object. Contains info about thumb dimensions + * and colors. */ - public CircularJSliderUI(JSlider b, CircularJSliderConfiguration config) { - super(b); + public CircularJSliderUI(JSlider slider, CircularJSliderConfiguration config) { + super(slider); this.config = config; } @@ -658,23 +657,23 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie * rectangle Controller. */ @Override - public void paintThumb(Graphics g) { + public void paintThumb(Graphics graphic) { Rectangle thumb = this.thumbRect; - Color original = g.getColor(); + Color original = graphic.getColor(); //Change the thumb view from the rectangle //controller to an oval. - g.setColor(config.getThumbColor()); + graphic.setColor(config.getThumbColor()); Dimension thumbDimension = config.getThumbDimension(); - g.fillOval(thumb.x, thumb.y, thumbDimension.width, thumbDimension.height); + graphic.fillOval(thumb.x, thumb.y, thumbDimension.width, thumbDimension.height); //Preserve the graphics original color - g.setColor(original); + graphic.setColor(original); } @Override - public void paintTrack(Graphics g) { + public void paintTrack(Graphics graphic) { //This rectangle is the bounding box for the progress bar //portion of the slider. The track is painted in the middle //of this rectangle and the thumb laid overtop. @@ -686,20 +685,20 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie int thumbX = thumb.x; int thumbY = thumb.y; - Color original = g.getColor(); + Color original = graphic.getColor(); //Paint the seen side - g.setColor(config.getSeenTrackColor()); - g.drawLine(track.x, track.y + track.height / 2, + graphic.setColor(config.getSeenTrackColor()); + graphic.drawLine(track.x, track.y + track.height / 2, thumbX, thumbY + track.height / 2); //Paint the unseen side - g.setColor(config.getUnseenTrackColor()); - g.drawLine(thumbX, thumbY + track.height / 2, + graphic.setColor(config.getUnseenTrackColor()); + graphic.drawLine(thumbX, thumbY + track.height / 2, track.x + track.width, track.y + track.height / 2); //Preserve the graphics color. - g.setColor(original); + graphic.setColor(original); } @Override @@ -728,6 +727,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie slider.setValueIsAdjusting(false); sliderLock.release(); } catch (InterruptedException ex) { + //Thread (EDT) interrupted while waiting on lock. } } @@ -735,14 +735,14 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie * Applies anti-aliasing if available. */ @Override - public void update(Graphics g, JComponent c) { - if (g instanceof Graphics2D) { - Graphics2D g2 = (Graphics2D) g; - g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + public void update(Graphics graphic, JComponent component) { + if (graphic instanceof Graphics2D) { + Graphics2D graphic2 = (Graphics2D) graphic; + graphic2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); } - super.update(g, c); + super.update(graphic, component); } /** From 756c82256f904996e7021d98359efa1ebb9892c5 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Tue, 22 Oct 2019 10:57:16 -0400 Subject: [PATCH 048/134] Paused the video when users click in the track. Unpause when mouse is released. This is the behavior of YouTube. It also keeps the video from ending during a drag. --- .../autopsy/contentviewers/MediaPlayerPanel.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java index 95634f0435..56feadbb82 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java @@ -757,10 +757,21 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie public void mousePressed(MouseEvent e) { //Snap the thumb to position of the mouse scrollDueToClickInTrack(0); + + //Pause the video for convenience + gstPlayBin.pause(); //Handle the event as normal. super.mousePressed(e); - } + } + + @Override + public void mouseReleased(MouseEvent e) { + super.mouseReleased(e); + + //Unpause once the mouse has been released. + gstPlayBin.play(); + } } } From f1b9d2ab85419a49379c572c564e05d07893086a Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Tue, 22 Oct 2019 11:08:10 -0400 Subject: [PATCH 049/134] Ran source formatting and disable track listening when the slider is not enabled. --- .../contentviewers/MediaPlayerPanel.java | 46 +++++++++++-------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java index 56feadbb82..966d5829e6 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java @@ -200,7 +200,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie private static final int SKIP_IN_SECONDS = 30; private ExtractMedia extractMediaWorker; - + //Serialize setting the value of the Video progress slider. //The slider is a shared resource between the VideoPanelUpdater //and the TrackListener of the JSliderUI. @@ -555,9 +555,10 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie long position = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS); /** - * Duration may not be known until there is video data in the - * pipeline. We start this updater when data-flow has just been - * initiated so buffering may still be in progress. + * Duration may not be known until there is video data in + * the pipeline. We start this updater when data-flow has + * just been initiated so buffering may still be in + * progress. */ if (duration >= 0 && position >= 0) { double relativePosition = (double) position / duration; @@ -639,8 +640,8 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie * the thumb progresses. * * @param b JSlider component - * @param config Configuration object. Contains info about thumb dimensions - * and colors. + * @param config Configuration object. Contains info about thumb + * dimensions and colors. */ public CircularJSliderUI(JSlider slider, CircularJSliderConfiguration config) { super(slider); @@ -653,8 +654,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie } /** - * Modifies the View to be an oval rather than the - * rectangle Controller. + * Modifies the View to be an oval rather than the rectangle Controller. */ @Override public void paintThumb(Graphics graphic) { @@ -700,7 +700,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie //Preserve the graphics color. graphic.setColor(original); } - + @Override protected TrackListener createTrackListener(JSlider slider) { return new CustomTrackListener(); @@ -716,7 +716,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie return; } int value = this.valueForXPosition(mousePosition.x); - + //Lock the slider down, which is a shared resource. //The VideoPanelUpdater (dedicated thread) keeps the //slider in sync with the video position, so without @@ -744,31 +744,39 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie super.update(graphic, component); } - + /** - * This track listener will force the thumb to be snapped to the - * mouse location. This makes grabbing and dragging the JSlider much - * easier. Using the default track listener, the user would have to - * click exactly on the slider thumb to drag it. Now the thumb positions + * This track listener will force the thumb to be snapped to the mouse + * location. This makes grabbing and dragging the JSlider much easier. + * Using the default track listener, the user would have to click + * exactly on the slider thumb to drag it. Now the thumb positions * itself under the mouse so that it can always be dragged. */ private class CustomTrackListener extends CircularJSliderUI.TrackListener { - @Override + + @Override public void mousePressed(MouseEvent e) { + if (!slider.isEnabled()) { + return; + } //Snap the thumb to position of the mouse scrollDueToClickInTrack(0); - + //Pause the video for convenience gstPlayBin.pause(); //Handle the event as normal. super.mousePressed(e); } - + @Override public void mouseReleased(MouseEvent e) { - super.mouseReleased(e); + if (!slider.isEnabled()) { + return; + } + super.mouseReleased(e); + //Unpause once the mouse has been released. gstPlayBin.play(); } From 30c18c15bfdb829a4c1807b37cd316afdde50029 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Tue, 22 Oct 2019 13:05:47 -0400 Subject: [PATCH 050/134] Added fairness to semaphore and changed the acquire to not be interruptable. --- .../contentviewers/MediaPlayerPanel.java | 82 +++++++++---------- 1 file changed, 38 insertions(+), 44 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java index 966d5829e6..1f02708f64 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java @@ -66,6 +66,7 @@ import javax.swing.JSlider; import javax.swing.SwingUtilities; import javax.swing.event.ChangeListener; import javax.swing.plaf.basic.BasicSliderUI; +import javax.swing.plaf.basic.BasicSliderUI.TrackListener; import org.freedesktop.gstreamer.ClockTime; import org.freedesktop.gstreamer.Format; import org.freedesktop.gstreamer.GstException; @@ -212,7 +213,9 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie public MediaPlayerPanel() throws GstException, UnsatisfiedLinkError { initComponents(); customizeComponents(); - sliderLock = new Semaphore(1); + //True for fairness. In other words, + //acquire() calls are processed in order of invocation. + sliderLock = new Semaphore(1, true); } private void customizeComponents() { @@ -549,29 +552,24 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie @Override public void actionPerformed(ActionEvent e) { - try { - if (!progressSlider.getValueIsAdjusting()) { - sliderLock.acquire(); - long position = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); - long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS); - /** - * Duration may not be known until there is video data in - * the pipeline. We start this updater when data-flow has - * just been initiated so buffering may still be in - * progress. - */ - if (duration >= 0 && position >= 0) { - double relativePosition = (double) position / duration; - progressSlider.setValue((int) (relativePosition * PROGRESS_SLIDER_SIZE)); - } - - SwingUtilities.invokeLater(() -> { - updateTimeLabel(position, duration); - }); - sliderLock.release(); + if (!progressSlider.getValueIsAdjusting()) { + sliderLock.acquireUninterruptibly(); + long position = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); + long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS); + /** + * Duration may not be known until there is video data in the + * pipeline. We start this updater when data-flow has just been + * initiated so buffering may still be in progress. + */ + if (duration >= 0 && position >= 0) { + double relativePosition = (double) position / duration; + progressSlider.setValue((int) (relativePosition * PROGRESS_SLIDER_SIZE)); } - } catch (InterruptedException ex) { - //Video panel thread interrupted while waiting on lock. + + SwingUtilities.invokeLater(() -> { + updateTimeLabel(position, duration); + }); + sliderLock.release(); } } } @@ -708,27 +706,23 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie @Override protected void scrollDueToClickInTrack(int direction) { - try { - //Set the thumb position to the mouse press location, as opposed - //to the closest "block" which is the default behavior. - Point mousePosition = slider.getMousePosition(); - if (mousePosition == null) { - return; - } - int value = this.valueForXPosition(mousePosition.x); - - //Lock the slider down, which is a shared resource. - //The VideoPanelUpdater (dedicated thread) keeps the - //slider in sync with the video position, so without - //proper locking our change could be overwritten. - sliderLock.acquire(); - slider.setValueIsAdjusting(true); - slider.setValue(value); - slider.setValueIsAdjusting(false); - sliderLock.release(); - } catch (InterruptedException ex) { - //Thread (EDT) interrupted while waiting on lock. + //Set the thumb position to the mouse press location, as opposed + //to the closest "block" which is the default behavior. + Point mousePosition = slider.getMousePosition(); + if (mousePosition == null) { + return; } + int value = this.valueForXPosition(mousePosition.x); + + //Lock the slider down, which is a shared resource. + //The VideoPanelUpdater (dedicated thread) keeps the + //slider in sync with the video position, so without + //proper locking our change could be overwritten. + sliderLock.acquireUninterruptibly(); + slider.setValueIsAdjusting(true); + slider.setValue(value); + slider.setValueIsAdjusting(false); + sliderLock.release(); } /** @@ -774,7 +768,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie if (!slider.isEnabled()) { return; } - + super.mouseReleased(e); //Unpause once the mouse has been released. From a0c34afa5ebe938ef1622b6a253ab238cda66ab5 Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Tue, 22 Oct 2019 14:06:39 -0400 Subject: [PATCH 051/134] Continued to clean up api --- .../datamodel/ArtifactWaypoint.java | 225 ++++++++++++++ .../geolocation/datamodel/AttributeUtils.java | 122 ++++++++ .../datamodel/BlackboardArtifactWaypoint.java | 282 ------------------ .../datamodel/Bundle.properties-MERGED | 6 +- .../geolocation/datamodel/EXIFWaypoint.java | 78 +++++ .../datamodel/GPSSearchWaypoint.java | 65 ++++ .../datamodel/GeolocationManager.java | 123 -------- .../datamodel/GeolocationUtils.java | 170 ++--------- .../datamodel/LastKnownWaypoint.java | 55 ++++ .../autopsy/geolocation/datamodel/Route.java | 50 ++-- .../geolocation/datamodel/RoutePoint.java | 6 +- .../datamodel/TrackpointWaypoint.java | 71 +++++ .../geolocation/datamodel/Waypoint.java | 226 ++++++++++++-- .../autopsy/report/modules/kml/KMLReport.java | 71 ++--- 14 files changed, 918 insertions(+), 632 deletions(-) create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactWaypoint.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/datamodel/AttributeUtils.java delete mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/datamodel/BlackboardArtifactWaypoint.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/datamodel/EXIFWaypoint.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GPSSearchWaypoint.java delete mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationManager.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/datamodel/LastKnownWaypoint.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/datamodel/TrackpointWaypoint.java diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactWaypoint.java new file mode 100755 index 0000000000..09fd01d5a1 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactWaypoint.java @@ -0,0 +1,225 @@ +/* + * 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.util.Collections; +import java.util.List; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Representation of a Waypoint created from a BlackboardArtifact. + * + */ +class ArtifactWaypoint implements Waypoint { + + final private Long timestamp; + final private Double longitude; + final private Double latitude; + final private Double altitude; + final private String label; + final private AbstractFile image; + final private BlackboardArtifact artifact; + final private Waypoint.Type type; + + // This list is not expected to change after construction so the + // constructor will take care of creating an unmodifiable List + final private List immutablePropertiesList; + + /** + * Construct a simple waypoint with the given artifact and assign the given + * type. + * + * This constructor is for use with artifacts that use the basic attributes + * of: TSK_NAME TSK_GEO_LONGITUDE TSK_GEO_LATITUDE TSK_GEO_ALITUDE + * TSK_DATETIME + * + * @param artifact BlackboardArtifact for this waypoint + * @param type Waypoint type + * + * @throws TskCoreException + */ + protected ArtifactWaypoint(BlackboardArtifact artifact, Waypoint.Type type) throws TskCoreException { + this(artifact, + getLabelFromArtifact(artifact), + type); + } + + /** + * For use by subclasses that want to customize the label, but use the basic + * attributes of: TSK_GEO_LONGITUDE TSK_GEO_LATITUDE TSK_GEO_ALITUDE + * TSK_DATETIME + * + * @param artifact BlackboardArtifact for this waypoint + * @param label String label for this waypoint + * @param type Waypoint type + * + * @throws TskCoreException + */ + protected ArtifactWaypoint(BlackboardArtifact artifact, String label, Waypoint.Type type) throws TskCoreException { + this(artifact, + label, + getTimestampFromArtifact(artifact), + null, + type); + } + + /** + * Constructor for use by Waypoint subclasses that want to customize the + * label, specify the timestamp or supply and image. + * + * Uses the following attributes to set longitude, latitude, altitude: + * TSK_GEO_LONGITUDE TSK_GEO_LATITUDE TSK_GEO_ALITUDE + * + * @param artifact BlackboardArtifact for this waypoint + * @param label String waypoint label + * @param timestamp Long timestamp, epoch seconds + * @param image AbstractFile image for waypoint, this maybe null + * @param type Waypoint.Type value for waypoint + * + * @throws TskCoreException + */ + protected ArtifactWaypoint(BlackboardArtifact artifact, String label, Long timestamp, AbstractFile image, Waypoint.Type type) throws TskCoreException { + this(artifact, + label, + timestamp, + AttributeUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE), + AttributeUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE), + AttributeUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE), + image, + type); + } + + /** + * Private constructor that sets all of the member variables. + * + * @param artifact BlackboardArtifact for this waypoint + * @param label String waypoint label + * @param timestamp Long timestamp, epoch seconds + * @param latitude Double waypoint latitude + * @param longitude Double waypoint longitude + * @param altitude Double waypoint altitude + * @param image AbstractFile image for waypoint, this maybe null + * @param type Waypoint.Type value for waypoint + * + * @throws TskCoreException + */ + private ArtifactWaypoint(BlackboardArtifact artifact, String label, Long timestamp, Double latitude, Double longitude, Double altitude, AbstractFile image, Waypoint.Type type) throws TskCoreException { + this.artifact = artifact; + this.label = label; + this.type = type; + this.image = image; + this.timestamp = timestamp; + this.longitude = longitude; + this.latitude = latitude; + this.altitude = altitude; + + immutablePropertiesList = Collections.unmodifiableList(GeolocationUtils.getOtherGeolocationProperties(artifact)); + } + + /** + * Get the BlackboardArtifact that this waypoint represents. + * + * @return BlackboardArtifact for this waypoint. + */ + BlackboardArtifact getArtifact() { + return artifact; + } + + @Override + public Long getTimestamp() { + return timestamp; + } + + @Override + public String getLabel() { + return label; + } + + @Override + public Double getLatitude() { + return latitude; + } + + @Override + public Double getLongitude() { + return longitude; + } + + @Override + public Double getAltitude() { + return altitude; + } + + @Override + public AbstractFile getImage() { + return image; + } + + @Override + public Waypoint.Type getType() { + return type; + } + + @Override + public List getOtherProperties() { + return immutablePropertiesList; + } + + /** + * Get the timestamp attribute based on type for the given artifact. + * + * @param artifact BlackboardArtifact for waypoint + * + * @return Long timestamp or null if a value was not found. + * + * @throws TskCoreException + */ + private static Long getTimestampFromArtifact(BlackboardArtifact artifact) throws TskCoreException { + if (artifact == null) { + return null; + } + + return AttributeUtils.getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME); + } + + /** + * Gets the label for this waypoint based on the artifact type. + * + * This is the original waypoint naming code from the KML report, we may + * what to thinki about better ways to name some of the point. + * + * @param artifact BlackboardArtifact for waypoint + * + * @return Returns a label for the waypoint based on artifact type, or empty + * string if no label was found. + * + * @throws TskCoreException + */ + private static String getLabelFromArtifact(BlackboardArtifact artifact) throws TskCoreException { + + String typeLabel = AttributeUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); + if (typeLabel == null) { + typeLabel = ""; + } + return typeLabel; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/AttributeUtils.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/AttributeUtils.java new file mode 100755 index 0000000000..00ca090bee --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/AttributeUtils.java @@ -0,0 +1,122 @@ +/* + * 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 org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Utilities for simplifying and reducing redundant when getting Artifact + * attributes. + */ +public class AttributeUtils { + + /** + * Helper function for getting a String attribute from an artifact. This + * will work for all attributes + * + * @param artifact The BlackboardArtifact to get the attributeType + * @param attributeType BlackboardAttribute attributeType + * + * @return String value for the given attribute or null if attribute was not + * set for the given artifact + * + * @throws TskCoreException + */ + static String getString(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) throws TskCoreException { + if (artifact == null) { + return null; + } + + BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(attributeType)); + return (attribute != null ? attribute.getDisplayString() : null); + } + + /** + * Helper function for getting a Double attribute from an artifact. + * + * @param artifact The BlackboardArtifact to get the attributeType + * @param attributeType BlackboardAttribute attributeType + * + * @return Double value for the given attribute. + * + * @throws TskCoreException + */ + static Double getDouble(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) throws TskCoreException { + if (artifact == null) { + return null; + } + + if (attributeType.getValueType() != BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DOUBLE) { + return null; + } + + BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(attributeType)); + return (attribute != null ? attribute.getValueDouble() : null); + } + + /** + * Helper function for getting a Long attribute from an artifact. + * + * @param artifact The BlackboardArtifact to get the attributeType + * @param attributeType BlackboardAttribute attributeType + * + * @return Long value for the given attribute. + * + * @throws TskCoreException + */ + static Long getLong(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) throws TskCoreException { + if (artifact == null) { + return null; + } + + if (attributeType.getValueType() != BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.LONG + || attributeType.getValueType() != BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME) { + return null; + } + + BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(attributeType)); + return (attribute != null ? attribute.getValueLong() : null); + } + + /** + * Helper function for getting a Integer attribute from an artifact. + * + * @param artifact The BlackboardArtifact to get the attributeType + * @param attributeType BlackboardAttribute attributeType + * + * @return Integer value for the given attribute. + * + * @throws TskCoreException + */ + static Integer getInteger(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) throws TskCoreException { + if (artifact == null) { + return null; + } + + if (attributeType.getValueType() != BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.INTEGER) { + return null; + } + + BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(attributeType)); + return (attribute != null ? attribute.getValueInt() : null); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/BlackboardArtifactWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/BlackboardArtifactWaypoint.java deleted file mode 100755 index 3487c61aba..0000000000 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/BlackboardArtifactWaypoint.java +++ /dev/null @@ -1,282 +0,0 @@ -/* - * 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.util.Map; -import org.openide.util.NbBundle.Messages; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * Representation of a Waypoint created from a BlackboardArtifact. - * - */ -public class BlackboardArtifactWaypoint implements Waypoint { - - final private Long timestamp; - final private Double longitude; - final private Double latitude; - final private Double altitude; - final private String label; - final private AbstractFile image; - final private BlackboardArtifact artifact; - final private Waypoint.Type type; - final private Map otherAttributesMap; - - @Messages({ - "BlackboardArtifactWaypoint_Last_Known_Label=Last Known Location", - "BlackboardArtifactWaypoint_GPS_Trackpoint=GPS Trackpoint", - "BlackboardArtifactWaypoint_GPS_Search=GPS Search" - }) - - /** - * Constructs a Waypoint from a BlackboardArtifact - * - * @param artifact BlackboardArtifact with which to create the waypoint - * - * @throws TskCoreException - */ - protected BlackboardArtifactWaypoint(BlackboardArtifact artifact) throws TskCoreException { - this.artifact = artifact; - timestamp = getTimestampFromArtifact(artifact); - longitude = GeolocationUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE); - latitude = GeolocationUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE); - altitude = GeolocationUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE); - image = getImageFromArtifact(artifact); - label = getLabelFromArtifact(artifact); - type = getTypeFromArtifact(artifact); - otherAttributesMap = GeolocationUtils.getOtherGeolocationAttributes(artifact); - } - - /** - * Get the BlackboardArtifact that this waypoint represents. - * - * @return BlackboardArtifact for this waypoint. - */ - BlackboardArtifact getArtifact() { - return artifact; - } - - @Override - public Long getTimestamp() { - return timestamp; - } - - @Override - public String getLabel() { - return label; - } - - @Override - public Double getLatitude() { - return latitude; - } - - @Override - public Double getLongitude() { - return longitude; - } - - @Override - public Double getAltitude() { - return altitude; - } - - @Override - public AbstractFile getImage() { - return image; - } - - @Override - public Waypoint.Type getType() { - return type; - } - - @Override - public Map getOtherProperties() { - return otherAttributesMap; - } - - /** - * Get the timestamp attribute based on type for the given artifact. - * - * @param artifact BlackboardArtifact for waypoint - * - * @return Long timestamp or null if a value was not found. - * - * @throws TskCoreException - */ - private Long getTimestampFromArtifact(BlackboardArtifact artifact) throws TskCoreException { - if (artifact == null) { - return null; - } - - BlackboardArtifact.ARTIFACT_TYPE artifactType = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID()); - - if (artifactType == BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF) { - return GeolocationUtils.getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED); - } - - return GeolocationUtils.getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME); - } - - /** - * Gets the image from the given artifact, this is really only applicable to - * the artifact type TSK_METADATA_EXIF - * - * @param artifact BlackboardArtifact for waypoint - * - * @return AbstractFile image for this waypoint or null if one is not - * available - * - * @throws TskCoreException - */ - private AbstractFile getImageFromArtifact(BlackboardArtifact artifact) throws TskCoreException { - BlackboardArtifact.ARTIFACT_TYPE artifactType = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID()); - - if (artifactType == BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF) { - return artifact.getSleuthkitCase().getAbstractFileById(artifact.getObjectID()); - } - - return null; - } - - /** - * Gets the label for this waypoint based on the artifact type. - * - * This is the original waypoint naming code from the KML report, we may - * what to thinki about better ways to name some of the point. - * - * @param artifact BlackboardArtifact for waypoint - * - * @return Returns a label for the waypoint based on artifact type, or empty - * string if no label was found. - * - * @throws TskCoreException - */ - private String getLabelFromArtifact(BlackboardArtifact artifact) throws TskCoreException { - String typeLabel = getLabelFromImage(artifact); - if (typeLabel != null && !typeLabel.isEmpty()) { - return typeLabel; - } - - BlackboardArtifact.ARTIFACT_TYPE artifactType = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID()); - typeLabel = GeolocationUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); - if (typeLabel == null || typeLabel.isEmpty()) { - switch (artifactType) { - case TSK_GPS_SEARCH: - typeLabel = getLabelForSearch(artifact); - break; - case TSK_GPS_TRACKPOINT: - typeLabel = getLabelforTrackpoint(artifact); - break; - case TSK_GPS_LAST_KNOWN_LOCATION: - typeLabel = Bundle.BlackboardArtifactWaypoint_Last_Known_Label(); - break; - default: - typeLabel = ""; - break; - } - } - return typeLabel; - } - - /** - * Returns a Label for a GPS_Trackpoint artifact. This function assumes the - * calling function has already checked TSK_NAME. - * - * @param artifact BlackboardArtifact for waypoint - * - * @return String label for the artifacts way point. - * - * @throws TskCoreException - */ - private String getLabelforTrackpoint(BlackboardArtifact artifact) throws TskCoreException { - String typeLabel = GeolocationUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME); - if (typeLabel == null || typeLabel.isEmpty()) { - typeLabel = GeolocationUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FLAG); - } - - if (typeLabel == null || typeLabel.isEmpty()) { - typeLabel = Bundle.BlackboardArtifactWaypoint_GPS_Trackpoint(); - } - - return typeLabel; - } - - /** - * Returns a Label for a GPS_SEARCH artifact. This function assumes the - * calling function has already checked TSK_NAME. - * - * @param artifact BlackboardArtifact for waypoint - * - * @return String label for the artifacts way point. - * - * @throws TskCoreException - */ - private String getLabelForSearch(BlackboardArtifact artifact) throws TskCoreException { - String typeLabel = GeolocationUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION); - if (typeLabel == null || typeLabel.isEmpty()) { - typeLabel = Bundle.BlackboardArtifactWaypoint_GPS_Search(); - } - - return typeLabel; - } - - /** - * Returns a Label from an image. - * - * @return String label for waypoint with image or null. - */ - private String getLabelFromImage(BlackboardArtifact artifact) throws TskCoreException{ - if (getImageFromArtifact(artifact) != null) { - return getImage().getName(); - } else { - return null; - } - } - - /** - * Gets the type of waypoint based on the artifact. - * - * @param artifact BlackboardArtifact for waypoint - * - * @return A waypoint type. - */ - private Waypoint.Type getTypeFromArtifact(BlackboardArtifact artifact) { - BlackboardArtifact.ARTIFACT_TYPE artifactType = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID()); - - switch (artifactType) { - case TSK_GPS_LAST_KNOWN_LOCATION: - return Waypoint.Type.LAST_KNOWN_LOCATION; - case TSK_GPS_TRACKPOINT: - return Waypoint.Type.TRACKPOINT; - case TSK_GPS_SEARCH: - return Waypoint.Type.SEARCH; - case TSK_GPS_BOOKMARK: - return Waypoint.Type.BOOKMARK; - case TSK_METADATA_EXIF: - return Waypoint.Type.METADATA_EXIF; - default: - return Waypoint.Type.UNKNOWN; - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Bundle.properties-MERGED index 33e0ff3247..60345f2e57 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Bundle.properties-MERGED @@ -1,9 +1,9 @@ -BlackboardArtifactWaypoint_GPS_Search=GPS Search -BlackboardArtifactWaypoint_GPS_Trackpoint=GPS Trackpoint -BlackboardArtifactWaypoint_Last_Known_Label=Last Known Location +LastKnownWaypoint_Label=Last Known Location Route_End_Label=End Route_Label=As-the-crow-flies Route Route_Start_Label=Start +SearchWaypoint_DisplayLabel=GPS Search +TrackpointWaypoint_DisplayLabel=GPS Trackpoint Waypoint_Bookmark_Display_String=GPS Bookmark Waypoint_EXIF_Display_String=EXIF Metadata With Location Waypoint_Last_Known_Display_String=GPS Last Known Location diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/EXIFWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/EXIFWaypoint.java new file mode 100755 index 0000000000..37a1a2c7e2 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/EXIFWaypoint.java @@ -0,0 +1,78 @@ +/* + * 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 org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Waypoint wrapper class for TSK_METADATA_EXIF artifacts. + */ +final class EXIFWaypoint extends ArtifactWaypoint { + + /** + * Construct a way point with the given artifact. + * + * @param artifact BlackboardArtifact for waypoint + * + * @throws TskCoreException + */ + protected EXIFWaypoint(BlackboardArtifact artifact) throws TskCoreException { + this(artifact, getImageFromArtifact(artifact)); + } + + /** + * Private constructor to help with the construction of EXIFWaypoints. + * + * @param artifact Waypoint BlackboardArtifact + * @param image EXIF AbstractFile image + * + * @throws TskCoreException + */ + private EXIFWaypoint(BlackboardArtifact artifact, AbstractFile image) throws TskCoreException { + super(artifact, + image != null ? image.getName() : "", + AttributeUtils.getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED), + image, + Waypoint.Type.METADATA_EXIF); + } + + /** + * Gets the image from the given artifact. + * + * @param artifact BlackboardArtifact for waypoint + * + * @return AbstractFile image for this waypoint or null if one is not + * available + * + * @throws TskCoreException + */ + private static AbstractFile getImageFromArtifact(BlackboardArtifact artifact) throws TskCoreException { + BlackboardArtifact.ARTIFACT_TYPE artifactType = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID()); + + if (artifactType == BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF) { + return artifact.getSleuthkitCase().getAbstractFileById(artifact.getObjectID()); + } + + return null; + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GPSSearchWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GPSSearchWaypoint.java new file mode 100755 index 0000000000..28bd7127a0 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GPSSearchWaypoint.java @@ -0,0 +1,65 @@ +/* + * 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 org.openide.util.NbBundle.Messages; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * A GPSSearchWaypoint is a subclass of ArtifactWaypoint. + */ +final class GPSSearchWaypoint extends ArtifactWaypoint { + + @Messages({ + "SearchWaypoint_DisplayLabel=GPS Search" + }) + + /** + * Construct a GPS Search waypoint. + */ + public GPSSearchWaypoint(BlackboardArtifact artifact) throws TskCoreException { + super(artifact, getLabelFromArtifact(artifact), Waypoint.Type.SEARCH); + } + + /** + * Returns a Label for a GPS_SEARCH artifact. + * + * @param artifact BlackboardArtifact for waypoint + * + * @return String label for the artifacts way point. + * + * @throws TskCoreException + */ + private static String getLabelFromArtifact(BlackboardArtifact artifact) throws TskCoreException { + String typeLabel = AttributeUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); + + if (typeLabel == null || typeLabel.isEmpty()) { + typeLabel = AttributeUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION); + } + + if (typeLabel == null || typeLabel.isEmpty()) { + typeLabel = Bundle.SearchWaypoint_DisplayLabel(); + } + + return typeLabel; + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationManager.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationManager.java deleted file mode 100755 index a6a882caa9..0000000000 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationManager.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * - * 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.util.ArrayList; -import java.util.List; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * Static functions for the creations of geolocation points for artifacts. - * - */ -public final class GeolocationManager { - - /** - * Add a private constructor to silence codacy warning about making this - * class a utility class as I suspect this class may grow when filtering is - * added. - */ - private GeolocationManager() { - - } - - /** - * 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 - * @param includeRoutes True to include the points at are in TSK_GPS_ROUTE - * objects - * - * @return List of BlackboardArtifactPoints - * - * @throws TskCoreException - */ - static public List getWaypoints(SleuthkitCase skCase) throws TskCoreException { - List points = new ArrayList<>(); - - points.addAll(getBasicPoints(skCase)); - - return points; - } - - /** - * Gets the list of Routes from the TSK_GPS_ROUTE artifacts. - * - * @param skCase Currently open SleuthkitCase - * - * @return List of Route objects, empty list will be returned if no Routes - * where found - * - * @throws TskCoreException - */ - static public List getGPSRoutes(SleuthkitCase skCase) throws TskCoreException { - List routes = new ArrayList<>(); - List artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE); - for (BlackboardArtifact artifact : artifacts) { - Route route = new Route(artifact); - routes.add(route); - } - return routes; - } - - /** - * Get a list of Waypoints for the GPS artifacts. Artifacts that will be - * included: TSK_GPS_TRACKPOINT TSK_GPS_SEARCH TSK_GPS_LAST_KNOWN_LOCATION - * TSK_GPS_BOOKMARK - * - * Waypoint objects will be created and added to the list only for artifacts - * with TSK_GEO_LONGITUDE and TSK_LATITUDE attributes. - * - * @param skCase Currently open SleuthkitCase - * - * @return List of BlackboardArtifactPoints for above artifacts or empty - * list if none where found. - * - * @throws TskCoreException - */ - static private List getBasicPoints(SleuthkitCase skCase) throws TskCoreException { - - List points = new ArrayList<>(); - - List artifacts = new ArrayList<>(); - artifacts.addAll(skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT)); - artifacts.addAll(skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH)); - artifacts.addAll(skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION)); - artifacts.addAll(skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK)); - artifacts.addAll(skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF)); - - for (BlackboardArtifact artifact : artifacts) { - BlackboardArtifactWaypoint point = new BlackboardArtifactWaypoint(artifact); - // Only add to the list if the point has a valid latitude - // and longitude. - if (point.getLatitude() != null && point.getLongitude() != null) { - points.add(point); - } - } - - return points; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtils.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtils.java index ee8d917fce..19e42c5707 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtils.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtils.java @@ -1,23 +1,34 @@ /* - * 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.datamodel; -import java.util.HashMap; -import java.util.Map; +import java.util.ArrayList; +import java.util.List; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.TskCoreException; /** - * + * GeolocationUtilis class for common to be share across in the package * */ -public final class GeolocationUtils { - - private static final String DEFAULT_COORD_FORMAT = "%.2f, %.2f"; +final class GeolocationUtils { /** * This is a list of attributes that are related to a geolocation waypoint @@ -40,160 +51,37 @@ public final class GeolocationUtils { BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_MAKE, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_MODEL }; - + /** * This is a Utility class that should not be constructed. */ private GeolocationUtils() { - - } - static public String getFormattedCoordinates(Double latitude, Double longitude) { - if (latitude == null || longitude == null) { - return ""; - } - - return String.format(DEFAULT_COORD_FORMAT, latitude, longitude); } /** - * Helper function for getting a String attribute from an artifact. This - * will work for all attributes + * Get a list of Waypoint.Property objects for the given artifact. * - * @param artifact The BlackboardArtifact to get the attributeType - * @param attributeType BlackboardAttribute attributeType + * @param artifact Blackboard artifact to get attributes\properties from * - * @return String value for the given attribute or null if attribute was not - * set for the given artifact + * @return A List of Waypoint.Property objects * * @throws TskCoreException */ - static String getString(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) throws TskCoreException { - if (artifact == null) { - return null; - } - - BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(attributeType)); - return (attribute != null ? attribute.getDisplayString() : null); - } - - /** - * Helper function for getting a Double attribute from an artifact. - * - * @param artifact The BlackboardArtifact to get the attributeType - * @param attributeType BlackboardAttribute attributeType - * - * @return Double value for the given attribute. - * - * @throws TskCoreException - */ - static Double getDouble(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) throws TskCoreException { - if (artifact == null) { - return null; - } - - if (attributeType.getValueType() != BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DOUBLE) { - return null; - } - - BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(attributeType)); - return (attribute != null ? attribute.getValueDouble() : null); - } - - /** - * Helper function for getting a Long attribute from an artifact. - * - * @param artifact The BlackboardArtifact to get the attributeType - * @param attributeType BlackboardAttribute attributeType - * - * @return Long value for the given attribute. - * - * @throws TskCoreException - */ - static Long getLong(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) throws TskCoreException { - if (artifact == null) { - return null; - } - - if (attributeType.getValueType() != BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.LONG - || attributeType.getValueType() != BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME) { - return null; - } - - BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(attributeType)); - return (attribute != null ? attribute.getValueLong() : null); - } - - /** - * Helper function for getting a Integer attribute from an artifact. - * - * @param artifact The BlackboardArtifact to get the attributeType - * @param attributeType BlackboardAttribute attributeType - * - * @return Integer value for the given attribute. - * - * @throws TskCoreException - */ - static Integer getInteger(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) throws TskCoreException { - if (artifact == null) { - return null; - } - - if (attributeType.getValueType() != BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.INTEGER) { - return null; - } - - BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(attributeType)); - return (attribute != null ? attribute.getValueInt() : null); - } - - /** - * Helper function for consistently formatting the timestamp. - * - * @param type BlackboardAttribute type - * - * @return The timestamp value formatted as string, or empty string if no - * timestamp is available. - * - * @throws TskCoreException - */ - static String getFormattedTimestamp(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) throws TskCoreException { - if (artifact == null) { - return null; - } - - if (attributeType.getValueType() != BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME) { - return null; - } - - BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(attributeType)); - return (attribute != null ? attribute.getDisplayString() : null); - } - - /** - * Returns a Map of formatted artifact attributes for geolocation artifacts. - * The map key will be the display string off the attribute, the value will - * be either a formatted value or a an empty string. - * - * @param artifact - * - * @return - */ - static Map getOtherGeolocationAttributes(BlackboardArtifact artifact) throws TskCoreException { - Map map = new HashMap<>(); + static List getOtherGeolocationProperties(BlackboardArtifact artifact) throws TskCoreException { + List list = new ArrayList<>(); for (BlackboardAttribute.ATTRIBUTE_TYPE type : OTHER_GEO_ATTRIBUTES) { String key = type.getDisplayName(); - String value = getString(artifact, type); + String value = AttributeUtils.getString(artifact, type); if (value == null) { value = ""; } - map.put(key, value); + list.add(new Waypoint.Property(key, value)); } - return map; + return list; } - } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/LastKnownWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/LastKnownWaypoint.java new file mode 100755 index 0000000000..6ef47f76a2 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/LastKnownWaypoint.java @@ -0,0 +1,55 @@ +/* + * 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 org.openide.util.NbBundle.Messages; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * A Last Know Location Waypoint object. + */ +@Messages({ + "LastKnownWaypoint_Label=Last Known Location",}) +final class LastKnownWaypoint extends ArtifactWaypoint { + + protected LastKnownWaypoint(BlackboardArtifact artifact) throws TskCoreException { + super(artifact, getLabelFromArtifact(artifact), Waypoint.Type.LAST_KNOWN_LOCATION); + } + + /** + * Gets the label for a TSK_LAST_KNOWN_LOCATION. + * + * @param artifact BlackboardArtifact to get label from + * + * @return String value from attribute TSK_NAME or LastKnownWaypoint_Label + * + * @throws TskCoreException + */ + private static String getLabelFromArtifact(BlackboardArtifact artifact) throws TskCoreException { + String label = AttributeUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); + + if (label == null || label.isEmpty()) { + label = Bundle.LastKnownWaypoint_Label(); + } + + return label; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java index 68d93509d5..4eefc72bca 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java @@ -20,11 +20,12 @@ package org.sleuthkit.autopsy.geolocation.datamodel; import java.util.ArrayList; +import java.util.Collections; import java.util.List; -import java.util.Map; import org.openide.util.NbBundle.Messages; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; /** @@ -43,7 +44,30 @@ public class Route { private final List points; private final Long timestamp; private final Double altitude; - private final Map otherAttributesMap; + + // This list is not expected to change after construction so the + // constructor will take care of creating an unmodifiable List + private final List immutablePropertiesList; + + /** + * Gets the list of Routes from the TSK_GPS_ROUTE artifacts. + * + * @param skCase Currently open SleuthkitCase + * + * @return List of Route objects, empty list will be returned if no Routes + * where found + * + * @throws TskCoreException + */ + static public List getGPSRoutes(SleuthkitCase skCase) throws TskCoreException { + List routes = new ArrayList<>(); + List artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE); + for (BlackboardArtifact artifact : artifacts) { + Route route = new Route(artifact); + routes.add(route); + } + return routes; + } /** * Construct a route for the given artifact. @@ -66,7 +90,7 @@ public class Route { altitude = getRouteAltitude(artifact); timestamp = getRouteTimestamp(artifact); - otherAttributesMap = initalizeOtherAttributes(artifact); + immutablePropertiesList = Collections.unmodifiableList(GeolocationUtils.getOtherGeolocationProperties(artifact)); } /** @@ -98,12 +122,12 @@ public class Route { /** * Get the "Other attributes" for this route. The map will contain display - * name, formatted value pairs. + * name, formatted value pairs. This list is unmodifiable. * * @return Map of key, value pairs. */ - public Map getOtherProperties() { - return otherAttributesMap; + public List getOtherProperties() { + return immutablePropertiesList; } /** @@ -206,18 +230,4 @@ public class Route { return attribute != null ? attribute.getValueLong() : null; } - /** - * Retrieve the "Other attributes" for this route. The map will contain - * display name, formatted value pairs. - * - * @param artifact The BlackboardARtifact object from which this route is - * created - * - * @return A Map of other attributes for this route. - * - * @throws TskCoreException - */ - private Map initalizeOtherAttributes(BlackboardArtifact artifact) throws TskCoreException { - return GeolocationUtils.getOtherGeolocationAttributes(artifact); - } } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/RoutePoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/RoutePoint.java index 23cc4b8198..25eac21422 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/RoutePoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/RoutePoint.java @@ -19,14 +19,14 @@ */ package org.sleuthkit.autopsy.geolocation.datamodel; -import java.util.Map; +import java.util.List; import org.sleuthkit.datamodel.AbstractFile; /** * A point in a Route. For future use this point will have a pointer to its * parent route. */ -public class RoutePoint implements Waypoint { +final class RoutePoint implements Waypoint { private final Route parent; private final Double longitude; @@ -74,7 +74,7 @@ public class RoutePoint implements Waypoint { } @Override - public Map getOtherProperties() { + public List getOtherProperties() { return parent.getOtherProperties(); } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/TrackpointWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/TrackpointWaypoint.java new file mode 100755 index 0000000000..b4b1205c77 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/TrackpointWaypoint.java @@ -0,0 +1,71 @@ +/* + * 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 org.openide.util.NbBundle.Messages; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * A wrapper class for TSK_GPS_TRACKPOINT artifacts. + */ +final class TrackpointWaypoint extends ArtifactWaypoint { + + @Messages({ + "TrackpointWaypoint_DisplayLabel=GPS Trackpoint" + }) + + /** + * Construct a waypoint for trackpoints. + */ + TrackpointWaypoint(BlackboardArtifact artifact) throws TskCoreException { + super(artifact, getLabelFromArtifact(artifact), Waypoint.Type.TRACKPOINT); + } + + /** + * Returns a Label for a GPS_Trackpoint artifact. This function assumes the + * calling function has already checked TSK_NAME. + * + * @param artifact BlackboardArtifact for waypoint + * + * @return String label for the artifacts way point. + * + * @throws TskCoreException + */ + private static String getLabelFromArtifact(BlackboardArtifact artifact) throws TskCoreException { + + String typeLabel = AttributeUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); + + if (typeLabel == null || typeLabel.isEmpty()) { + typeLabel = AttributeUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME); + } + + if (typeLabel == null || typeLabel.isEmpty()) { + typeLabel = AttributeUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FLAG); + } + + if (typeLabel == null || typeLabel.isEmpty()) { + typeLabel = Bundle.TrackpointWaypoint_DisplayLabel(); + } + + return typeLabel; + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java index edd3257391..2bf606b18b 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java @@ -18,9 +18,13 @@ */ package org.sleuthkit.autopsy.geolocation.datamodel; -import java.util.Map; +import java.util.ArrayList; +import java.util.List; import org.openide.util.NbBundle.Messages; import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; /** * The basic details of a waypoint. @@ -28,6 +32,16 @@ import org.sleuthkit.datamodel.AbstractFile; */ public interface Waypoint { + // Display names are from the original KML Report + @Messages({ + "Waypoint_Bookmark_Display_String=GPS Bookmark", + "Waypoint_Last_Known_Display_String=GPS Last Known Location", + "Waypoint_EXIF_Display_String=EXIF Metadata With Location", + "Waypoint_Route_Point_Display_String=GPS Individual Route Point", + "Waypoint_Search_Display_String=GPS Search", + "Waypoint_Trackpoint_Display_String=GPS Trackpoint" + }) + /** * Get the timestamp for this BlackboardArtifact. * @@ -64,16 +78,13 @@ public interface Waypoint { Double getAltitude(); /** - * Gets a Map of other properties that may be interesting to this way point. - * This map will not include properties for which there are getter functions + * Gets an unmodifiable List of other properties that may be interesting to this way point. + * The List will not include properties for which there are getter functions * for. * - * The key is a "Display String", the value will be either an empty string - * or the formatted value. - * - * @return A Map of waypoint properties + * @return A List of waypoint properties */ - Map getOtherProperties(); + List getOtherProperties(); /** * Get the image for this waypoint. @@ -89,15 +100,153 @@ public interface Waypoint { */ Type getType(); - // Display names are from the original KML Report - @Messages({ - "Waypoint_Bookmark_Display_String=GPS Bookmark", - "Waypoint_Last_Known_Display_String=GPS Last Known Location", - "Waypoint_EXIF_Display_String=EXIF Metadata With Location", - "Waypoint_Route_Point_Display_String=GPS Individual Route Point", - "Waypoint_Search_Display_String=GPS Search", - "Waypoint_Trackpoint_Display_String=GPS Trackpoint" - }) + /** + * 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 TskCoreException + */ + static List getAllWaypoints(SleuthkitCase skCase) throws TskCoreException { + 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 TskCoreException + */ + static List getTrackpointWaypoints(SleuthkitCase skCase) throws TskCoreException { + List points = new ArrayList<>(); + List artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT); + for (BlackboardArtifact artifact : artifacts) { + ArtifactWaypoint point = new TrackpointWaypoint(artifact); + // Only add to the list if the point has a valid latitude + // and longitude. + if (point.getLatitude() != null && point.getLongitude() != null) { + points.add(point); + } + } + return points; + } + + /** + * Gets a list of Waypoints for TSK_METADATA_EXIF artifacts. + * + * @param skCase Currently open SleuthkitCase + * + * @return List of Waypoint + * + * @throws TskCoreException + */ + static List getEXIFWaypoints(SleuthkitCase skCase) throws TskCoreException { + List points = new ArrayList<>(); + List artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF); + if (artifacts != null) { + for (BlackboardArtifact artifact : artifacts) { + ArtifactWaypoint point = new EXIFWaypoint(artifact); + // Only add to the list if the point has a valid latitude + // and longitude. + if (point.getLatitude() != null && point.getLongitude() != null) { + points.add(point); + } + } + } + return points; + } + + /** + * Gets a list of Waypoints for TSK_GPS_SEARCH artifacts. + * + * @param skCase Currently open SleuthkitCase + * + * @return List of Waypoint + * + * @throws TskCoreException + */ + static List getSearchWaypoints(SleuthkitCase skCase) throws TskCoreException { + List points = new ArrayList<>(); + List artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH); + if (artifacts != null) { + for (BlackboardArtifact artifact : artifacts) { + ArtifactWaypoint point = new GPSSearchWaypoint(artifact); + // Only add to the list if the point has a valid latitude + // and longitude. + if (point.getLatitude() != null && point.getLongitude() != null) { + points.add(point); + } + } + } + return points; + } + + /** + * Gets a list of Waypoints for TSK_GPS_LAST_KNOWN_LOCATION artifacts. + * + * @param skCase Currently open SleuthkitCase + * + * @return List of Waypoint + * + * @throws TskCoreException + */ + static List getLastKnownWaypoints(SleuthkitCase skCase) throws TskCoreException { + List points = new ArrayList<>(); + List artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION); + if (artifacts != null) { + for (BlackboardArtifact artifact : artifacts) { + ArtifactWaypoint point = new LastKnownWaypoint(artifact); + // Only add to the list if the point has a valid latitude + // and longitude. + if (point.getLatitude() != null && point.getLongitude() != null) { + points.add(point); + } + } + } + return points; + } + + /** + * Gets a list of Waypoints for TSK_GPS_BOOKMARK artifacts. + * + * @param skCase Currently open SleuthkitCase + * + * @return List of Waypoint + * + * @throws TskCoreException + */ + static List getBookmarkWaypoints(SleuthkitCase skCase) throws TskCoreException { + List points = new ArrayList<>(); + List artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK); + if (artifacts != null) { + for (BlackboardArtifact artifact : artifacts) { + ArtifactWaypoint point = new ArtifactWaypoint(artifact, Waypoint.Type.BOOKMARK); + // Only add to the list if the point has a valid latitude + // and longitude. + if (point.getLatitude() != null && point.getLongitude() != null) { + points.add(point); + } + } + } + return points; + } /** * An enum to keep track of the type of a way point. @@ -108,8 +257,7 @@ public interface Waypoint { METADATA_EXIF(Bundle.Waypoint_EXIF_Display_String()), ROUTE_POINT(Bundle.Waypoint_Route_Point_Display_String()), SEARCH(Bundle.Waypoint_Search_Display_String()), - TRACKPOINT(Bundle.Waypoint_Trackpoint_Display_String()), - UNKNOWN("Unknown"); + TRACKPOINT(Bundle.Waypoint_Trackpoint_Display_String()); private final String displayName; @@ -131,4 +279,44 @@ public interface Waypoint { return displayName; } } + + /** + * Simple property class for waypoint properties that a purely + * informational. + */ + class Property { + + private final String displayName; + private final String value; + + /** + * Construct a Property object. + * + * @param displayName String display name for property. Ideally not null + * or empty string. + * @param value String value for property. Can be null. + */ + Property(String displayName, String value) { + this.displayName = displayName; + this.value = value; + } + + /** + * Get the display name for this property. + * + * @return String display name. + */ + public String getDisplayName() { + return displayName; + } + + /** + * Get the property value. + * + * @return String value. + */ + public String getValue() { + return value; + } + } } 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 175819610f..4a5afdc200 100644 --- a/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java +++ b/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java @@ -36,9 +36,6 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.text.SimpleDateFormat; import java.util.List; -import java.util.Map; -import java.util.Iterator; -import java.util.Map.Entry; import java.util.logging.Level; import org.jdom2.Document; import org.jdom2.Element; @@ -49,8 +46,6 @@ import org.jdom2.CDATA; import org.openide.filesystems.FileUtil; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint; -import org.sleuthkit.autopsy.geolocation.datamodel.GeolocationManager; -import org.sleuthkit.autopsy.geolocation.datamodel.GeolocationUtils; import org.sleuthkit.autopsy.geolocation.datamodel.Route; import org.sleuthkit.autopsy.report.ReportBranding; import org.sleuthkit.autopsy.report.ReportProgressPanel; @@ -70,7 +65,7 @@ class KMLReport implements GeneralReportModule { private SleuthkitCase skCase; private final SimpleDateFormat kmlDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX"); private Namespace ns; - String HTML_PROP_FORMAT = "%s: %s
    "; + private final String HTML_PROP_FORMAT = "%s: %s
    "; private Element gpsExifMetadataFolder; private Element gpsBookmarksFolder; @@ -309,7 +304,7 @@ class KMLReport implements GeneralReportModule { path = Paths.get(abstractFile.getName()); } - gpsExifMetadataFolder.addContent(makePlacemarkWithPicture(abstractFile.getName(), FeatureColor.RED, details, point.getTimestamp(), mapPoint, path, GeolocationUtils.getFormattedCoordinates(point.getLatitude(), point.getLongitude()))); + gpsExifMetadataFolder.addContent(makePlacemarkWithPicture(abstractFile.getName(), FeatureColor.RED, details, point.getTimestamp(), mapPoint, path, formattedCoordinates(point.getLatitude(), point.getLongitude()))); } /** @@ -322,7 +317,7 @@ class KMLReport implements GeneralReportModule { * @throws IOException */ void addLocationsToReport(SleuthkitCase skCase, String baseReportDir) throws TskCoreException, IOException { - List points = GeolocationManager.getWaypoints(skCase); + List points = Waypoint.getAllWaypoints(skCase); for (Waypoint point : points) { Element reportPoint = makePoint(point); @@ -330,7 +325,7 @@ class KMLReport implements GeneralReportModule { continue; } - String formattedCords = GeolocationUtils.getFormattedCoordinates(point.getLatitude(), point.getLongitude()); + String formattedCords = formattedCoordinates(point.getLatitude(), point.getLongitude()); switch (point.getType()) { case METADATA_EXIF: @@ -363,7 +358,7 @@ class KMLReport implements GeneralReportModule { * @throws TskCoreException */ void makeRoutes(SleuthkitCase skCase) throws TskCoreException { - List routes = GeolocationManager.getGPSRoutes(skCase); + List routes = Route.getGPSRoutes(skCase); if(routes == null) { return; @@ -392,12 +387,12 @@ class KMLReport implements GeneralReportModule { return; } - Element reportRoute = makeLineString(start.getLatitude(), start.getLongitude(), start.getAltitude(), end.getLatitude(), end.getLongitude(), end.getAltitude()); + Element reportRoute = makeLineString(start.getLatitude(), start.getLongitude(), end.getLatitude(), end.getLongitude()); Element startingPoint = makePoint(start.getLatitude(), start.getLongitude(), start.getAltitude()); Element endingPoint = makePoint(end.getLatitude(), end.getLongitude(), end.getAltitude()); - String formattedEnd = GeolocationUtils.getFormattedCoordinates(end.getLatitude(), end.getLongitude()); - String formattedStart = GeolocationUtils.getFormattedCoordinates(start.getLatitude(), start.getLongitude()); + String formattedEnd = formattedCoordinates(end.getLatitude(), end.getLongitude()); + String formattedStart = formattedCoordinates(start.getLatitude(), start.getLongitude()); String formattedCoordinates = String.format("%s to %s", formattedStart, formattedEnd); @@ -452,15 +447,12 @@ class KMLReport implements GeneralReportModule { return null; } - if (altitude == null) { - altitude = 0.0; - } Element point = new Element("Point", ns); //NON-NLS - // KML uses lon, lat. Deliberately reversed. - Element coordinates = new Element("coordinates", ns).addContent(longitude + "," + latitude + "," + altitude); //NON-NLS + // KML uses lon, lat. Deliberately reversed.1 + Element coordinates = new Element("coordinates", ns).addContent(longitude + "," + latitude + "," + (altitude != null ? altitude : 0.0)); //NON-NLS - if (altitude != 0) { + if (altitude != null && altitude != 0) { /* * Though we are including a non-zero altitude, clamp it to the * ground because inaccuracies from the GPS data can cause the @@ -494,18 +486,11 @@ class KMLReport implements GeneralReportModule { * * @return the Line as an Element */ - private Element makeLineString(Double startLatitude, Double startLongitude, Double startAltitude, Double stopLatitude, Double stopLongitude, Double stopAltitude) { + private Element makeLineString(Double startLatitude, Double startLongitude, Double stopLatitude, Double stopLongitude) { if (startLatitude == null || startLongitude == null || stopLatitude == null || stopLongitude == null) { return null; } - if (startAltitude == null) { - startAltitude = 0.0; - } - if (stopAltitude == null) { - stopAltitude = 0.0; - } - Element lineString = new Element("LineString", ns); //NON-NLS lineString.addContent(new Element("extrude", ns).addContent("1")); //NON-NLS lineString.addContent(new Element("tessellate", ns).addContent("1")); //NON-NLS @@ -691,13 +676,11 @@ class KMLReport implements GeneralReportModule { result.append(formatAttribute("Altitude", point.getAltitude().toString())); } - Map otherProps = point.getOtherProperties(); - Iterator> iterator = otherProps.entrySet().iterator(); - while (iterator.hasNext()) { - Entry entry = iterator.next(); - String value = entry.getValue(); - if (value != null && !value.isEmpty()) { - result.append(formatAttribute(entry.getKey(), value)); + List list = point.getOtherProperties(); + for(Waypoint.Property prop: list) { + String value = prop.getValue(); + if(value != null && !value.isEmpty()) { + result.append(formatAttribute(prop.getDisplayName(), value)); } } @@ -750,16 +733,22 @@ class KMLReport implements GeneralReportModule { result.append(formatAttribute("Altitude", route.getAltitude().toString())); } - Map otherProps = route.getOtherProperties(); - Iterator> iterator = otherProps.entrySet().iterator(); - while (iterator.hasNext()) { - Entry entry = iterator.next(); - String value = entry.getValue(); - if (value != null && !value.isEmpty()) { - result.append(formatAttribute(entry.getKey(), value)); + List list = route.getOtherProperties(); + for(Waypoint.Property prop: list) { + String value = prop.getValue(); + if(value != null && !value.isEmpty()) { + result.append(formatAttribute(prop.getDisplayName(), value)); } } return result.toString(); } + + private String formattedCoordinates(Double latitude, Double longitude) { + if (latitude == null || longitude == null) { + return ""; + } + + return String.format("%.2f, %.2f", latitude, longitude); + } } From b5ccef0fb200ae3ef3d6a275f21790379182a6af Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Tue, 22 Oct 2019 14:19:59 -0400 Subject: [PATCH 052/134] Two small codacy fixes --- .../autopsy/geolocation/datamodel/AttributeUtils.java | 7 +++++++ .../sleuthkit/autopsy/report/modules/kml/KMLReport.java | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/AttributeUtils.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/AttributeUtils.java index 00ca090bee..5892733350 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/AttributeUtils.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/AttributeUtils.java @@ -27,6 +27,13 @@ import org.sleuthkit.datamodel.TskCoreException; * attributes. */ public class AttributeUtils { + + /** + * Private constructor for this Utility class. + */ + private AttributeUtils() { + + } /** * Helper function for getting a String attribute from an artifact. This 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 4a5afdc200..ff5d1c35ba 100644 --- a/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java +++ b/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java @@ -65,7 +65,7 @@ class KMLReport implements GeneralReportModule { private SleuthkitCase skCase; private final SimpleDateFormat kmlDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX"); private Namespace ns; - private final String HTML_PROP_FORMAT = "%s: %s
    "; + private final static String HTML_PROP_FORMAT = "%s: %s
    "; private Element gpsExifMetadataFolder; private Element gpsBookmarksFolder; From 750c8b86fb9387ed7fa6e7f71787e567a5000501 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 22 Oct 2019 14:20:00 -0400 Subject: [PATCH 053/134] Fix DataContentViewerUtility.java formatting --- .../DataContentViewerUtility.java | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerUtility.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerUtility.java index 24f54fa7ac..ea1e7b58a0 100755 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerUtility.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerUtility.java @@ -1,15 +1,15 @@ /* * Autopsy Forensic Browser - * - * Copyright 2018 Basis Technology Corp. + * + * Copyright 2018-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. @@ -23,32 +23,32 @@ import org.openide.nodes.Node; import org.sleuthkit.datamodel.BlackboardArtifact; /** - * Utility classes for content viewers. - * In theory, this would live in the contentviewer package, - * but the initial method was needed only be viewers in + * Utility classes for content viewers. In theory, this would live in the + * contentviewer package, but the initial method was needed only be viewers in * corecomponents and therefore can stay out of public API. */ public class DataContentViewerUtility { + /** - * Returns the first non-Blackboard Artifact from a Node. - * Needed for (at least) Hex and Strings that want to view - * all types of content (not just AbstractFile), but don't want - * to display an artifact unless that's the only thing there. - * Scenario is hash hit or interesting item hit. - * + * Returns the first non-Blackboard Artifact from a Node. Needed for (at + * least) Hex and Strings that want to view all types of content (not just + * AbstractFile), but don't want to display an artifact unless that's the + * only thing there. Scenario is hash hit or interesting item hit. + * * @param node Node passed into content viewer + * * @return highest priority content or null if there is no content */ public static Content getDefaultContent(Node node) { Content bbContentSeen = null; - for (Content content : (node).getLookup().lookupAll(Content.class)) { + for (Content content : (node).getLookup().lookupAll(Content.class)) { if (content instanceof BlackboardArtifact) { bbContentSeen = content; - } - else { + } else { return content; } } return bbContentSeen; } + } From e9890a72679597d1c8bb0b3a68a2e59345839f42 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Tue, 22 Oct 2019 14:42:15 -0400 Subject: [PATCH 054/134] Cleaned up line queries and tested for regressions --- InternalPythonModules/android/line.py | 85 ++++++++++----------------- 1 file changed, 31 insertions(+), 54 deletions(-) diff --git a/InternalPythonModules/android/line.py b/InternalPythonModules/android/line.py index ab8e24c9f9..6edcf158fa 100644 --- a/InternalPythonModules/android/line.py +++ b/InternalPythonModules/android/line.py @@ -236,28 +236,23 @@ class LineCallLogsParser(TskCallLogsParser): def __init__(self, calllog_db): super(LineCallLogsParser, self).__init__(calllog_db.runQuery( """ - SELECT Substr(CH.call_type, -1) AS direction, - CH.start_time AS start_time, - CH.end_time AS end_time, - contacts_list_with_groups.members AS group_members, - contacts_list_with_groups.member_names AS names, - CH.caller_mid, - CH.voip_type AS call_type, - CH.voip_gc_media_type AS group_call_type + SELECT Substr(calls.call_type, -1) AS direction, + calls.start_time AS start_time, + calls.end_time AS end_time, + contact_book_w_groups.members AS group_members, + calls.caller_mid, + calls.voip_type AS call_type, + calls.voip_gc_media_type AS group_call_type FROM (SELECT id, - Group_concat(M.m_id) AS members, - Group_concat(Replace(C.server_name, ",", "")) AS member_names + Group_concat(M.m_id) AS members FROM membership AS M - JOIN naver.contacts AS C - ON M.m_id = C.m_id GROUP BY id UNION SELECT m_id, - NULL, - server_name - FROM naver.contacts) AS contacts_list_with_groups - JOIN call_history AS CH - ON CH.caller_mid = contacts_list_with_groups.id + NULL + FROM naver.contacts) AS contact_book_w_groups + JOIN call_history AS calls + ON calls.caller_mid = contact_book_w_groups.id """ ) ) @@ -355,43 +350,25 @@ class LineMessagesParser(TskMessagesParser): def __init__(self, message_db): super(LineMessagesParser, self).__init__(message_db.runQuery( """ - SELECT contact_list_with_groups.name, - contact_list_with_groups.id, - contact_list_with_groups.members, - contact_list_with_groups.member_names, - CH.from_mid, - C.server_name AS from_name, - CH.content, - CH.created_time, - CH.attachement_type, - CH.attachement_local_uri, - CH.status - FROM (SELECT G.name, - group_members.id, - group_members.members, - group_members.member_names - FROM (SELECT id, - group_concat(M.m_id) AS members, - group_concat(replace(C.server_name, - ",", - "")) as member_names - FROM membership AS M - JOIN contacts as C - ON M.m_id = C.m_id - GROUP BY id) AS group_members - JOIN groups AS G - ON G.id = group_members.id - UNION - SELECT server_name, - m_id, - NULL, - NULL - FROM contacts) AS contact_list_with_groups - JOIN chat_history AS CH - ON CH.chat_id = contact_list_with_groups.id - LEFT JOIN contacts as C - ON C.m_id = CH.from_mid - WHERE attachement_type != 6 + SELECT contact_book_w_groups.id, + contact_book_w_groups.members, + messages.from_mid, + messages.content, + messages.created_time, + messages.attachement_type, + messages.attachement_local_uri, + messages.status + FROM (SELECT id, + Group_concat(M.m_id) AS members + FROM membership AS M + GROUP BY id + UNION + SELECT m_id, + NULL + FROM contacts) AS contact_book_w_groups + JOIN chat_history AS messages + ON messages.chat_id = contact_book_w_groups.id + WHERE attachement_type != 6 """ ) ) From 77aa843d862c55c861d41cd77ecd5580be71e839 Mon Sep 17 00:00:00 2001 From: Eammon Date: Tue, 22 Oct 2019 14:43:30 -0400 Subject: [PATCH 055/134] Added mechanism to get the startup window and to use it or the multi user case dialog as the parent of the file chooser. --- .../autopsy/casemodule/CaseOpenAction.java | 14 ++++++++------ .../autopsy/casemodule/StartupWindowProvider.java | 9 +++++++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java index b68d1a49f6..0ba92c7bce 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseOpenAction.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.casemodule; +import java.awt.Component; import java.awt.Cursor; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -93,7 +94,8 @@ public final class CaseOpenAction extends CallableSystemAction implements Action * If the open multi user case dialog is open make sure it's not set * to always be on top as this hides the file chooser on macOS. */ - OpenMultiUserCaseDialog.getInstance().setAlwaysOnTop(false); + OpenMultiUserCaseDialog multiUserCaseDialog = OpenMultiUserCaseDialog.getInstance(); + multiUserCaseDialog.setAlwaysOnTop(false); String optionsDlgTitle = NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning.title"); String optionsDlgMessage = NbBundle.getMessage(Case.class, "CloseCaseWhileIngesting.Warning"); if (IngestRunningCheck.checkAndConfirmProceed(optionsDlgTitle, optionsDlgMessage)) { @@ -102,11 +104,11 @@ public final class CaseOpenAction extends CallableSystemAction implements Action * file (.aut file). */ /** - * Passing the fileChooser as its own parent gets around an issue - * where the fileChooser was hidden behind the CueBannerPanel ("Welcome" dialog) - * on macOS. + * The parent of the fileChooser will either be the multi user + * case dialog or the startup window. */ - int retval = fileChooser.showOpenDialog(fileChooser); + int retval = fileChooser.showOpenDialog(multiUserCaseDialog.isVisible() + ? multiUserCaseDialog : (Component) StartupWindowProvider.getInstance().getStartupWindow()); if (retval == JFileChooser.APPROVE_OPTION) { /* * Close the startup window, if it is open. @@ -116,7 +118,7 @@ public final class CaseOpenAction extends CallableSystemAction implements Action /* * Close the Open Multi-User Case window, if it is open. */ - OpenMultiUserCaseDialog.getInstance().setVisible(false); + multiUserCaseDialog.setVisible(false); /* * Try to open the case associated with the case metadata file diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/StartupWindowProvider.java b/Core/src/org/sleuthkit/autopsy/casemodule/StartupWindowProvider.java index 13aae1c0de..8bec55f53c 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/StartupWindowProvider.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/StartupWindowProvider.java @@ -144,4 +144,13 @@ public class StartupWindowProvider implements StartupWindowInterface { startupWindowToUse.close(); } } + + /** + * Get the chosen startup window. + * + * @return The startup window. + */ + public StartupWindowInterface getStartupWindow() { + return startupWindowToUse; + } } From 65af0dfb40000fe83fec244441373f3f36075f17 Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Tue, 22 Oct 2019 15:11:54 -0400 Subject: [PATCH 056/134] Fixed bug in ArtifactWaypoint.java --- .../autopsy/geolocation/datamodel/ArtifactWaypoint.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactWaypoint.java index 09fd01d5a1..280da3a563 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactWaypoint.java @@ -102,7 +102,7 @@ class ArtifactWaypoint implements Waypoint { label, timestamp, AttributeUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE), - AttributeUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE), + AttributeUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE), AttributeUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE), image, type); From 7bd266027ea028be8d68c3b7a456718fef06974b Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Tue, 22 Oct 2019 15:27:00 -0400 Subject: [PATCH 057/134] Updated to get waypoints from new datamodel --- .../geolocation/Bundle.properties-MERGED | 1 + .../geolocation/GeolocationTopComponent.java | 8 +-- .../autopsy/geolocation/MapWaypoint.java | 56 +++++++++++++++++++ 3 files changed, 58 insertions(+), 7 deletions(-) create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties-MERGED index 5f7a6cd2f0..f925437b0b 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/geolocation/Bundle.properties-MERGED @@ -1,6 +1,7 @@ CTL_OpenGeolocation=Geolocation CTL_GeolocationTopComponentAction=GeolocationTopComponent CTL_GeolocationTopComponent=Geolocation +GLTopComponent_initilzation_error=An error occurred during waypoint initilization. Geolocation data maybe incomplete. GLTopComponent_name=Geolocation OpenGeolocationAction_displayName=Geolocation OpenGeolocationAction_name=Geolocation diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java index 3678f562e8..1e3143d678 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java @@ -42,8 +42,6 @@ import org.sleuthkit.autopsy.ingest.IngestManager; import static org.sleuthkit.autopsy.ingest.IngestManager.IngestModuleEvent.DATA_ADDED; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.TskCoreException; /** * Top component which displays the Geolocation Tool. @@ -157,11 +155,7 @@ public final class GeolocationTopComponent extends TopComponent { Case currentCase = Case.getCurrentCaseThrows(); -// waypoints.addAll(getGPSRouteWaypoints(currentCase.getSleuthkitCase())); -// waypoints.addAll(getEXIFWaypoints(currentCase.getSleuthkitCase())); -// waypoints.addAll(getSimpleWaypoints(currentCase.getSleuthkitCase())); - - return waypoints; + return MapWaypoint.getWaypoints(currentCase.getSleuthkitCase()); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java new file mode 100755 index 0000000000..d1fb35ae46 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java @@ -0,0 +1,56 @@ +/* + * 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.util.ArrayList; +import java.util.List; +import org.jxmapviewer.viewer.GeoPosition; +import org.sleuthkit.autopsy.geolocation.datamodel.Route; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint; + +/** + * + * + */ +final class MapWaypoint implements org.jxmapviewer.viewer.Waypoint{ + + private final Waypoint dataModelWaypoint; + private final GeoPosition position; + + private MapWaypoint(Waypoint dataModelWaypoint) { + this.dataModelWaypoint = dataModelWaypoint; + position = new GeoPosition(dataModelWaypoint.getLatitude(), dataModelWaypoint.getLongitude()); + } + + static List getWaypoints(SleuthkitCase skCase) throws TskCoreException{ + List points = Waypoint.getAllWaypoints(skCase); + + List routes = Route.getGPSRoutes(skCase); + for(Route route: routes) { + points.addAll(route.getRoute()); + } + + List mapPoints = new ArrayList<>(); + + for(Waypoint point: points) { + mapPoints.add(new MapWaypoint(point)); + } + + return mapPoints; + } + + @Override + public GeoPosition getPosition() { + return position; + } + + String getDisplayName() { + return dataModelWaypoint.getLabel(); + } + +} From 2883e5f2814972f8d31de9cfe5c4f1d4feb162a2 Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Tue, 22 Oct 2019 15:28:59 -0400 Subject: [PATCH 058/134] Removed updated file name and func name for consistancy --- .../org/sleuthkit/autopsy/geolocation/datamodel/Route.java | 2 +- .../{GPSSearchWaypoint.java => SearchWaypoint.java} | 6 +++--- .../sleuthkit/autopsy/geolocation/datamodel/Waypoint.java | 2 +- .../org/sleuthkit/autopsy/report/modules/kml/KMLReport.java | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) rename Core/src/org/sleuthkit/autopsy/geolocation/datamodel/{GPSSearchWaypoint.java => SearchWaypoint.java} (90%) diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java index 4eefc72bca..728c5995c7 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java @@ -59,7 +59,7 @@ public class Route { * * @throws TskCoreException */ - static public List getGPSRoutes(SleuthkitCase skCase) throws TskCoreException { + static public List getRoutes(SleuthkitCase skCase) throws TskCoreException { List routes = new ArrayList<>(); List artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE); for (BlackboardArtifact artifact : artifacts) { diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GPSSearchWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/SearchWaypoint.java similarity index 90% rename from Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GPSSearchWaypoint.java rename to Core/src/org/sleuthkit/autopsy/geolocation/datamodel/SearchWaypoint.java index 28bd7127a0..0c780861dc 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GPSSearchWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/SearchWaypoint.java @@ -24,9 +24,9 @@ import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.TskCoreException; /** - * A GPSSearchWaypoint is a subclass of ArtifactWaypoint. + * A SearchWaypoint is a subclass of ArtifactWaypoint. */ -final class GPSSearchWaypoint extends ArtifactWaypoint { +final class SearchWaypoint extends ArtifactWaypoint { @Messages({ "SearchWaypoint_DisplayLabel=GPS Search" @@ -35,7 +35,7 @@ final class GPSSearchWaypoint extends ArtifactWaypoint { /** * Construct a GPS Search waypoint. */ - public GPSSearchWaypoint(BlackboardArtifact artifact) throws TskCoreException { + public SearchWaypoint(BlackboardArtifact artifact) throws TskCoreException { super(artifact, getLabelFromArtifact(artifact), Waypoint.Type.SEARCH); } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java index 2bf606b18b..c64bc29ae2 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java @@ -187,7 +187,7 @@ public interface Waypoint { List artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH); if (artifacts != null) { for (BlackboardArtifact artifact : artifacts) { - ArtifactWaypoint point = new GPSSearchWaypoint(artifact); + ArtifactWaypoint point = new SearchWaypoint(artifact); // Only add to the list if the point has a valid latitude // and longitude. if (point.getLatitude() != null && point.getLongitude() != null) { 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 ff5d1c35ba..30ff371860 100644 --- a/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java +++ b/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java @@ -358,7 +358,7 @@ class KMLReport implements GeneralReportModule { * @throws TskCoreException */ void makeRoutes(SleuthkitCase skCase) throws TskCoreException { - List routes = Route.getGPSRoutes(skCase); + List routes = Route.getRoutes(skCase); if(routes == null) { return; From 7e745617c0cb5009ad8e50d6053e3e7bc6b02a9b Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Tue, 22 Oct 2019 15:38:16 -0400 Subject: [PATCH 059/134] Simplifed and tested the textnow queries --- InternalPythonModules/android/textnow.py | 78 +++++++++++------------- 1 file changed, 37 insertions(+), 41 deletions(-) diff --git a/InternalPythonModules/android/textnow.py b/InternalPythonModules/android/textnow.py index ad9704cece..1890c7ae42 100644 --- a/InternalPythonModules/android/textnow.py +++ b/InternalPythonModules/android/textnow.py @@ -290,53 +290,50 @@ class TextNowMessagesParser(TskMessagesParser): """ super(TextNowMessagesParser, self).__init__(message_db.runQuery( """ - - SELECT CASE - WHEN message_direction == 2 THEN "" - WHEN to_addresses IS NULL THEN M.contact_value - ELSE contact_name - end from_address, - CASE - WHEN message_direction == 1 THEN "" - WHEN to_addresses IS NULL THEN M.contact_value - ELSE to_addresses - end to_address, - message_direction, - message_text, - M.READ, - M.date, - M.attach, - thread_id - FROM (SELECT group_info.contact_value, - group_info.to_addresses, - G.contact_value AS thread_id - FROM (SELECT GM.contact_value, - Group_concat(GM.member_contact_value) AS to_addresses - FROM group_members AS GM - GROUP BY GM.contact_value) AS group_info - JOIN groups AS G - ON G.contact_value = group_info.contact_value - UNION - SELECT c.contact_value, - NULL, - "-1" - FROM contacts AS c) AS to_from_map - JOIN messages AS M - ON M.contact_value = to_from_map.contact_value - WHERE message_type NOT IN ( 102, 100 ) + SELECT CASE + WHEN messages.message_direction == 2 THEN NULL + WHEN contact_book_w_groups.to_addresses IS NULL THEN + messages.contact_value + END from_address, + CASE + WHEN messages.message_direction == 1 THEN NULL + WHEN contact_book_w_groups.to_addresses IS NULL THEN + messages.contact_value + ELSE contact_book_w_groups.to_addresses + END to_address, + messages.message_direction, + messages.message_text, + messages.READ, + messages.DATE, + messages.attach, + thread_id + FROM (SELECT GM.contact_value, + Group_concat(GM.member_contact_value) AS to_addresses, + G.contact_value AS thread_id + FROM group_members AS GM + join GROUPS AS G + ON G.contact_value = GM.contact_value + GROUP BY GM.contact_value + UNION + SELECT contact_value, + NULL, + NULL + FROM contacts) AS contact_book_w_groups + join messages + ON messages.contact_value = contact_book_w_groups.contact_value + WHERE message_type NOT IN ( 102, 100 ) """ ) ) self._TEXTNOW_MESSAGE_TYPE = "TextNow Message" self._INCOMING_MESSAGE_TYPE = 1 self._OUTGOING_MESSAGE_TYPE = 2 - self._UNKNOWN_THREAD_ID = "-1" def get_message_type(self): return self._TEXTNOW_MESSAGE_TYPE def get_phone_number_from(self): - if self.result_set.getString("from_address") == "": + if self.result_set.getString("from_address") is None: return super(TextNowMessagesParser, self).get_phone_number_from() return self.result_set.getString("from_address") @@ -347,10 +344,9 @@ class TextNowMessagesParser(TskMessagesParser): return self.OUTGOING def get_phone_number_to(self): - if self.result_set.getString("to_address") == "": + if self.result_set.getString("to_address") is None: return super(TextNowMessagesParser, self).get_phone_number_to() - recipients = self.result_set.getString("to_address").split(",") - return recipients + return self.result_set.getString("to_address").split(",") def get_message_date_time(self): #convert ms to s @@ -359,7 +355,7 @@ class TextNowMessagesParser(TskMessagesParser): def get_message_read_status(self): read = self.result_set.getBoolean("read") if self.get_message_direction() == self.INCOMING: - if read == True: + if read: return self.READ return self.UNREAD @@ -375,6 +371,6 @@ class TextNowMessagesParser(TskMessagesParser): def get_thread_id(self): thread_id = self.result_set.getString("thread_id") - if thread_id == self._UNKNOWN_THREAD_ID: + if thread_id is None: return super(TextNowMessagesParser, self).get_thread_id() return thread_id From f458b7d2254cf99cdf16e50227a5d8b04c750f98 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 22 Oct 2019 15:42:08 -0400 Subject: [PATCH 060/134] 5665 allow creation of sub menus for collection instead of lookup --- .../AddBlackboardArtifactTagAction.java | 28 +++++++--- .../autopsy/actions/AddContentTagAction.java | 15 ++++- .../autopsy/actions/AddTagAction.java | 17 ++++++ .../actions/DeleteFileContentTagAction.java | 55 ++++++++++--------- .../AddContentToHashDbAction.java | 19 +++++-- 5 files changed, 94 insertions(+), 40 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java index 9015d9a6ad..ce5b5167ed 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,6 +30,7 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; @@ -72,15 +73,24 @@ public class AddBlackboardArtifactTagAction extends AddTagAction { @Override protected void addTag(TagName tagName, String comment) { - /* - * The documentation for Lookup.lookupAll() explicitly says that the - * collection it returns may contain duplicates. Within this invocation - * of addTag(), we don't want to tag the same BlackboardArtifact more - * than once, so we dedupe the BlackboardArtifacts by stuffing them into - * a HashSet. - */ - final Collection selectedArtifacts = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); + final Collection selectedArtifacts = new HashSet<>(); + if (getContentToTag().isEmpty()) { + /* + * The documentation for Lookup.lookupAll() explicitly says that the + * collection it returns may contain duplicates. Within this + * invocation of addTag(), we don't want to tag the same + * BlackboardArtifact more than once, so we dedupe the + * BlackboardArtifacts by stuffing them into a HashSet. + */ + selectedArtifacts.addAll(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); + } else { + for (Content content : getContentToTag()) { + if (content instanceof BlackboardArtifact) { + selectedArtifacts.add((BlackboardArtifact) content); + } + } + } new Thread(() -> { for (BlackboardArtifact artifact : selectedArtifacts) { try { diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java index 34e7b2a110..e6f09ede0c 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java @@ -76,6 +76,8 @@ public class AddContentTagAction extends AddTagAction { @Override protected void addTag(TagName tagName, String comment) { + final Collection selectedFiles = new HashSet<>(); + if (getContentToTag().isEmpty()) { /* * The documentation for Lookup.lookupAll() explicitly says that the * collection it returns may contain duplicates. Within this invocation @@ -84,8 +86,17 @@ public class AddContentTagAction extends AddTagAction { * * We don't want VirtualFile and DerivedFile objects to be tagged. */ - final Collection selectedFiles = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); - + + selectedFiles.addAll(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); + } + else { + for (Content content : getContentToTag()){ + if (content instanceof AbstractFile){ + selectedFiles.add((AbstractFile)content); + } + } + + } new Thread(() -> { for (AbstractFile file : selectedFiles) { try { diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java index 6606185e24..00fe6630c5 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java @@ -20,6 +20,9 @@ package org.sleuthkit.autopsy.actions; import java.awt.event.ActionEvent; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.TreeMap; @@ -33,6 +36,8 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.services.TagsManager; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; @@ -45,6 +50,7 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { private static final long serialVersionUID = 1L; private static final String NO_COMMENT = ""; + private final Collection content = new HashSet<>(); AddTagAction(String menuText) { super(menuText); @@ -52,6 +58,17 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { @Override public JMenuItem getPopupPresenter() { + content.clear(); + return new TagMenu(); + } + + Collection getContentToTag(){ + return Collections.unmodifiableCollection(content); + } + + public JMenuItem getMenuForContent(Collection contentToTag){ + content.clear(); + content.addAll(contentToTag); return new TagMenu(); } diff --git a/Core/src/org/sleuthkit/autopsy/actions/DeleteFileContentTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/DeleteFileContentTagAction.java index c8a8a69fdc..ea3d2369be 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/DeleteFileContentTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/DeleteFileContentTagAction.java @@ -1,15 +1,15 @@ /* * Autopsy Forensic Browser - * + * * Copyright 2017-2018 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. @@ -53,7 +53,7 @@ import org.sleuthkit.datamodel.TskData; "DeleteFileContentTagAction.deleteTag=Remove File Tag" }) public class DeleteFileContentTagAction extends AbstractAction implements Presenter.Popup { - + private static final Logger logger = Logger.getLogger(DeleteFileContentTagAction.class.getName()); private static final long serialVersionUID = 1L; @@ -81,6 +81,10 @@ public class DeleteFileContentTagAction extends AbstractAction implements Presen return new TagMenu(); } + public JMenuItem getMenuForFiles(Collection selectedFiles) { + return new TagMenu(selectedFiles); + } + @Override public void actionPerformed(ActionEvent e) { } @@ -102,19 +106,19 @@ public class DeleteFileContentTagAction extends AbstractAction implements Presen tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Error untagging file. No open case found.", ex); //NON-NLS - Platform.runLater(() -> - new Alert(Alert.AlertType.ERROR, Bundle.DeleteFileContentTagAction_deleteTag_alert(fileId)).show() + Platform.runLater(() + -> new Alert(Alert.AlertType.ERROR, Bundle.DeleteFileContentTagAction_deleteTag_alert(fileId)).show() ); return null; } - + try { logger.log(Level.INFO, "Removing tag {0} from {1}", new Object[]{tagName.getDisplayName(), contentTag.getContent().getName()}); //NON-NLS tagsManager.deleteContentTag(contentTag); } catch (TskCoreException tskCoreException) { logger.log(Level.SEVERE, "Error untagging file", tskCoreException); //NON-NLS - Platform.runLater(() -> - new Alert(Alert.AlertType.ERROR, Bundle.DeleteFileContentTagAction_deleteTag_alert(fileId)).show() + Platform.runLater(() + -> new Alert(Alert.AlertType.ERROR, Bundle.DeleteFileContentTagAction_deleteTag_alert(fileId)).show() ); } return null; @@ -142,14 +146,15 @@ public class DeleteFileContentTagAction extends AbstractAction implements Presen private static final long serialVersionUID = 1L; TagMenu() { + this(new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class))); + } + + TagMenu(Collection selectedFiles) { super(getActionDisplayName()); - - final Collection selectedAbstractFilesList = - new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); - - if(!selectedAbstractFilesList.isEmpty()) { - AbstractFile file = selectedAbstractFilesList.iterator().next(); - + + if (!selectedFiles.isEmpty()) { + AbstractFile file = selectedFiles.iterator().next(); + Map tagNamesMap = null; List standardTagNames = TagsManager.getStandardTagNames(); List standardTagMenuitems = new ArrayList<>(); @@ -167,22 +172,22 @@ public class DeleteFileContentTagAction extends AbstractAction implements Presen // a tag with the associated tag name. if (null != tagNamesMap && !tagNamesMap.isEmpty()) { try { - List existingTagsList = - Case.getCurrentCaseThrows().getServices().getTagsManager() + List existingTagsList + = Case.getCurrentCaseThrows().getServices().getTagsManager() .getContentTagsByContent(file); for (Map.Entry entry : tagNamesMap.entrySet()) { String tagDisplayName = entry.getKey(); TagName tagName = entry.getValue(); - for(ContentTag contentTag : existingTagsList) { - if(tagDisplayName.equals(contentTag.getName().getDisplayName())) { + for (ContentTag contentTag : existingTagsList) { + if (tagDisplayName.equals(contentTag.getName().getDisplayName())) { String notableString = tagName.getKnownStatus() == TskData.FileKnown.BAD ? TagsManager.getNotableTagLabel() : ""; JMenuItem tagNameItem = new JMenuItem(tagDisplayName + notableString); tagNameItem.addActionListener((ActionEvent e) -> { deleteTag(tagName, contentTag, file.getId()); }); - + // Show custom tags before predefined tags in the menu if (standardTagNames.contains(tagDisplayName)) { standardTagMenuitems.add(tagNameItem); @@ -198,14 +203,14 @@ public class DeleteFileContentTagAction extends AbstractAction implements Presen } } - if ((getItemCount() > 0) && !standardTagMenuitems.isEmpty() ){ + if ((getItemCount() > 0) && !standardTagMenuitems.isEmpty()) { addSeparator(); } standardTagMenuitems.forEach((menuItem) -> { add(menuItem); }); - - if(getItemCount() == 0) { + + if (getItemCount() == 0) { setEnabled(false); } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/AddContentToHashDbAction.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/AddContentToHashDbAction.java index f80d791bf3..121f76522f 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/AddContentToHashDbAction.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/AddContentToHashDbAction.java @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.modules.hashdatabase; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Collection; +import java.util.HashSet; import java.util.List; import java.util.logging.Level; import javax.swing.AbstractAction; @@ -42,7 +43,7 @@ import org.sleuthkit.datamodel.TskCoreException; /** * Instances of this Action allow users to content to a hash database. */ -final class AddContentToHashDbAction extends AbstractAction implements Presenter.Popup { +public final class AddContentToHashDbAction extends AbstractAction implements Presenter.Popup { private static AddContentToHashDbAction instance; @@ -83,6 +84,10 @@ final class AddContentToHashDbAction extends AbstractAction implements Presenter private AddContentToHashDbAction() { } + public JMenuItem getMenuForFiles(Collection selectedFiles) { + return new AddContentToHashDbMenu(selectedFiles); + } + @Override public JMenuItem getPopupPresenter() { return new AddContentToHashDbMenu(); @@ -96,10 +101,10 @@ final class AddContentToHashDbAction extends AbstractAction implements Presenter // action. private final class AddContentToHashDbMenu extends JMenu { - AddContentToHashDbMenu() { + private static final long serialVersionUID = 1L; + + AddContentToHashDbMenu(Collection selectedFiles) { super(SINGLE_SELECTION_NAME); - // Get any AbstractFile objects from the lookup of the currently focused top component. - final Collection selectedFiles = Utilities.actionsGlobalContext().lookupAll(AbstractFile.class); int numberOfFilesSelected = selectedFiles.size(); // Disable the menu if file ingest is in progress. @@ -178,6 +183,12 @@ final class AddContentToHashDbAction extends AbstractAction implements Presenter add(newHashSetItem); } + AddContentToHashDbMenu() { + // Get any AbstractFile objects from the lookup of the currently focused top component. + this(new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class))); + + } + /** * Determines which (2) display text should be set given the number of * files selected. From b3705dd123e5a62638b60fee9b3266bbf0bbe5bf Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 22 Oct 2019 16:00:45 -0400 Subject: [PATCH 061/134] 5665 add comments update dates --- .../AddBlackboardArtifactTagAction.java | 2 +- .../autopsy/actions/AddContentTagAction.java | 31 ++++++------ .../autopsy/actions/AddTagAction.java | 48 ++++++++++++------- .../actions/DeleteFileContentTagAction.java | 19 +++++++- .../AddContentToHashDbAction.java | 19 +++++++- 5 files changed, 83 insertions(+), 36 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java index ce5b5167ed..26d1c7a9bc 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java @@ -74,6 +74,7 @@ public class AddBlackboardArtifactTagAction extends AddTagAction { @Override protected void addTag(TagName tagName, String comment) { final Collection selectedArtifacts = new HashSet<>(); + //If the contentToTag is empty look up the selected content if (getContentToTag().isEmpty()) { /* * The documentation for Lookup.lookupAll() explicitly says that the @@ -89,7 +90,6 @@ public class AddBlackboardArtifactTagAction extends AddTagAction { selectedArtifacts.add((BlackboardArtifact) content); } } - } new Thread(() -> { for (BlackboardArtifact artifact : selectedArtifacts) { diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java index e6f09ede0c..c32533b0cd 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -77,25 +77,24 @@ public class AddContentTagAction extends AddTagAction { @Override protected void addTag(TagName tagName, String comment) { final Collection selectedFiles = new HashSet<>(); + //If the contentToTag is empty look up the selected content if (getContentToTag().isEmpty()) { - /* - * The documentation for Lookup.lookupAll() explicitly says that the - * collection it returns may contain duplicates. Within this invocation - * of addTag(), we don't want to tag the same AbstractFile more than - * once, so we dedupe the AbstractFiles by stuffing them into a HashSet. - * - * We don't want VirtualFile and DerivedFile objects to be tagged. - */ - + /* + * The documentation for Lookup.lookupAll() explicitly says that the + * collection it returns may contain duplicates. Within this + * invocation of addTag(), we don't want to tag the same + * AbstractFile more than once, so we dedupe the AbstractFiles by + * stuffing them into a HashSet. + * + * We don't want VirtualFile and DerivedFile objects to be tagged. + */ selectedFiles.addAll(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); - } - else { - for (Content content : getContentToTag()){ - if (content instanceof AbstractFile){ - selectedFiles.add((AbstractFile)content); + } else { + for (Content content : getContentToTag()) { + if (content instanceof AbstractFile) { + selectedFiles.add((AbstractFile) content); } } - } new Thread(() -> { for (AbstractFile file : selectedFiles) { diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java index 00fe6630c5..3709592e62 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2011-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -36,7 +36,6 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.services.TagsManager; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; @@ -61,12 +60,27 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { content.clear(); return new TagMenu(); } - - Collection getContentToTag(){ + + /** + * Get the collection of content which may have been specified for this + * action. Empty collection returned when no content was specified. + * + * @return The specified content for this action. + */ + Collection getContentToTag() { return Collections.unmodifiableCollection(content); } - - public JMenuItem getMenuForContent(Collection contentToTag){ + + /** + * Get the menu for adding tags to the specified collection of Content. + * + * @param contentToTag The collection of Content the menu actions will be + * applied to. + * + * @return The menu which will allow users to choose the tag they want to + * apply to the Content specified. + */ + public JMenuItem getMenuForContent(Collection contentToTag) { content.clear(); content.addAll(contentToTag); return new TagMenu(); @@ -135,26 +149,26 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { tagNameItem.addActionListener((ActionEvent e) -> { getAndAddTag(entry.getKey(), entry.getValue(), NO_COMMENT); }); - - // Show custom tags before predefined tags in the menu + + // Show custom tags before predefined tags in the menu if (standardTagNames.contains(tagDisplayName)) { standardTagMenuitems.add(tagNameItem); } else { add(tagNameItem); } } - } - + } + if (getItemCount() > 0) { addSeparator(); } - + standardTagMenuitems.forEach((menuItem) -> { add(menuItem); }); - + addSeparator(); - + // Create a "Choose Tag and Comment..." menu item. Selecting this item initiates // a dialog that can be used to create or select a tag name with an // optional comment and adds a tag with the resulting name. @@ -167,7 +181,7 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { } }); add(tagAndCommentItem); - + // Create a "New Tag..." menu item. // Selecting this item initiates a dialog that can be used to create // or select a tag name and adds a tag with the resulting name. @@ -179,7 +193,7 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { } }); add(newTagMenuItem); - + } /** @@ -211,10 +225,10 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { tagName = openCase.getServices().getTagsManager().getDisplayNamesToTagNamesMap().get(tagDisplayName); } catch (TskCoreException ex1) { Logger.getLogger(AddTagAction.class.getName()).log(Level.SEVERE, tagDisplayName + " already exists in database but an error occurred in retrieving it.", ex1); //NON-NLS - } + } } catch (TskCoreException ex) { Logger.getLogger(AddTagAction.class.getName()).log(Level.SEVERE, "Error adding " + tagDisplayName + " tag name", ex); //NON-NLS - } + } } addTag(tagName, comment); } diff --git a/Core/src/org/sleuthkit/autopsy/actions/DeleteFileContentTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/DeleteFileContentTagAction.java index ea3d2369be..f68017de19 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/DeleteFileContentTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/DeleteFileContentTagAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2017-2018 Basis Technology Corp. + * Copyright 2017-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -81,6 +81,15 @@ public class DeleteFileContentTagAction extends AbstractAction implements Presen return new TagMenu(); } + /** + * Get the menu for removing tags from the specified collection of Files. + * + * @param selectedFiles The collection of AbstractFiles the menu actions + * will be applied to. + * + * @return The menu which will allow users to remove tags from the specified + * collection of Files. + */ public JMenuItem getMenuForFiles(Collection selectedFiles) { return new TagMenu(selectedFiles); } @@ -145,10 +154,18 @@ public class DeleteFileContentTagAction extends AbstractAction implements Presen private static final long serialVersionUID = 1L; + /** + * Construct an TagMenu object using the specified collection of files + * as the files to remove a tag from. + */ TagMenu() { this(new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class))); } + /** + * Construct an TagMenu object using the specified collection of files + * as the files to remove a tag from. + */ TagMenu(Collection selectedFiles) { super(getActionDisplayName()); diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/AddContentToHashDbAction.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/AddContentToHashDbAction.java index 121f76522f..18acd79d6f 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/AddContentToHashDbAction.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/AddContentToHashDbAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-2018 Basis Technology Corp. + * Copyright 2013-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -84,6 +84,15 @@ public final class AddContentToHashDbAction extends AbstractAction implements Pr private AddContentToHashDbAction() { } + /** + * Get the menu for adding the specified collection of Files to a HashDb. + * + * @param selectedFiles The collection of AbstractFiles the menu actions + * will be applied to. + * + * @return The menu which will allow users to add the specified files to a + * HashDb. + */ public JMenuItem getMenuForFiles(Collection selectedFiles) { return new AddContentToHashDbMenu(selectedFiles); } @@ -103,6 +112,10 @@ public final class AddContentToHashDbAction extends AbstractAction implements Pr private static final long serialVersionUID = 1L; + /** + * Construct an AddContentToHashDbMenu object using the specified + * collection of files as the files to be added to a HashDb. + */ AddContentToHashDbMenu(Collection selectedFiles) { super(SINGLE_SELECTION_NAME); int numberOfFilesSelected = selectedFiles.size(); @@ -183,6 +196,10 @@ public final class AddContentToHashDbAction extends AbstractAction implements Pr add(newHashSetItem); } + /** + * Construct an AddContentToHashDbMenu object using the currently + * selected files as the files to be added to a HashDb. + */ AddContentToHashDbMenu() { // Get any AbstractFile objects from the lookup of the currently focused top component. this(new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class))); From b46fe4d52432ed47e77589cccee69df604c2b1e2 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Tue, 22 Oct 2019 16:56:54 -0400 Subject: [PATCH 062/134] Cleaned up the whatsapp query and tested changes --- InternalPythonModules/android/whatsapp.py | 49 +++++++++++------------ 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/InternalPythonModules/android/whatsapp.py b/InternalPythonModules/android/whatsapp.py index 9cb7ea3d73..438784f3e2 100644 --- a/InternalPythonModules/android/whatsapp.py +++ b/InternalPythonModules/android/whatsapp.py @@ -433,31 +433,28 @@ class WhatsAppMessagesParser(TskMessagesParser): def __init__(self, message_db): super(WhatsAppMessagesParser, self).__init__(message_db.runQuery( """ - SELECT M.key_remote_jid AS id, - contact_info.recipients, - key_from_me AS direction, - CASE - WHEN M.data IS NULL THEN "" - ELSE M.data - END AS content, - M.timestamp AS send_timestamp, - M.received_timestamp, - M.remote_resource AS group_sender, - M.media_url As attachment - FROM (SELECT jid, - recipients - FROM wadb.wa_contacts AS WC - LEFT JOIN (SELECT gjid, - group_concat(CASE - WHEN jid == "" THEN NULL - ELSE jid - END) AS recipients - FROM group_participants - GROUP BY gjid) AS group_map - ON WC.jid = group_map.gjid - GROUP BY jid) AS contact_info - JOIN messages AS M - ON M.key_remote_jid = contact_info.jid + SELECT messages.key_remote_jid AS id, + contact_book_w_groups.recipients, + key_from_me AS direction, + messages.data AS content, + messages.timestamp AS send_timestamp, + messages.received_timestamp, + messages.remote_resource AS group_sender, + messages.media_url AS attachment + FROM (SELECT jid, + recipients + FROM wadb.wa_contacts AS contacts + left join (SELECT gjid, + Group_concat(CASE + WHEN jid == "" THEN NULL + ELSE jid + END) AS recipients + FROM group_participants + GROUP BY gjid) AS groups + ON contacts.jid = groups.gjid + GROUP BY jid) AS contact_book_w_groups + join messages + ON messages.key_remote_jid = contact_book_w_groups.jid """ ) ) @@ -503,6 +500,8 @@ class WhatsAppMessagesParser(TskMessagesParser): def get_message_text(self): message = self.result_set.getString("content") + if message is None: + message = super(WhatsAppMessagesParser, self).get_message_text() attachment = self.result_set.getString("attachment") if attachment is not None: return general.appendAttachmentList(message, [attachment]) From cb820db3753638602373173ded34439b600eaca4 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 22 Oct 2019 16:59:20 -0400 Subject: [PATCH 063/134] 5665 attempt to reduce cyclomatic complexity and address other codacy issue --- .../AddContentToHashDbAction.java | 51 ++++++++++--------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/AddContentToHashDbAction.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/AddContentToHashDbAction.java index 18acd79d6f..1868d3c57f 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/AddContentToHashDbAction.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/AddContentToHashDbAction.java @@ -67,12 +67,16 @@ public final class AddContentToHashDbAction extends AbstractAction implements Pr "AddContentToHashDbAction.singleSelectionNameNoMD5"); private final static String MULTI_SELECTION_NAME_NO_MD5 = NbBundle.getMessage(AddContentToHashDbAction.class, "AddContentToHashDbAction.multipleSelectionNameNoMD5"); + private static final long serialVersionUID = 1L; /** * AddContentToHashDbAction is a singleton to support multi-selection of * nodes, since org.openide.nodes.NodeOp.findActions(Node[] nodes) will only * pick up an Action from a node if every node in the nodes array returns a * reference to the same action object from Node.getActions(boolean). + * + * @return The AddContentToHashDbAction instance which is used to provide + * the menu for adding content to a HashDb. */ public static synchronized AddContentToHashDbAction getInstance() { if (null == instance) { @@ -127,16 +131,13 @@ public final class AddContentToHashDbAction extends AbstractAction implements Pr SINGLE_SELECTION_NAME_DURING_INGEST, MULTI_SELECTION_NAME_DURING_INGEST); return; - } - - if (selectedFiles.isEmpty()) { + } else if (numberOfFilesSelected == 0) { setEnabled(false); return; - } else { - setTextBasedOnNumberOfSelections(numberOfFilesSelected, - SINGLE_SELECTION_NAME, - MULTI_SELECTION_NAME); } + setTextBasedOnNumberOfSelections(numberOfFilesSelected, + SINGLE_SELECTION_NAME, + MULTI_SELECTION_NAME); // Disable the menu if md5 have not been computed or if the file size // is empty. Display the appropriate reason to the user. @@ -155,7 +156,26 @@ public final class AddContentToHashDbAction extends AbstractAction implements Pr return; } } + addExistingHashDatabases(selectedFiles); + // Add a "New Hash Set..." menu item. Selecting this item invokes a + // a hash database creation dialog and adds the selected files to the + // the new database. + addSeparator(); + JMenuItem newHashSetItem = new JMenuItem(NbBundle.getMessage(this.getClass(), + "AddContentToHashDbAction.ContentMenu.createDbItem")); + newHashSetItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + HashDb hashDb = new HashDbCreateDatabaseDialog().getHashDatabase(); + if (null != hashDb) { + addFilesToHashSet(selectedFiles, hashDb); + } + } + }); + add(newHashSetItem); + } + private void addExistingHashDatabases(Collection selectedFiles) { // Get the current set of updateable hash databases and add each // one to the menu as a separate menu item. Selecting a hash database // adds the selected files to the selected database. @@ -177,23 +197,6 @@ public final class AddContentToHashDbAction extends AbstractAction implements Pr empty.setEnabled(false); add(empty); } - - // Add a "New Hash Set..." menu item. Selecting this item invokes a - // a hash database creation dialog and adds the selected files to the - // the new database. - addSeparator(); - JMenuItem newHashSetItem = new JMenuItem(NbBundle.getMessage(this.getClass(), - "AddContentToHashDbAction.ContentMenu.createDbItem")); - newHashSetItem.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - HashDb hashDb = new HashDbCreateDatabaseDialog().getHashDatabase(); - if (null != hashDb) { - addFilesToHashSet(selectedFiles, hashDb); - } - } - }); - add(newHashSetItem); } /** From cd7942af8905fc399f94e130466f95aa9c5c21d5 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 22 Oct 2019 17:06:52 -0400 Subject: [PATCH 064/134] 5506 More robust machine trans --- .../ui/TranslatedTextViewer.java | 158 +++++++++--------- 1 file changed, 75 insertions(+), 83 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java index 8c49c23ed7..883b36c2fb 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java @@ -53,6 +53,7 @@ import org.sleuthkit.autopsy.texttranslation.TranslationException; import org.sleuthkit.datamodel.Content; import java.util.List; import java.util.logging.Level; +import javax.swing.SwingUtilities; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.texttranslation.ui.TranslationContentPanel.DisplayDropdownOptions; @@ -63,7 +64,7 @@ import org.sleuthkit.autopsy.texttranslation.ui.TranslationContentPanel.DisplayD @ServiceProvider(service = TextViewer.class, position = 4) public final class TranslatedTextViewer implements TextViewer { - private static final Logger logger = Logger.getLogger(TranslatedTextViewer.class.getName()); + private static final Logger logger = Logger.getLogger(TranslatedTextViewer.class.getName()); private static final boolean OCR_ENABLED = true; private static final boolean OCR_DISABLED = false; @@ -72,7 +73,7 @@ public final class TranslatedTextViewer implements TextViewer { private final TranslationContentPanel panel = new TranslationContentPanel(); private volatile Node node; - private volatile BackgroundTranslationTask updateTask; + private volatile ExtractAndTranslateTextTask backgroundTask; private final ThreadFactory translationThreadFactory = new ThreadFactoryBuilder().setNameFormat("translation-content-viewer-%d").build(); private final ExecutorService executorService = Executors.newSingleThreadExecutor(translationThreadFactory); @@ -95,7 +96,7 @@ public final class TranslatedTextViewer implements TextViewer { panel.addLanguagePackNames(INSTALLED_LANGUAGE_PACKS); } } - + int payloadMaxInKB = TextTranslationService.getInstance().getMaxTextChars() / 1000; panel.setWarningLabelMsg(String.format(Bundle.TranslatedTextViewer_maxPayloadSize(), payloadMaxInKB)); @@ -129,10 +130,10 @@ public final class TranslatedTextViewer implements TextViewer { public void resetComponent() { panel.reset(); this.node = null; - if (updateTask != null) { - updateTask.cancel(true); + if (backgroundTask != null) { + backgroundTask.cancel(true); } - updateTask = null; + backgroundTask = null; } @Override @@ -157,62 +158,62 @@ public final class TranslatedTextViewer implements TextViewer { } /** - * Fetches file text and performs translation. + * Extracts text from a file in the currently selected display node and + * optionally translates it. */ - private class BackgroundTranslationTask extends SwingWorker { + private class ExtractAndTranslateTextTask extends SwingWorker { + + private final AbstractFile file; + private final boolean translateText; + + private ExtractAndTranslateTextTask(AbstractFile file, boolean translateText) { + this.file = file; + this.translateText = translateText; + } @NbBundle.Messages({ - "TranslatedContentViewer.noIndexedTextMsg=Run the Keyword Search Ingest Module to get text for translation.", - "TranslatedContentViewer.textAlreadyIndexed=Please view the original text in the Indexed Text viewer.", - "TranslatedContentViewer.errorMsg=Error encountered while getting file text.", - "TranslatedContentViewer.errorExtractingText=Could not extract text from file.", - "TranslatedContentViewer.translatingText=Translating text, please wait..." + "TranslatedContentViewer.translatingText=Translating text, please wait...", + "TranslatedContentViewer.errorExtractingText=Error encountered while extracting text from file.", + "TranslatedContentViewer.fileHasNoText=File has no text.", + "TranslatedContentViewer.errorTranslatingText=Could not translate text from file." }) @Override public String doInBackground() throws InterruptedException { if (this.isCancelled()) { throw new InterruptedException(); } - String dropdownSelection = panel.getDisplayDropDownSelection(); - if (dropdownSelection.equals(DisplayDropdownOptions.ORIGINAL_TEXT.toString())) { - try { - return getFileText(node); - } catch (IOException ex) { - logger.log(Level.WARNING, "Error getting text", ex); - return Bundle.TranslatedContentViewer_errorMsg(); - } catch (TextExtractor.InitReaderException ex) { - logger.log(Level.WARNING, "Error getting text", ex); - return Bundle.TranslatedContentViewer_errorExtractingText(); - } - } else { - try { - return translate(getFileText(node)); - } catch (IOException ex) { - logger.log(Level.WARNING, "Error translating text", ex); - return Bundle.TranslatedContentViewer_errorMsg(); - } catch (TextExtractor.InitReaderException ex) { - logger.log(Level.WARNING, "Error translating text", ex); - return Bundle.TranslatedContentViewer_errorExtractingText(); - } - } - } + SwingUtilities.invokeLater(() -> { + panel.display(Bundle.TranslatedContentViewer_translatingText(), + ComponentOrientation.LEFT_TO_RIGHT, Font.ITALIC); + }); - /** - * Update the extraction loading message depending on the file type. - * - * @param isImage Boolean indicating if the selecting node is an image - */ - @NbBundle.Messages({"TranslatedContentViewer.extractingImageText=Extracting text from image, please wait...", - "TranslatedContentViewer.extractingFileText=Extracting text from file, please wait...",}) - private void updateExtractionLoadingMessage(boolean isImage) { - if (isImage) { - panel.display(Bundle.TranslatedContentViewer_extractingImageText(), - ComponentOrientation.LEFT_TO_RIGHT, Font.ITALIC); - } else { - panel.display(Bundle.TranslatedContentViewer_extractingFileText(), - ComponentOrientation.LEFT_TO_RIGHT, Font.ITALIC); + String fileText; + try { + fileText = getFileText(file); + } catch (IOException | TextExtractor.InitReaderException ex) { + logger.log(Level.WARNING, String.format("Error getting text for file %s (objId=%d)", file.getName(), file.getId()), ex); + return Bundle.TranslatedContentViewer_errorExtractingText(); } + + if (this.isCancelled()) { + throw new InterruptedException(); + } + + if (fileText == null || fileText.isEmpty()) { + return Bundle.TranslatedContentViewer_fileHasNoText(); + } + + if (this.translateText) { + String translation = translate(fileText); + if (this.isCancelled()) { + throw new InterruptedException(); + } + return translation; + } else { + return fileText; + } + } @Override @@ -227,8 +228,10 @@ public final class TranslatedTextViewer implements TextViewer { String orientDetectSubstring = result.substring(0, maxOrientChars); ComponentOrientation orientation = TextUtil.getTextDirection(orientDetectSubstring); panel.display(result, orientation, Font.PLAIN); - } catch (InterruptedException | ExecutionException | CancellationException ignored) { - //InterruptedException & CancellationException - User cancelled, no error. + } catch (InterruptedException | CancellationException ignored) { + // Task cancelled, no error. + } catch (ExecutionException ex) { + logger.log(Level.WARNING, "Error occurred during background task execution", ex); } } @@ -243,14 +246,7 @@ public final class TranslatedTextViewer implements TextViewer { "TranslatedContentViewer.emptyTranslation=The resulting translation was empty.", "TranslatedContentViewer.noServiceProvider=Machine Translation software was not found.", "TranslatedContentViewer.translationException=Error encountered while attempting translation."}) - private String translate(String input) throws InterruptedException { - if (this.isCancelled()) { - throw new InterruptedException(); - } - - panel.display(Bundle.TranslatedContentViewer_translatingText(), - ComponentOrientation.LEFT_TO_RIGHT, Font.ITALIC); - + private String translate(String input) { try { TextTranslationService translatorInstance = TextTranslationService.getInstance(); String translatedResult = translatorInstance.translate(input); @@ -277,33 +273,22 @@ public final class TranslatedTextViewer implements TextViewer { * @throws InterruptedException * @throws * org.sleuthkit.autopsy.textextractors.TextExtractor.InitReaderException - * @throws NoOpenCoreException - * @throws KeywordSearchModuleException */ - private String getFileText(Node node) throws IOException, + private String getFileText(AbstractFile file) throws IOException, InterruptedException, TextExtractor.InitReaderException { - AbstractFile source = (AbstractFile) DataContentViewerUtility.getDefaultContent(node); - boolean isImage = false; - - if (source != null) { - isImage = source.getMIMEType().toLowerCase().startsWith("image/"); - } - - updateExtractionLoadingMessage(isImage); - + final boolean isImage = file.getMIMEType().toLowerCase().startsWith("image/"); // NON-NLS String result; - if (isImage) { - result = extractText(source, OCR_ENABLED); + result = extractText(file, OCR_ENABLED); } else { - result = extractText(source, OCR_DISABLED); + result = extractText(file, OCR_DISABLED); } //Correct for UTF-8 byte[] resultInUTF8Bytes = result.getBytes("UTF8"); - byte[] trimToArraySize = Arrays.copyOfRange(resultInUTF8Bytes, 0, - Math.min(resultInUTF8Bytes.length, MAX_EXTRACT_SIZE_BYTES) ); + byte[] trimToArraySize = Arrays.copyOfRange(resultInUTF8Bytes, 0, + Math.min(resultInUTF8Bytes.length, MAX_EXTRACT_SIZE_BYTES)); return new String(trimToArraySize, "UTF-8"); } @@ -348,7 +333,7 @@ public final class TranslatedTextViewer implements TextViewer { textBuilder.append(cbuf, 0, read); bytesRead += read; } - + return textBuilder.toString(); } @@ -399,7 +384,7 @@ public final class TranslatedTextViewer implements TextViewer { */ private abstract class SelectionChangeListener implements ActionListener { - public String currentSelection = null; + public String currentSelection; public abstract String getSelection(); @@ -408,14 +393,21 @@ public final class TranslatedTextViewer implements TextViewer { String selection = getSelection(); if (!selection.equals(currentSelection)) { currentSelection = selection; - if (updateTask != null && !updateTask.isDone()) { - updateTask.cancel(true); + + if (backgroundTask != null && !backgroundTask.isDone()) { + backgroundTask.cancel(true); } - updateTask = new BackgroundTranslationTask(); + + AbstractFile file = node.getLookup().lookup(AbstractFile.class); + if (file == null) { + return; + } + boolean translateText = currentSelection.equals(DisplayDropdownOptions.ORIGINAL_TEXT.toString()); + backgroundTask = new ExtractAndTranslateTextTask(file, translateText); //Pass the background task to a single threaded pool to keep //the number of jobs running to one. - executorService.execute(updateTask); + executorService.execute(backgroundTask); } } } From ec58218cb21addcf7139ab51650cf1566b08cf55 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 22 Oct 2019 17:16:11 -0400 Subject: [PATCH 065/134] 5506 More robust machine trans --- .../autopsy/texttranslation/ui/TranslatedTextViewer.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java index 883b36c2fb..ee8d4d96b4 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java @@ -158,8 +158,7 @@ public final class TranslatedTextViewer implements TextViewer { } /** - * Extracts text from a file in the currently selected display node and - * optionally translates it. + * Extracts text from a file and optionally translates it. */ private class ExtractAndTranslateTextTask extends SwingWorker { @@ -183,6 +182,11 @@ public final class TranslatedTextViewer implements TextViewer { throw new InterruptedException(); } + /* + * This message is only written to the viewer once this task starts + * and any previous task has been completed by the single-threaded + * executor. + */ SwingUtilities.invokeLater(() -> { panel.display(Bundle.TranslatedContentViewer_translatingText(), ComponentOrientation.LEFT_TO_RIGHT, Font.ITALIC); From a58de8029febce29f4f454a79d0dc0fe6f7811aa Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Tue, 22 Oct 2019 17:28:13 -0400 Subject: [PATCH 066/134] Cleaned up skype queries and tested them --- InternalPythonModules/android/skype.py | 141 ++++++++----------------- 1 file changed, 45 insertions(+), 96 deletions(-) diff --git a/InternalPythonModules/android/skype.py b/InternalPythonModules/android/skype.py index 5044898d5f..d8b79ac7fe 100644 --- a/InternalPythonModules/android/skype.py +++ b/InternalPythonModules/android/skype.py @@ -76,11 +76,8 @@ class SkypeAnalyzer(general.AndroidComponentAnalyzer): as they would be excluded in the join. Since the chatItem table stores both the group id or skype_id in one column, an implementation decision was made to union the person and particiapnt table together so that all rows are matched in one join - with chatItem. This result is consistently labeled contact_list_with_groups in the + with chatItem. This result is consistently labeled contact_book_w_groups in the following queries. - - In order to keep the formatting of the name consistent throughout each query, - a _format_user_name() function was created to encapsulate the CASE statement - that was being shared across them. Refer to the method for more details. """ def __init__(self): @@ -93,7 +90,12 @@ class SkypeAnalyzer(general.AndroidComponentAnalyzer): account_query_result = skype_db.runQuery( """ SELECT entry_id, - """+_format_user_name()+""" AS name + CASE + WHEN Ifnull(first_name, "") == "" AND Ifnull(last_name, "") == "" THEN entry_id + WHEN first_name is NULL THEN replace(last_name, ",", "") + WHEN last_name is NULL THEN replace(first_name, ",", "") + ELSE replace(first_name, ",", "") || " " || replace(last_name, ",", "") + END AS name FROM user """ ) @@ -251,14 +253,6 @@ class SkypeCallLogsParser(TskCallLogsParser): def __init__(self, calllog_db): """ - Big picture: - The query below creates a contacts_list_with_groups table, which - represents the recipient info. A chatItem record holds ids for - both the recipient and sender. The first join onto chatItem fills - in the blanks for the recipients. The second join back onto person - handles the sender info. The result is a table with all of the - communication details. - Implementation details: - message_type w/ value 3 appeared to be the call type, regardless of if it was audio or video. @@ -266,37 +260,23 @@ class SkypeCallLogsParser(TskCallLogsParser): """ super(SkypeCallLogsParser, self).__init__(calllog_db.runQuery( """ - SELECT contacts_list_with_groups.conversation_id, - contacts_list_with_groups.participant_ids, - contacts_list_with_groups.participants, - time, - duration, - is_sender_me, - person_id as sender_id, - sender_name.name as sender_name + SELECT contact_book_w_groups.conversation_id, + contact_book_w_groups.participant_ids, + messages.time, + messages.duration, + messages.is_sender_me, + messages.person_id AS sender_id FROM (SELECT conversation_id, - Group_concat(person_id) AS participant_ids, - Group_concat("""+_format_user_name()+""") AS participants - FROM particiapnt AS PART - JOIN person AS P - ON PART.person_id = P.entry_id + Group_concat(person_id) AS participant_ids + FROM particiapnt GROUP BY conversation_id UNION - SELECT entry_id, - NULL, - """+_format_user_name()+""" AS participant - FROM person) AS contacts_list_with_groups - JOIN chatitem AS C - ON C.conversation_link = contacts_list_with_groups.conversation_id - JOIN (SELECT entry_id as id, - """+_format_user_name()+""" AS name - FROM person - UNION - SELECT entry_id as id, - """+_format_user_name()+""" AS name - FROM user) AS sender_name - ON sender_name.id = C.person_id - WHERE message_type == 3 + SELECT entry_id AS conversation_id, + NULL + FROM person) AS contact_book_w_groups + join chatitem AS messages + ON messages.conversation_link = contact_book_w_groups.conversation_id + WHERE message_type == 3 """ ) ) @@ -347,7 +327,12 @@ class SkypeContactsParser(TskContactsParser): super(SkypeContactsParser, self).__init__(contact_db.runQuery( """ SELECT entry_id, - """+_format_user_name()+""" AS name + CASE + WHEN Ifnull(first_name, "") == "" AND Ifnull(last_name, "") == "" THEN entry_id + WHEN first_name is NULL THEN replace(last_name, ",", "") + WHEN last_name is NULL THEN replace(first_name, ",", "") + ELSE replace(first_name, ",", "") || " " || replace(last_name, ",", "") + END AS name FROM person """ ) @@ -379,39 +364,25 @@ class SkypeMessagesParser(TskMessagesParser): """ super(SkypeMessagesParser, self).__init__(message_db.runQuery( """ - SELECT contacts_list_with_groups.conversation_id, - contacts_list_with_groups.participant_ids, - contacts_list_with_groups.participants, - time, - content, - device_gallery_path, - is_sender_me, - person_id as sender_id, - sender_name.name AS sender_name - FROM (SELECT conversation_id, - Group_concat(person_id) AS participant_ids, - Group_concat("""+_format_user_name()+""") AS participants - FROM particiapnt AS PART - JOIN person AS P - ON PART.person_id = P.entry_id - GROUP BY conversation_id - UNION - SELECT entry_id as conversation_id, - NULL, - """+_format_user_name()+""" AS participant - FROM person) AS contacts_list_with_groups - JOIN chatitem AS C - ON C.conversation_link = contacts_list_with_groups.conversation_id - JOIN (SELECT entry_id as id, - """+_format_user_name()+""" AS name - FROM person - UNION - SELECT entry_id as id, - """+_format_user_name()+""" AS name - FROM user) AS sender_name - ON sender_name.id = C.person_id + SELECT contact_book_w_groups.conversation_id, + contact_book_w_groups.participant_ids, + messages.time, + messages.content, + messages.device_gallery_path, + messages.is_sender_me, + messages.person_id as sender_id + FROM (SELECT conversation_id, + Group_concat(person_id) AS participant_ids + FROM particiapnt + GROUP BY conversation_id + UNION + SELECT entry_id as conversation_id, + NULL + FROM person) AS contact_book_w_groups + JOIN chatitem AS messages + ON messages.conversation_link = contact_book_w_groups.conversation_id WHERE message_type != 3 - """ + """ ) ) self._SKYPE_MESSAGE_TYPE = "Skype Message" @@ -469,25 +440,3 @@ class SkypeMessagesParser(TskMessagesParser): if group_ids is not None: return self.result_set.getString("conversation_id") return super(SkypeMessagesParser, self).get_thread_id() - -def _format_user_name(): - """ - This CASE SQL statement is used in many queries to - format the names of users. For a user, there is a first_name - column and a last_name column. Some of these columns can be null - and our goal is to produce the cleanest data possible. In the event - that both the first and last name columns are null, we return the skype_id - which is stored in the database as 'entry_id'. Commas are removed from the name - so that we can concatenate names into a comma seperate list for group chats. - """ - - return """ - CASE - WHEN Ifnull(first_name, "") == "" AND Ifnull(last_name, "") == "" THEN entry_id - WHEN first_name is NULL THEN replace(last_name, ",", "") - WHEN last_name is NULL THEN replace(first_name, ",", "") - ELSE replace(first_name, ",", "") || " " || replace(last_name, ",", "") - END - """ - - From 1acd1ccfd1c81d2d6137d0fbd398cb095dff5040 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 22 Oct 2019 17:30:35 -0400 Subject: [PATCH 067/134] 5506 More robust machine trans --- .../ui/Bundle.properties-MERGED | 16 ++++++------- .../ui/TranslatedTextViewer.java | 23 +++++++++---------- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/Bundle.properties-MERGED index 141ca2f7ef..bbf7046f47 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/Bundle.properties-MERGED @@ -2,16 +2,14 @@ OptionsCategory_Name_Machine_Translation=Machine Translation OptionsCategory_Keywords_Machine_Translation_Settings=Machine Translation Settings TranslatedContentPanel.comboBoxOption.originalText=Original Text (Up to 25KB) TranslatedContentPanel.comboBoxOption.translatedText=Translated Text -TranslatedContentViewer.emptyTranslation=The resulting translation was empty. -TranslatedContentViewer.errorExtractingText=Could not extract text from file. -TranslatedContentViewer.errorMsg=Error encountered while getting file text. -TranslatedContentViewer.extractingFileText=Extracting text from file, please wait... -TranslatedContentViewer.extractingImageText=Extracting text from image, please wait... -TranslatedContentViewer.noIndexedTextMsg=Run the Keyword Search Ingest Module to get text for translation. -TranslatedContentViewer.noServiceProvider=Machine Translation software was not found. -TranslatedContentViewer.textAlreadyIndexed=Please view the original text in the Indexed Text viewer. +TranslatedContentViewer.emptyTranslation=The translation is empty. +TranslatedContentViewer.errorExtractingText=Error encountered while extracting text from file. +TranslatedContentViewer.errorTranslatingText=Could not translate text from file. +TranslatedContentViewer.fileHasNoText=File has no text. +TranslatedContentViewer.noServiceProvider=Machine translation software was not found. TranslatedContentViewer.translatingText=Translating text, please wait... -TranslatedContentViewer.translationException=Error encountered while attempting translation. +# {0} - exception message +TranslatedContentViewer.translationException=Error encountered while attempting translation ({0}). TranslatedTextViewer.maxPayloadSize=Up to the first %dKB of text will be translated TranslatedTextViewer.title=Translation TranslatedTextViewer.toolTip=Displays translated file text. diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java index ee8d4d96b4..035d9f6398 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java @@ -188,8 +188,7 @@ public final class TranslatedTextViewer implements TextViewer { * executor. */ SwingUtilities.invokeLater(() -> { - panel.display(Bundle.TranslatedContentViewer_translatingText(), - ComponentOrientation.LEFT_TO_RIGHT, Font.ITALIC); + panel.display(Bundle.TranslatedContentViewer_translatingText(), ComponentOrientation.LEFT_TO_RIGHT, Font.ITALIC); }); String fileText; @@ -235,7 +234,7 @@ public final class TranslatedTextViewer implements TextViewer { } catch (InterruptedException | CancellationException ignored) { // Task cancelled, no error. } catch (ExecutionException ex) { - logger.log(Level.WARNING, "Error occurred during background task execution", ex); + logger.log(Level.WARNING, String.format("Error occurred during background task execution for file %s (objId=%d)", file.getName(), file.getId()), ex); } } @@ -247,9 +246,9 @@ public final class TranslatedTextViewer implements TextViewer { * @return Translated text or error message */ @NbBundle.Messages({ - "TranslatedContentViewer.emptyTranslation=The resulting translation was empty.", - "TranslatedContentViewer.noServiceProvider=Machine Translation software was not found.", - "TranslatedContentViewer.translationException=Error encountered while attempting translation."}) + "TranslatedContentViewer.emptyTranslation=The translation is empty.", + "TranslatedContentViewer.noServiceProvider=Machine translation software was not found.", + "# {0} - exception message", "TranslatedContentViewer.translationException=Error encountered while attempting translation ({0})."}) private String translate(String input) { try { TextTranslationService translatorInstance = TextTranslationService.getInstance(); @@ -261,8 +260,8 @@ public final class TranslatedTextViewer implements TextViewer { } catch (NoServiceProviderException ex) { return Bundle.TranslatedContentViewer_noServiceProvider(); } catch (TranslationException ex) { - logger.log(Level.WARNING, "Error translating text", ex); - return Bundle.TranslatedContentViewer_translationException() + " (" + ex.getMessage() + ")"; + logger.log(Level.WARNING, String.format("Error occurred translating text for file %s (objId=%d)", file.getName(), file.getId()), ex); + return Bundle.TranslatedContentViewer_translationException(ex.getMessage()); } } @@ -388,9 +387,9 @@ public final class TranslatedTextViewer implements TextViewer { */ private abstract class SelectionChangeListener implements ActionListener { - public String currentSelection; + private String currentSelection; - public abstract String getSelection(); + abstract String getSelection(); @Override public final void actionPerformed(ActionEvent e) { @@ -422,7 +421,7 @@ public final class TranslatedTextViewer implements TextViewer { private class DisplayDropDownChangeListener extends SelectionChangeListener { @Override - public String getSelection() { + String getSelection() { return panel.getDisplayDropDownSelection(); } } @@ -433,7 +432,7 @@ public final class TranslatedTextViewer implements TextViewer { private class OCRDropdownChangeListener extends SelectionChangeListener { @Override - public String getSelection() { + String getSelection() { return panel.getSelectedOcrLanguagePack(); } } From 3d19a07ba4bd75d4e7150216d492a1e5d193fdfa Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 22 Oct 2019 17:48:58 -0400 Subject: [PATCH 068/134] 5506 More robust machine trans --- .../ui/Bundle.properties-MERGED | 4 +- .../ui/TranslatedTextViewer.java | 59 ++++++++++--------- 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/Bundle.properties-MERGED index bbf7046f47..cfa9af0fb7 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/Bundle.properties-MERGED @@ -3,8 +3,8 @@ OptionsCategory_Keywords_Machine_Translation_Settings=Machine Translation Settin TranslatedContentPanel.comboBoxOption.originalText=Original Text (Up to 25KB) TranslatedContentPanel.comboBoxOption.translatedText=Translated Text TranslatedContentViewer.emptyTranslation=The translation is empty. -TranslatedContentViewer.errorExtractingText=Error encountered while extracting text from file. -TranslatedContentViewer.errorTranslatingText=Could not translate text from file. +# {0} - exception message +TranslatedContentViewer.errorExtractingText=Error encountered while extracting text from file ({0}). TranslatedContentViewer.fileHasNoText=File has no text. TranslatedContentViewer.noServiceProvider=Machine translation software was not found. TranslatedContentViewer.translatingText=Translating text, please wait... diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java index 035d9f6398..b6490b70bd 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java @@ -172,9 +172,11 @@ public final class TranslatedTextViewer implements TextViewer { @NbBundle.Messages({ "TranslatedContentViewer.translatingText=Translating text, please wait...", - "TranslatedContentViewer.errorExtractingText=Error encountered while extracting text from file.", + "# {0} - exception message", "TranslatedContentViewer.errorExtractingText=Error encountered while extracting text from file ({0}).", "TranslatedContentViewer.fileHasNoText=File has no text.", - "TranslatedContentViewer.errorTranslatingText=Could not translate text from file." + "TranslatedContentViewer.emptyTranslation=The translation is empty.", + "TranslatedContentViewer.noServiceProvider=Machine translation software was not found.", + "# {0} - exception message", "TranslatedContentViewer.translationException=Error encountered while attempting translation ({0})." }) @Override public String doInBackground() throws InterruptedException { @@ -184,8 +186,8 @@ public final class TranslatedTextViewer implements TextViewer { /* * This message is only written to the viewer once this task starts - * and any previous task has been completed by the single-threaded - * executor. + * and any previous task has therefore been completed by the + * single-threaded executor. */ SwingUtilities.invokeLater(() -> { panel.display(Bundle.TranslatedContentViewer_translatingText(), ComponentOrientation.LEFT_TO_RIGHT, Font.ITALIC); @@ -196,7 +198,7 @@ public final class TranslatedTextViewer implements TextViewer { fileText = getFileText(file); } catch (IOException | TextExtractor.InitReaderException ex) { logger.log(Level.WARNING, String.format("Error getting text for file %s (objId=%d)", file.getName(), file.getId()), ex); - return Bundle.TranslatedContentViewer_errorExtractingText(); + return Bundle.TranslatedContentViewer_errorExtractingText(ex.getMessage()); } if (this.isCancelled()) { @@ -207,16 +209,26 @@ public final class TranslatedTextViewer implements TextViewer { return Bundle.TranslatedContentViewer_fileHasNoText(); } - if (this.translateText) { - String translation = translate(fileText); - if (this.isCancelled()) { - throw new InterruptedException(); - } - return translation; - } else { + if (!this.translateText) { return fileText; } + String translation; + try { + translation = translate(fileText); + } catch (NoServiceProviderException ex) { + logger.log(Level.WARNING, String.format("Error occurred translating text for file %s (objId=%d)", file.getName(), file.getId()), ex); + translation = Bundle.TranslatedContentViewer_noServiceProvider(); + } catch (TranslationException ex) { + logger.log(Level.WARNING, String.format("Error occurred translating text for file %s (objId=%d)", file.getName(), file.getId()), ex); + translation = Bundle.TranslatedContentViewer_translationException(ex.getMessage()); + } + + if (this.isCancelled()) { + throw new InterruptedException(); + } + + return translation; } @Override @@ -245,24 +257,13 @@ public final class TranslatedTextViewer implements TextViewer { * * @return Translated text or error message */ - @NbBundle.Messages({ - "TranslatedContentViewer.emptyTranslation=The translation is empty.", - "TranslatedContentViewer.noServiceProvider=Machine translation software was not found.", - "# {0} - exception message", "TranslatedContentViewer.translationException=Error encountered while attempting translation ({0})."}) - private String translate(String input) { - try { - TextTranslationService translatorInstance = TextTranslationService.getInstance(); - String translatedResult = translatorInstance.translate(input); - if (translatedResult.isEmpty()) { - return Bundle.TranslatedContentViewer_emptyTranslation(); - } - return translatedResult; - } catch (NoServiceProviderException ex) { - return Bundle.TranslatedContentViewer_noServiceProvider(); - } catch (TranslationException ex) { - logger.log(Level.WARNING, String.format("Error occurred translating text for file %s (objId=%d)", file.getName(), file.getId()), ex); - return Bundle.TranslatedContentViewer_translationException(ex.getMessage()); + private String translate(String input) throws NoServiceProviderException, TranslationException { + TextTranslationService translatorInstance = TextTranslationService.getInstance(); + String translatedResult = translatorInstance.translate(input); + if (translatedResult.isEmpty()) { + return Bundle.TranslatedContentViewer_emptyTranslation(); } + return translatedResult; } /** From 5a9c630d4b3284de7f0564c73496a99763e16b6f Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 22 Oct 2019 17:50:19 -0400 Subject: [PATCH 069/134] 5506 More robust machine trans --- .../autopsy/texttranslation/ui/TranslatedTextViewer.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java index b6490b70bd..65d65f4ad5 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java @@ -176,7 +176,7 @@ public final class TranslatedTextViewer implements TextViewer { "TranslatedContentViewer.fileHasNoText=File has no text.", "TranslatedContentViewer.emptyTranslation=The translation is empty.", "TranslatedContentViewer.noServiceProvider=Machine translation software was not found.", - "# {0} - exception message", "TranslatedContentViewer.translationException=Error encountered while attempting translation ({0})." + "# {0} - exception message", "TranslatedContentViewer.translationException=Error encountered while translating file ({0})." }) @Override public String doInBackground() throws InterruptedException { @@ -243,6 +243,7 @@ public final class TranslatedTextViewer implements TextViewer { String orientDetectSubstring = result.substring(0, maxOrientChars); ComponentOrientation orientation = TextUtil.getTextDirection(orientDetectSubstring); panel.display(result, orientation, Font.PLAIN); + } catch (InterruptedException | CancellationException ignored) { // Task cancelled, no error. } catch (ExecutionException ex) { From 0de0c7d852bae5dfba0e0a085e494202a458e62b Mon Sep 17 00:00:00 2001 From: esaunders Date: Tue, 22 Oct 2019 17:53:07 -0400 Subject: [PATCH 070/134] Add note about location of Options dialog on Mac. --- docs/doxygen-user/main.dox | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/doxygen-user/main.dox b/docs/doxygen-user/main.dox index 122e14fb0f..61d555ace4 100644 --- a/docs/doxygen-user/main.dox +++ b/docs/doxygen-user/main.dox @@ -6,6 +6,8 @@ Overview This is the User's Guide for the open source Autopsy platform. Autopsy allows you to examine a hard drive or mobile device and recover evidence from it. This guide should help you with using Autopsy. The developer's guide will help you develop your own Autopsy modules. +Note: For those users running Autopsy on Mac devices, the functionality available through the "Tools" -> "Options" dialog as described in this documentation can be accessed through the system menu bar under "Preferences" or through the Cmd + , (command-comma) shortcut. + Help Topics ------- The following topics are available here: From 9e01f63d5008b18d90a8fa360cfa36fb1760a412 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 22 Oct 2019 17:53:42 -0400 Subject: [PATCH 071/134] 5506 More robust machine trans --- .../autopsy/texttranslation/ui/TranslatedTextViewer.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java index 65d65f4ad5..811dc229cc 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java @@ -248,6 +248,7 @@ public final class TranslatedTextViewer implements TextViewer { // Task cancelled, no error. } catch (ExecutionException ex) { logger.log(Level.WARNING, String.format("Error occurred during background task execution for file %s (objId=%d)", file.getName(), file.getId()), ex); + panel.display(Bundle.TranslatedContentViewer_translationException(ex.getMessage()), ComponentOrientation.LEFT_TO_RIGHT, Font.ITALIC); } } From 9efea1c6a3a6bfbf4ec012e5b3202e1c90604a7c Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Wed, 23 Oct 2019 10:23:51 -0400 Subject: [PATCH 072/134] codacy fix --- .../sleuthkit/autopsy/geolocation/datamodel/AttributeUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/AttributeUtils.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/AttributeUtils.java index 5892733350..c757c63c28 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/AttributeUtils.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/AttributeUtils.java @@ -26,7 +26,7 @@ import org.sleuthkit.datamodel.TskCoreException; * Utilities for simplifying and reducing redundant when getting Artifact * attributes. */ -public class AttributeUtils { +final class AttributeUtils { /** * Private constructor for this Utility class. From 08ccb2a9c95dd0ff1decac9124318ab8a5fce53a Mon Sep 17 00:00:00 2001 From: Mark McKinnon Date: Wed, 23 Oct 2019 11:39:13 -0400 Subject: [PATCH 073/134] Update EmailMessage.java If messageid is null then set it to "" in setMessageID --- .../thunderbirdparser/EmailMessage.java | 74 ++++++++++--------- 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessage.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessage.java index 40f2fc0933..09a6637e6e 100644 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessage.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/EmailMessage.java @@ -83,7 +83,7 @@ class EmailMessage { void setSubject(String subject) { if (subject != null) { this.subject = subject; - if(subject.matches("^[R|r][E|e].*?:.*")) { + if (subject.matches("^[R|r][E|e].*?:.*")) { this.simplifiedSubject = subject.replaceAll("[R|r][E|e].*?:", "").trim(); replySubject = true; } else { @@ -93,19 +93,19 @@ class EmailMessage { this.simplifiedSubject = ""; } } - + /** * Returns the orginal subject with the "RE:" stripped off". - * + * * @return Message subject with the "RE" stripped off */ String getSimplifiedSubject() { return simplifiedSubject; } - + /** * Returns whether or not the message subject started with "RE:" - * + * * @return true if the original subject started with RE otherwise false. */ boolean isReplySubject() { @@ -121,6 +121,7 @@ class EmailMessage { this.headers = headers; } } + String getTextBody() { return textBody; } @@ -211,75 +212,80 @@ class EmailMessage { this.localPath = localPath; } } - + /** - * Returns the value of the Message-ID header field of this message or - * empty string if it is not present. - * + * Returns the value of the Message-ID header field of this message or empty + * string if it is not present. + * * @return the identifier of this message. */ String getMessageID() { return messageID; } - + /** * Sets the identifier of this message. - * + * * @param messageID identifer of this message */ void setMessageID(String messageID) { - this.messageID = messageID; + if (messageID != null) { + this.messageID = messageID; + } else { + this.messageID = ""; + } } - + /** - * Returns the messageID of the parent message or empty String if not present. - * + * Returns the messageID of the parent message or empty String if not + * present. + * * @return the idenifier of the message parent */ String getInReplyToID() { return inReplyToID; } - + /** * Sets the messageID of the parent message. - * + * * @param inReplyToID messageID of the parent message. */ void setInReplyToID(String inReplyToID) { this.inReplyToID = inReplyToID; } - + /** - * Returns a list of Message-IDs listing the parent, grandparent, - * great-grandparent, and so on, of this message. - * + * Returns a list of Message-IDs listing the parent, grandparent, + * great-grandparent, and so on, of this message. + * * @return The reference list or empty string if none is available. */ List getReferences() { return references; } - + /** * Set the list of reference message-IDs from the email message header. - * - * @param references + * + * @param references */ void setReferences(List references) { this.references = references; } - + /** * Sets the ThreadID of this message. - * + * * @param threadID - the thread ID to set */ void setMessageThreadID(String threadID) { this.messageThreadID = threadID; } - + /** * Returns the ThreadID for this message. - * + * * @return - the message thread ID or "" is non is available */ String getMessageThreadID() { @@ -308,7 +314,7 @@ class EmailMessage { private long aTime = 0L; private long mTime = 0L; - + private TskData.EncodingType encodingType = TskData.EncodingType.NONE; String getName() { @@ -394,14 +400,14 @@ class EmailMessage { this.mTime = mTime.getTime() / 1000; } } - - void setEncodingType(TskData.EncodingType encodingType){ + + void setEncodingType(TskData.EncodingType encodingType) { this.encodingType = encodingType; } - - TskData.EncodingType getEncodingType(){ + + TskData.EncodingType getEncodingType() { return encodingType; } - + } } From 0b8ea73f7f52f3968a90aaab94b7a7c0a6cefd98 Mon Sep 17 00:00:00 2001 From: Mark McKinnon Date: Wed, 23 Oct 2019 11:45:25 -0400 Subject: [PATCH 074/134] Update ThunderbirdMboxFileIngestModule.java Revert changes from prior commit. --- .../ThunderbirdMboxFileIngestModule.java | 162 +++++++++--------- 1 file changed, 81 insertions(+), 81 deletions(-) diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java index 91bf391e63..5c42269a8a 100644 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java @@ -65,13 +65,12 @@ import org.sleuthkit.datamodel.TskException; * structure and metadata. */ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { - private static final Logger logger = Logger.getLogger(ThunderbirdMboxFileIngestModule.class.getName()); private final IngestServices services = IngestServices.getInstance(); private FileManager fileManager; private IngestJobContext context; private Blackboard blackboard; - + private Case currentCase; /** @@ -81,7 +80,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { } @Override - @Messages({"ThunderbirdMboxFileIngestModule.noOpenCase.errMsg=Exception while getting open case."}) + @Messages ({"ThunderbirdMboxFileIngestModule.noOpenCase.errMsg=Exception while getting open case."}) public void startUp(IngestJobContext context) throws IngestModuleException { this.context = context; try { @@ -104,8 +103,8 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { } //skip unalloc - if ((abstractFile.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS)) - || (abstractFile.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK))) { + if ((abstractFile.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS)) || + (abstractFile.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK))) { return ProcessResult.OK; } @@ -116,7 +115,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { // check its signature boolean isMbox = false; boolean isEMLFile = false; - + try { byte[] t = new byte[64]; if (abstractFile.getSize() > 64) { @@ -133,7 +132,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { if (isMbox) { return processMBox(abstractFile); } - + if (isEMLFile) { return processEMLFile(abstractFile); } @@ -141,7 +140,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { if (PstParser.isPstFile(abstractFile)) { return processPst(abstractFile); } - + if (VcardParser.isVcardFile(abstractFile)) { return processVcard(abstractFile); } @@ -161,7 +160,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { String fileName; try { fileName = getTempPath() + File.separator + abstractFile.getName() - + "-" + String.valueOf(abstractFile.getId()); + + "-" + String.valueOf(abstractFile.getId()); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS return ProcessResult.ERROR; @@ -189,11 +188,11 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { PstParser parser = new PstParser(services); PstParser.ParseResult result = parser.open(file, abstractFile.getId()); - switch (result) { + switch( result) { case OK: Iterator pstMsgIterator = parser.getEmailMessageIterator(); if (pstMsgIterator != null) { - processEmails(parser.getPartialEmailMessages(), pstMsgIterator, abstractFile); + processEmails(parser.getPartialEmailMessages(), pstMsgIterator , abstractFile); } else { // sometimes parser returns ParseResult=OK but there are no messages postErrorMessage( @@ -274,7 +273,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { String fileName; try { fileName = getTempPath() + File.separator + abstractFile.getName() - + "-" + String.valueOf(abstractFile.getId()); + + "-" + String.valueOf(abstractFile.getId()); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS return ProcessResult.ERROR; @@ -299,16 +298,16 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { return ProcessResult.OK; } - MboxParser emailIterator = MboxParser.getEmailIterator(emailFolder, file, abstractFile.getId()); + MboxParser emailIterator = MboxParser.getEmailIterator( emailFolder, file, abstractFile.getId()); List emails = new ArrayList<>(); - if (emailIterator != null) { - while (emailIterator.hasNext()) { + if(emailIterator != null) { + while(emailIterator.hasNext()) { EmailMessage emailMessage = emailIterator.next(); - if (emailMessage != null) { + if(emailMessage != null) { emails.add(emailMessage); } } - + String errors = emailIterator.getErrors(); if (!errors.isEmpty()) { postErrorMessage( @@ -316,7 +315,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { abstractFile.getName()), errors); } } - processEmails(emails, MboxParser.getEmailIterator(emailFolder, file, abstractFile.getId()), abstractFile); + processEmails(emails, MboxParser.getEmailIterator( emailFolder, file, abstractFile.getId()), abstractFile); if (file.delete() == false) { logger.log(Level.INFO, "Failed to delete temp file: {0}", file.getName()); //NON-NLS @@ -324,7 +323,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { return ProcessResult.OK; } - + /** * Parse and extract data from a vCard file. * @@ -348,8 +347,8 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { } return ProcessResult.OK; } - - private ProcessResult processEMLFile(AbstractFile abstractFile) { + + private ProcessResult processEMLFile(AbstractFile abstractFile) { try { EmailMessage message = EMLParser.parse(abstractFile); @@ -401,7 +400,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { /** * Get a module output folder. - * + * * @throws NoCurrentCaseException if there is no open case. * * @return the module output folder @@ -436,39 +435,38 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { * @param abstractFile */ private void processEmails(List partialEmailsForThreading, Iterator fullMessageIterator, AbstractFile abstractFile) { - + // Putting try/catch around this to catch any exception and still allow // the creation of the artifacts to continue. - try { + try{ EmailMessageThreader.threadMessages(partialEmailsForThreading); - } catch (Exception ex) { + } catch(Exception ex) { logger.log(Level.WARNING, String.format("Exception thrown parsing emails from %s", abstractFile.getName()), ex); } - + List derivedFiles = new ArrayList<>(); int msgCnt = 0; - while (fullMessageIterator.hasNext()) { + while(fullMessageIterator.hasNext()) { EmailMessage current = fullMessageIterator.next(); - - if (current == null) { + + if(current == null) { continue; } - if (partialEmailsForThreading.size() > msgCnt) { + if(partialEmailsForThreading.size() > msgCnt) { EmailMessage threaded = partialEmailsForThreading.get(msgCnt++); - - if ((threaded.getMessageID() != null) && - (threaded.getMessageID().equals(current.getMessageID()) - && threaded.getSubject().equals(current.getSubject()))) { - current.setMessageThreadID(threaded.getMessageThreadID()); - } + + if(threaded.getMessageID().equals(current.getMessageID()) && + threaded.getSubject().equals(current.getSubject())) { + current.setMessageThreadID(threaded.getMessageThreadID()); + } } - + BlackboardArtifact msgArtifact = addEmailArtifact(current, abstractFile); - - if ((msgArtifact != null) && (current.hasAttachment())) { - derivedFiles.addAll(handleAttachments(current.getAttachments(), abstractFile, msgArtifact)); + + if ((msgArtifact != null) && (current.hasAttachment())) { + derivedFiles.addAll(handleAttachments(current.getAttachments(), abstractFile, msgArtifact )); } } @@ -479,7 +477,6 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { } context.addFilesToJob(derivedFiles); } - /** * Add the given attachments as derived files and reschedule them for * ingest. @@ -520,30 +517,29 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { } /** - * Finds and returns a set of unique email addresses found in the input - * string + * Finds and returns a set of unique email addresses found in the input string * * @param input - input string, like the To/CC line from an email header - * + * * @return Set: set of email addresses found in the input string */ private Set findEmailAddresess(String input) { Pattern p = Pattern.compile("\\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}\\b", - Pattern.CASE_INSENSITIVE); + Pattern.CASE_INSENSITIVE); Matcher m = p.matcher(input); Set emailAddresses = new HashSet<>(); while (m.find()) { - emailAddresses.add(m.group()); + emailAddresses.add( m.group()); } return emailAddresses; } - + /** * Add a blackboard artifact for the given e-mail message. * * @param email The e-mail message. * @param abstractFile The associated file. - * + * * @return The generated e-mail message artifact. */ @Messages({"ThunderbirdMboxFileIngestModule.addArtifact.indexError.message=Failed to index email message detected artifact for keyword search."}) @@ -567,69 +563,73 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { List senderAddressList = new ArrayList<>(); String senderAddress; senderAddressList.addAll(findEmailAddresess(from)); - + AccountFileInstance senderAccountInstance = null; if (senderAddressList.size() == 1) { senderAddress = senderAddressList.get(0); try { senderAccountInstance = currentCase.getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, senderAddress, EmailParserModuleFactory.getModuleName(), abstractFile); - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Failed to create account for email address " + senderAddress, ex); //NON-NLS } - } else { - logger.log(Level.WARNING, "Failed to find sender address, from = {0}", from); //NON-NLS + catch(TskCoreException ex) { + logger.log(Level.WARNING, "Failed to create account for email address " + senderAddress, ex); //NON-NLS + } } - + else { + logger.log(Level.WARNING, "Failed to find sender address, from = {0}", from); //NON-NLS + } + List recipientAddresses = new ArrayList<>(); recipientAddresses.addAll(findEmailAddresess(to)); recipientAddresses.addAll(findEmailAddresess(cc)); recipientAddresses.addAll(findEmailAddresess(bcc)); - + List recipientAccountInstances = new ArrayList<>(); recipientAddresses.forEach((addr) -> { try { - AccountFileInstance recipientAccountInstance - = currentCase.getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, addr, - EmailParserModuleFactory.getModuleName(), abstractFile); + AccountFileInstance recipientAccountInstance = + currentCase.getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, addr, + EmailParserModuleFactory.getModuleName(), abstractFile); recipientAccountInstances.add(recipientAccountInstance); - } catch (TskCoreException ex) { + } + catch(TskCoreException ex) { logger.log(Level.WARNING, "Failed to create account for email address " + addr, ex); //NON-NLS } }); - + addArtifactAttribute(headers, ATTRIBUTE_TYPE.TSK_HEADERS, bbattributes); addArtifactAttribute(from, ATTRIBUTE_TYPE.TSK_EMAIL_FROM, bbattributes); addArtifactAttribute(to, ATTRIBUTE_TYPE.TSK_EMAIL_TO, bbattributes); addArtifactAttribute(subject, ATTRIBUTE_TYPE.TSK_SUBJECT, bbattributes); - + addArtifactAttribute(dateL, ATTRIBUTE_TYPE.TSK_DATETIME_RCVD, bbattributes); addArtifactAttribute(dateL, ATTRIBUTE_TYPE.TSK_DATETIME_SENT, bbattributes); - + addArtifactAttribute(body, ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_PLAIN, bbattributes); - - addArtifactAttribute(((id < 0L) ? NbBundle.getMessage(this.getClass(), "ThunderbirdMboxFileIngestModule.notAvail") : String.valueOf(id)), + + addArtifactAttribute(((id < 0L) ? NbBundle.getMessage(this.getClass(), "ThunderbirdMboxFileIngestModule.notAvail") : String.valueOf(id)), ATTRIBUTE_TYPE.TSK_MSG_ID, bbattributes); - - addArtifactAttribute(((localPath.isEmpty() == false) ? localPath : ""), + + addArtifactAttribute(((localPath.isEmpty() == false) ? localPath : ""), ATTRIBUTE_TYPE.TSK_PATH, bbattributes); - + addArtifactAttribute(cc, ATTRIBUTE_TYPE.TSK_EMAIL_CC, bbattributes); addArtifactAttribute(bodyHTML, ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_HTML, bbattributes); addArtifactAttribute(rtf, ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_RTF, bbattributes); addArtifactAttribute(threadID, ATTRIBUTE_TYPE.TSK_THREAD_ID, bbattributes); - + + try { - + bbart = abstractFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG); bbart.addAttributes(bbattributes); // Add account relationships - currentCase.getSleuthkitCase().getCommunicationsManager().addRelationships(senderAccountInstance, recipientAccountInstances, bbart, Relationship.Type.MESSAGE, dateL); - + currentCase.getSleuthkitCase().getCommunicationsManager().addRelationships(senderAccountInstance, recipientAccountInstances, bbart,Relationship.Type.MESSAGE, dateL); + try { // index the artifact for keyword search - blackboard.postArtifact(bbart, EmailParserModuleFactory.getModuleName()); + blackboard.postArtifact(bbart, EmailParserModuleFactory.getModuleName()); } catch (Blackboard.BlackboardException ex) { logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bbart.getArtifactID(), ex); //NON-NLS MessageNotifyUtil.Notify.error(Bundle.ThunderbirdMboxFileIngestModule_addArtifact_indexError_message(), bbart.getDisplayName()); @@ -640,11 +640,11 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { return bbart; } - + /** * Add an attribute of a specified type to a supplied Collection. - * - * @param stringVal The attribute value. + * + * @param stringVal The attribute value. * @param attrType The type of attribute to be added. * @param bbattributes The Collection to which the attribute will be added. */ @@ -656,7 +656,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { /** * Add an attribute of a specified type to a supplied Collection. - * + * * @param stringVal The attribute value. * @param attrType The type of attribute to be added. * @param bbattributes The Collection to which the attribute will be added. @@ -666,10 +666,10 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { bbattributes.add(new BlackboardAttribute(attrType, EmailParserModuleFactory.getModuleName(), stringVal)); } } - + /** * Add an attribute of a specified type to a supplied Collection. - * + * * @param longVal The attribute value. * @param attrType The type of attribute to be added. * @param bbattributes The Collection to which the attribute will be added. @@ -679,10 +679,10 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { bbattributes.add(new BlackboardAttribute(attrType, EmailParserModuleFactory.getModuleName(), longVal)); } } - + /** * Post an error message for the user. - * + * * @param subj The error subject. * @param details The error details. */ @@ -693,7 +693,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { /** * Get the IngestServices object. - * + * * @return The IngestServices object. */ IngestServices getServices() { From 166366e2410e468a9070b8689f04cd00ea1ab96c Mon Sep 17 00:00:00 2001 From: esaunders Date: Wed, 23 Oct 2019 14:12:52 -0400 Subject: [PATCH 075/134] Don't make building dependent on downloading test data files. Out Travis build occasionally fails when attempting to dowmload these files. Ideally these files should be downloaded only if tests are to be run that require them. --- Core/build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/build.xml b/Core/build.xml index 28e64b83e5..0e5c90ef04 100644 --- a/Core/build.xml +++ b/Core/build.xml @@ -137,7 +137,7 @@ - + From f306fc28ad13da320f545ff51525efeae750c39a Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 23 Oct 2019 14:43:35 -0400 Subject: [PATCH 076/134] 5506 More robust machine trans --- .../ui/Bundle.properties-MERGED | 3 ++- .../ui/TranslatedTextViewer.java | 25 ++++++++++--------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/Bundle.properties-MERGED index cfa9af0fb7..c4da06114c 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/Bundle.properties-MERGED @@ -5,11 +5,12 @@ TranslatedContentPanel.comboBoxOption.translatedText=Translated Text TranslatedContentViewer.emptyTranslation=The translation is empty. # {0} - exception message TranslatedContentViewer.errorExtractingText=Error encountered while extracting text from file ({0}). +TranslatedContentViewer.extractingText=Getting text, please wait... TranslatedContentViewer.fileHasNoText=File has no text. TranslatedContentViewer.noServiceProvider=Machine translation software was not found. TranslatedContentViewer.translatingText=Translating text, please wait... # {0} - exception message -TranslatedContentViewer.translationException=Error encountered while attempting translation ({0}). +TranslatedContentViewer.translationException=Error encountered while translating file ({0}). TranslatedTextViewer.maxPayloadSize=Up to the first %dKB of text will be translated TranslatedTextViewer.title=Translation TranslatedTextViewer.toolTip=Displays translated file text. diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java index 811dc229cc..aed254c9e0 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java @@ -171,6 +171,7 @@ public final class TranslatedTextViewer implements TextViewer { } @NbBundle.Messages({ + "TranslatedContentViewer.extractingText=Getting text, please wait...", "TranslatedContentViewer.translatingText=Translating text, please wait...", "# {0} - exception message", "TranslatedContentViewer.errorExtractingText=Error encountered while extracting text from file ({0}).", "TranslatedContentViewer.fileHasNoText=File has no text.", @@ -184,15 +185,14 @@ public final class TranslatedTextViewer implements TextViewer { throw new InterruptedException(); } - /* - * This message is only written to the viewer once this task starts - * and any previous task has therefore been completed by the - * single-threaded executor. - */ SwingUtilities.invokeLater(() -> { - panel.display(Bundle.TranslatedContentViewer_translatingText(), ComponentOrientation.LEFT_TO_RIGHT, Font.ITALIC); + /* + * This message is only written to the viewer once this task + * starts and any previous task has therefore been completed by + * the single-threaded executor. + */ + panel.display(Bundle.TranslatedContentViewer_extractingText(), ComponentOrientation.LEFT_TO_RIGHT, Font.ITALIC); }); - String fileText; try { fileText = getFileText(file); @@ -213,6 +213,9 @@ public final class TranslatedTextViewer implements TextViewer { return fileText; } + SwingUtilities.invokeLater(() -> { + panel.display(Bundle.TranslatedContentViewer_translatingText(), ComponentOrientation.LEFT_TO_RIGHT, Font.ITALIC); + }); String translation; try { translation = translate(fileText); @@ -243,7 +246,7 @@ public final class TranslatedTextViewer implements TextViewer { String orientDetectSubstring = result.substring(0, maxOrientChars); ComponentOrientation orientation = TextUtil.getTextDirection(orientDetectSubstring); panel.display(result, orientation, Font.PLAIN); - + } catch (InterruptedException | CancellationException ignored) { // Task cancelled, no error. } catch (ExecutionException ex) { @@ -405,10 +408,8 @@ public final class TranslatedTextViewer implements TextViewer { } AbstractFile file = node.getLookup().lookup(AbstractFile.class); - if (file == null) { - return; - } - boolean translateText = currentSelection.equals(DisplayDropdownOptions.ORIGINAL_TEXT.toString()); + String textDisplaySelection = panel.getDisplayDropDownSelection(); + boolean translateText = !textDisplaySelection.equals(DisplayDropdownOptions.ORIGINAL_TEXT.toString()); backgroundTask = new ExtractAndTranslateTextTask(file, translateText); //Pass the background task to a single threaded pool to keep From a416f04a46b5baf7cd6145975181b5a536fe9348 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 23 Oct 2019 14:45:13 -0400 Subject: [PATCH 077/134] 5506 More robust machine trans --- .../autopsy/texttranslation/ui/TranslatedTextViewer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java index aed254c9e0..242c32684d 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java @@ -171,7 +171,7 @@ public final class TranslatedTextViewer implements TextViewer { } @NbBundle.Messages({ - "TranslatedContentViewer.extractingText=Getting text, please wait...", + "TranslatedContentViewer.extractingText=Extracting text, please wait...", "TranslatedContentViewer.translatingText=Translating text, please wait...", "# {0} - exception message", "TranslatedContentViewer.errorExtractingText=Error encountered while extracting text from file ({0}).", "TranslatedContentViewer.fileHasNoText=File has no text.", From 156edd28f5962dcadb88ab3cc37816cfc15fa7cf Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Wed, 23 Oct 2019 15:11:23 -0400 Subject: [PATCH 078/134] Updated basic window to use new datamodel --- .../geolocation/GeolocationTopComponent.java | 14 ++++++++++++++ .../sleuthkit/autopsy/geolocation/MapWaypoint.java | 2 +- .../autopsy/geolocation/OpenGeolocationAction.java | 4 +++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java index 1e3143d678..7e096aa4d7 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java @@ -33,6 +33,7 @@ import org.jxmapviewer.viewer.Waypoint; import org.openide.util.NbBundle.Messages; import org.openide.windows.RetainLocation; import org.openide.windows.TopComponent; +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; @@ -52,6 +53,8 @@ import org.sleuthkit.datamodel.BlackboardArtifact; @RetainLocation("geolocation") @SuppressWarnings("PMD.SingularField") public final class GeolocationTopComponent extends TopComponent { + + private static final long serialVersionUID = 1L; private static final Logger logger = Logger.getLogger(GeolocationTopComponent.class.getName()); @@ -127,6 +130,17 @@ public final class GeolocationTopComponent extends TopComponent { super.removeNotify(); IngestManager.getInstance().removeIngestModuleEventListener(ingestListener); } + + @Override + public void componentOpened() { + super.componentOpened(); + WindowManager.getDefault().setTopComponentFloating(this, true); + } + + @Override + public void open() { + super.open(); + } /** * Set the state of the refresh panel at the top of the mapPanel. diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java index d1fb35ae46..fc969c58e8 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java @@ -30,7 +30,7 @@ final class MapWaypoint implements org.jxmapviewer.viewer.Waypoint{ static List getWaypoints(SleuthkitCase skCase) throws TskCoreException{ List points = Waypoint.getAllWaypoints(skCase); - List routes = Route.getGPSRoutes(skCase); + List routes = Route.getRoutes(skCase); for(Route route: routes) { points.addAll(route.getRoute()); } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/OpenGeolocationAction.java b/Core/src/org/sleuthkit/autopsy/geolocation/OpenGeolocationAction.java index e13031cd6e..e0f8d42389 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/OpenGeolocationAction.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/OpenGeolocationAction.java @@ -42,7 +42,9 @@ import org.sleuthkit.autopsy.core.RuntimeProperties; @ActionReferences(value = { @ActionReference(path = "Menu/Tools", position = 102)}) public class OpenGeolocationAction extends CallableSystemAction { - + + private static final long serialVersionUID = 1L; + @Messages({ "OpenGeolocationAction_name=Geolocation", "OpenGeolocationAction_displayName=Geolocation" From 4c7d14683aa6d01b97e78584c21b7b33f459402e Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 23 Oct 2019 15:11:44 -0400 Subject: [PATCH 079/134] 5506 More robust machine trans --- .../texttranslation/ui/Bundle.properties-MERGED | 10 +++++----- .../texttranslation/ui/TranslatedTextViewer.java | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/Bundle.properties-MERGED index c4da06114c..fa1a250e44 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/Bundle.properties-MERGED @@ -2,15 +2,15 @@ OptionsCategory_Name_Machine_Translation=Machine Translation OptionsCategory_Keywords_Machine_Translation_Settings=Machine Translation Settings TranslatedContentPanel.comboBoxOption.originalText=Original Text (Up to 25KB) TranslatedContentPanel.comboBoxOption.translatedText=Translated Text -TranslatedContentViewer.emptyTranslation=The translation is empty. +TranslatedContentViewer.emptyTranslation=The machine translation software did not return any text. # {0} - exception message -TranslatedContentViewer.errorExtractingText=Error encountered while extracting text from file ({0}). -TranslatedContentViewer.extractingText=Getting text, please wait... +TranslatedContentViewer.errorExtractingText=An error occurred while extracting the text ({0}). +TranslatedContentViewer.extractingText=Extracting text, please wait... TranslatedContentViewer.fileHasNoText=File has no text. -TranslatedContentViewer.noServiceProvider=Machine translation software was not found. +TranslatedContentViewer.noServiceProvider=The machine translation software was not found. TranslatedContentViewer.translatingText=Translating text, please wait... # {0} - exception message -TranslatedContentViewer.translationException=Error encountered while translating file ({0}). +TranslatedContentViewer.translationException=An error occurred while translating the text ({0}). TranslatedTextViewer.maxPayloadSize=Up to the first %dKB of text will be translated TranslatedTextViewer.title=Translation TranslatedTextViewer.toolTip=Displays translated file text. diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java index 242c32684d..e3993e629b 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java @@ -173,11 +173,11 @@ public final class TranslatedTextViewer implements TextViewer { @NbBundle.Messages({ "TranslatedContentViewer.extractingText=Extracting text, please wait...", "TranslatedContentViewer.translatingText=Translating text, please wait...", - "# {0} - exception message", "TranslatedContentViewer.errorExtractingText=Error encountered while extracting text from file ({0}).", + "# {0} - exception message", "TranslatedContentViewer.errorExtractingText=An error occurred while extracting the text ({0}).", "TranslatedContentViewer.fileHasNoText=File has no text.", - "TranslatedContentViewer.emptyTranslation=The translation is empty.", - "TranslatedContentViewer.noServiceProvider=Machine translation software was not found.", - "# {0} - exception message", "TranslatedContentViewer.translationException=Error encountered while translating file ({0})." + "TranslatedContentViewer.emptyTranslation=The machine translation software did not return any text.", + "TranslatedContentViewer.noServiceProvider=The machine translation software was not found.", + "# {0} - exception message", "TranslatedContentViewer.translationException=An error occurred while translating the text ({0})." }) @Override public String doInBackground() throws InterruptedException { From 264a827f21b4c650d6ff54ccce87b3fa18e4097f Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 23 Oct 2019 15:17:53 -0400 Subject: [PATCH 080/134] 5506 More robust machine trans --- .../autopsy/texttranslation/ui/TranslatedTextViewer.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java index e3993e629b..13dda67f5f 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java @@ -186,11 +186,6 @@ public final class TranslatedTextViewer implements TextViewer { } SwingUtilities.invokeLater(() -> { - /* - * This message is only written to the viewer once this task - * starts and any previous task has therefore been completed by - * the single-threaded executor. - */ panel.display(Bundle.TranslatedContentViewer_extractingText(), ComponentOrientation.LEFT_TO_RIGHT, Font.ITALIC); }); String fileText; From 6702fdb6acc6933753f992942410e80539d74336 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 23 Oct 2019 15:20:00 -0400 Subject: [PATCH 081/134] 5506 More robust machine trans --- .../autopsy/texttranslation/ui/TranslatedTextViewer.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java index 13dda67f5f..21e95cc329 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java @@ -192,7 +192,7 @@ public final class TranslatedTextViewer implements TextViewer { try { fileText = getFileText(file); } catch (IOException | TextExtractor.InitReaderException ex) { - logger.log(Level.WARNING, String.format("Error getting text for file %s (objId=%d)", file.getName(), file.getId()), ex); + logger.log(Level.WARNING, String.format("Error extracting text for file %s (objId=%d)", file.getName(), file.getId()), ex); return Bundle.TranslatedContentViewer_errorExtractingText(ex.getMessage()); } @@ -215,10 +215,10 @@ public final class TranslatedTextViewer implements TextViewer { try { translation = translate(fileText); } catch (NoServiceProviderException ex) { - logger.log(Level.WARNING, String.format("Error occurred translating text for file %s (objId=%d)", file.getName(), file.getId()), ex); + logger.log(Level.WARNING, String.format("Error translating text for file %s (objId=%d)", file.getName(), file.getId()), ex); translation = Bundle.TranslatedContentViewer_noServiceProvider(); } catch (TranslationException ex) { - logger.log(Level.WARNING, String.format("Error occurred translating text for file %s (objId=%d)", file.getName(), file.getId()), ex); + logger.log(Level.WARNING, String.format("Error translating text for file %s (objId=%d)", file.getName(), file.getId()), ex); translation = Bundle.TranslatedContentViewer_translationException(ex.getMessage()); } From 565a0296fba715f80adc2b907dfbd7f3fbf0a45d Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 23 Oct 2019 15:26:18 -0400 Subject: [PATCH 082/134] 5506 More robust machine trans --- .../autopsy/texttranslation/ui/TranslatedTextViewer.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java index 21e95cc329..bed42e2287 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java @@ -175,7 +175,6 @@ public final class TranslatedTextViewer implements TextViewer { "TranslatedContentViewer.translatingText=Translating text, please wait...", "# {0} - exception message", "TranslatedContentViewer.errorExtractingText=An error occurred while extracting the text ({0}).", "TranslatedContentViewer.fileHasNoText=File has no text.", - "TranslatedContentViewer.emptyTranslation=The machine translation software did not return any text.", "TranslatedContentViewer.noServiceProvider=The machine translation software was not found.", "# {0} - exception message", "TranslatedContentViewer.translationException=An error occurred while translating the text ({0})." }) @@ -257,6 +256,9 @@ public final class TranslatedTextViewer implements TextViewer { * * @return Translated text or error message */ + @NbBundle.Messages({ + "TranslatedContentViewer.emptyTranslation=The machine translation software did not return any text." + }) private String translate(String input) throws NoServiceProviderException, TranslationException { TextTranslationService translatorInstance = TextTranslationService.getInstance(); String translatedResult = translatorInstance.translate(input); From 338f49a9af7843ac224b700196895f07eba20e66 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 23 Oct 2019 15:33:09 -0400 Subject: [PATCH 083/134] Minor clean up of DataContentViewerUtility.java --- .../DataContentViewerUtility.java | 38 ++++++++++++------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerUtility.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerUtility.java index ea1e7b58a0..d460a16f03 100755 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerUtility.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerUtility.java @@ -23,32 +23,42 @@ import org.openide.nodes.Node; import org.sleuthkit.datamodel.BlackboardArtifact; /** - * Utility classes for content viewers. In theory, this would live in the - * contentviewer package, but the initial method was needed only be viewers in - * corecomponents and therefore can stay out of public API. + * Utility methods for content viewers. */ public class DataContentViewerUtility { /** - * Returns the first non-Blackboard Artifact from a Node. Needed for (at - * least) Hex and Strings that want to view all types of content (not just - * AbstractFile), but don't want to display an artifact unless that's the - * only thing there. Scenario is hash hit or interesting item hit. + * Gets a Content object from the Lookup of a display Node object, + * preferring to return any Content object other than a BlackboardArtifact + * object. * - * @param node Node passed into content viewer + * This method was written with the needs of the hex and strings content + * viewers in mind - the algorithm is exactly what those viewers require. * - * @return highest priority content or null if there is no content + * @param node A display Node object. + * + * @return If there are multiple Content objects associated with the Node, + * the first Content object that is not a BlackboardArtifact object + * is returned. If no Content objects other than artifacts are found, + * the first BlackboardArtifact object found is returned. If no + * Content objects are found, null is returned. */ public static Content getDefaultContent(Node node) { - Content bbContentSeen = null; - for (Content content : (node).getLookup().lookupAll(Content.class)) { - if (content instanceof BlackboardArtifact) { - bbContentSeen = content; + Content artifact = null; + for (Content content : node.getLookup().lookupAll(Content.class)) { + if (content instanceof BlackboardArtifact && artifact == null) { + artifact = content; } else { return content; } } - return bbContentSeen; + return artifact; + } + + /* + * Private constructor to prevent instantiation of utility class. + */ + private DataContentViewerUtility() { } } From b7ef4bec44a19fb3600b8b05e24921ee8cb4e86d Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Wed, 23 Oct 2019 15:39:32 -0400 Subject: [PATCH 084/134] Fixed codacy issues --- .../autopsy/geolocation/GeolocationTopComponent.java | 7 ------- .../org/sleuthkit/autopsy/geolocation/MapPanel.java | 11 +++++++---- .../sleuthkit/autopsy/geolocation/RefreshPanel.java | 6 ++++-- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java index 7e096aa4d7..cf76a66a66 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java @@ -136,11 +136,6 @@ public final class GeolocationTopComponent extends TopComponent { super.componentOpened(); WindowManager.getDefault().setTopComponentFloating(this, true); } - - @Override - public void open() { - super.open(); - } /** * Set the state of the refresh panel at the top of the mapPanel. @@ -165,8 +160,6 @@ public final class GeolocationTopComponent extends TopComponent { SwingWorker, Waypoint> worker = new SwingWorker, Waypoint>() { @Override protected List doInBackground() throws Exception { - List waypoints = new ArrayList<>(); - Case currentCase = Case.getCurrentCaseThrows(); return MapWaypoint.getWaypoints(currentCase.getSleuthkitCase()); diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java index 7abc1ff5be..9aace08818 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java @@ -39,12 +39,12 @@ import org.jxmapviewer.viewer.WaypointPainter; /** * Main panel with the JJXMapViewer object and its basic controls. */ -public class MapPanel extends javax.swing.JPanel { +final class MapPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; - private boolean zoomChanging = false; - private boolean sliderReversed = false; + private boolean zoomChanging; + private final boolean sliderReversed; // Using a DefaultListModel to store the way points because we get // a lot of functionality for free, like listeners. @@ -53,8 +53,11 @@ public class MapPanel extends javax.swing.JPanel { /** * Creates new form MapPanel */ - public MapPanel() { + MapPanel() { waypointListModel = new DefaultListModel<>(); + sliderReversed = false; + zoomChanging = false; + initComponents(); initMap(); } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/RefreshPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/RefreshPanel.java index d53dd26b70..e7ff40f31c 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/RefreshPanel.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/RefreshPanel.java @@ -26,12 +26,14 @@ import javax.swing.JPanel; * due to new artifacts. * */ -class RefreshPanel extends JPanel { +final class RefreshPanel extends JPanel { + + private static final long serialVersionUID = 1L; /** * Creates new form RefreshPanel */ - public RefreshPanel() { + RefreshPanel() { initComponents(); } From 2966483800306f7d9b6f7ed3d8de20ef6198e6a5 Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Wed, 23 Oct 2019 15:44:36 -0400 Subject: [PATCH 085/134] Fixed codacy issues --- .../sleuthkit/autopsy/geolocation/GeolocationTopComponent.java | 1 - 1 file changed, 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java index cf76a66a66..328aa048b2 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java @@ -22,7 +22,6 @@ import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.beans.PropertyChangeListener; -import java.util.ArrayList; import java.util.EnumSet; import java.util.List; import java.util.Set; From 6c54c81c7746b976a2438b94d06943645fb09b5f Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Thu, 24 Oct 2019 09:14:14 -0400 Subject: [PATCH 086/134] Update copyright for API docs. Fix doxygen warnings. --- .../centralrepository/eventlisteners/IngestEventsListener.java | 1 + .../org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java | 2 +- .../autopsy/texttranslation/ui/TranslatedTextViewer.java | 2 +- docs/doxygen/footer.html | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java index 282e225135..355c7c8dbc 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/IngestEventsListener.java @@ -224,6 +224,7 @@ public class IngestEventsListener { * in the central repository. * * @param originalArtifact the artifact to create the interesting item for + * @param caseDisplayNames the case names the artifact was previously seen in */ @NbBundle.Messages({"IngestEventsListener.prevExists.text=Previously Seen Devices (Central Repository)", "# {0} - typeName", diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java index 1f02708f64..84f7da6c36 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java @@ -637,7 +637,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie * thumb at the given width and height. It also paints the track blue as * the thumb progresses. * - * @param b JSlider component + * @param slider JSlider component * @param config Configuration object. Contains info about thumb * dimensions and colors. */ diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java index bed42e2287..7e07545b49 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/ui/TranslatedTextViewer.java @@ -271,7 +271,7 @@ public final class TranslatedTextViewer implements TextViewer { /** * Extracts text from the given node * - * @param node Selected node in UI + * @param file Selected node in UI * * @return Extracted text * diff --git a/docs/doxygen/footer.html b/docs/doxygen/footer.html index ac0c0a8d1c..8bf1577a45 100644 --- a/docs/doxygen/footer.html +++ b/docs/doxygen/footer.html @@ -1,5 +1,5 @@
    -

    Copyright © 2012-2018 Basis Technology. Generated on: $date
    +

    Copyright © 2012-2019 Basis Technology. Generated on: $date
    This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.

    From 73987f7a55cf857689092b4dbe3a0fb814ff1d95 Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Fri, 25 Oct 2019 16:22:22 -0400 Subject: [PATCH 087/134] Updated with missed review comments and added GeolocationDataException --- ...AttributeUtils.java => ArtifactUtils.java} | 63 ++++++++++++++++--- .../datamodel/ArtifactWaypoint.java | 35 +++++------ .../geolocation/datamodel/EXIFWaypoint.java | 25 +++++--- .../datamodel/GPSSearchWaypoint.java | 13 ++-- .../datamodel/GeoLocationDataException.java | 41 ++++++++++++ .../datamodel/GeolocationUtils.java | 7 +-- .../datamodel/LastKnownWaypoint.java | 8 +-- .../autopsy/geolocation/datamodel/Route.java | 54 +++++++--------- .../datamodel/TrackpointWaypoint.java | 15 ++--- .../geolocation/datamodel/Waypoint.java | 58 +++++++++-------- .../autopsy/report/modules/kml/KMLReport.java | 7 ++- 11 files changed, 207 insertions(+), 119 deletions(-) rename Core/src/org/sleuthkit/autopsy/geolocation/datamodel/{AttributeUtils.java => ArtifactUtils.java} (60%) create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeoLocationDataException.java diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/AttributeUtils.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactUtils.java similarity index 60% rename from Core/src/org/sleuthkit/autopsy/geolocation/datamodel/AttributeUtils.java rename to Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactUtils.java index c757c63c28..e99350072e 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/AttributeUtils.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactUtils.java @@ -18,20 +18,22 @@ */ package org.sleuthkit.autopsy.geolocation.datamodel; +import java.util.List; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; /** * Utilities for simplifying and reducing redundant when getting Artifact * attributes. */ -final class AttributeUtils { +final class ArtifactUtils { /** * Private constructor for this Utility class. */ - private AttributeUtils() { + private ArtifactUtils() { } @@ -47,12 +49,17 @@ final class AttributeUtils { * * @throws TskCoreException */ - static String getString(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) throws TskCoreException { + static String getString(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) throws GeoLocationDataException { if (artifact == null) { return null; } - BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(attributeType)); + BlackboardAttribute attribute; + try{ + attribute = artifact.getAttribute(new BlackboardAttribute.Type(attributeType)); + } catch(TskCoreException ex) { + throw new GeoLocationDataException(String.format("Failed to get double attribute for artifact: %d", artifact.getArtifactID()), ex); + } return (attribute != null ? attribute.getDisplayString() : null); } @@ -66,7 +73,7 @@ final class AttributeUtils { * * @throws TskCoreException */ - static Double getDouble(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) throws TskCoreException { + static Double getDouble(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) throws GeoLocationDataException { if (artifact == null) { return null; } @@ -75,7 +82,12 @@ final class AttributeUtils { return null; } - BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(attributeType)); + BlackboardAttribute attribute; + try{ + attribute = artifact.getAttribute(new BlackboardAttribute.Type(attributeType)); + } catch(TskCoreException ex) { + throw new GeoLocationDataException(String.format("Failed to get double attribute for artifact: %d", artifact.getArtifactID()), ex); + } return (attribute != null ? attribute.getValueDouble() : null); } @@ -89,7 +101,7 @@ final class AttributeUtils { * * @throws TskCoreException */ - static Long getLong(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) throws TskCoreException { + static Long getLong(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) throws GeoLocationDataException { if (artifact == null) { return null; } @@ -99,7 +111,12 @@ final class AttributeUtils { return null; } - BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(attributeType)); + BlackboardAttribute attribute; + try{ + attribute = artifact.getAttribute(new BlackboardAttribute.Type(attributeType)); + } catch(TskCoreException ex) { + throw new GeoLocationDataException(String.format("Failed to get double attribute for artifact: %d", artifact.getArtifactID()), ex); + } return (attribute != null ? attribute.getValueLong() : null); } @@ -113,7 +130,7 @@ final class AttributeUtils { * * @throws TskCoreException */ - static Integer getInteger(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) throws TskCoreException { + static Integer getInteger(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) throws GeoLocationDataException { if (artifact == null) { return null; } @@ -122,8 +139,34 @@ final class AttributeUtils { return null; } - BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(attributeType)); + BlackboardAttribute attribute; + try{ + attribute = artifact.getAttribute(new BlackboardAttribute.Type(attributeType)); + } catch(TskCoreException ex) { + throw new GeoLocationDataException(String.format("Failed to get double attribute for artifact: %d", artifact.getArtifactID()), ex); + } return (attribute != null ? attribute.getValueInt() : null); } + + /** + * Get a list of artifacts for the given BlackboardArtifact.Type. + * + * @param skCase Currently open case + * @param type BlackboardArtifact.Type to retrieve + * + * @return List of BlackboardArtifacts + * + * @throws GeoLocationDataException + */ + static List getArtifactsForType(SleuthkitCase skCase, BlackboardArtifact.ARTIFACT_TYPE type) throws GeoLocationDataException { + List artifacts; + try{ + artifacts = skCase.getBlackboardArtifacts(type); + } catch(TskCoreException ex) { + throw new GeoLocationDataException(String.format("Unable to get artifacts for type: %s", type.getLabel()), ex); + } + + return artifacts; + } } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactWaypoint.java index 280da3a563..511ad23b6d 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactWaypoint.java @@ -23,7 +23,6 @@ import java.util.List; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.TskCoreException; /** * Representation of a Waypoint created from a BlackboardArtifact. @@ -55,9 +54,9 @@ class ArtifactWaypoint implements Waypoint { * @param artifact BlackboardArtifact for this waypoint * @param type Waypoint type * - * @throws TskCoreException + * @throws GeoLocationDataException */ - protected ArtifactWaypoint(BlackboardArtifact artifact, Waypoint.Type type) throws TskCoreException { + protected ArtifactWaypoint(BlackboardArtifact artifact, Waypoint.Type type) throws GeoLocationDataException { this(artifact, getLabelFromArtifact(artifact), type); @@ -72,9 +71,9 @@ class ArtifactWaypoint implements Waypoint { * @param label String label for this waypoint * @param type Waypoint type * - * @throws TskCoreException + * @throws GeoLocationDataException */ - protected ArtifactWaypoint(BlackboardArtifact artifact, String label, Waypoint.Type type) throws TskCoreException { + protected ArtifactWaypoint(BlackboardArtifact artifact, String label, Waypoint.Type type) throws GeoLocationDataException { this(artifact, label, getTimestampFromArtifact(artifact), @@ -95,15 +94,15 @@ class ArtifactWaypoint implements Waypoint { * @param image AbstractFile image for waypoint, this maybe null * @param type Waypoint.Type value for waypoint * - * @throws TskCoreException + * @throws GeoLocationDataException */ - protected ArtifactWaypoint(BlackboardArtifact artifact, String label, Long timestamp, AbstractFile image, Waypoint.Type type) throws TskCoreException { + protected ArtifactWaypoint(BlackboardArtifact artifact, String label, Long timestamp, AbstractFile image, Waypoint.Type type) throws GeoLocationDataException { this(artifact, label, timestamp, - AttributeUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE), - AttributeUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE), - AttributeUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE), + ArtifactUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE), + ArtifactUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE), + ArtifactUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE), image, type); } @@ -120,9 +119,9 @@ class ArtifactWaypoint implements Waypoint { * @param image AbstractFile image for waypoint, this maybe null * @param type Waypoint.Type value for waypoint * - * @throws TskCoreException + * @throws GeoLocationDataException */ - private ArtifactWaypoint(BlackboardArtifact artifact, String label, Long timestamp, Double latitude, Double longitude, Double altitude, AbstractFile image, Waypoint.Type type) throws TskCoreException { + private ArtifactWaypoint(BlackboardArtifact artifact, String label, Long timestamp, Double latitude, Double longitude, Double altitude, AbstractFile image, Waypoint.Type type) throws GeoLocationDataException { this.artifact = artifact; this.label = label; this.type = type; @@ -191,14 +190,14 @@ class ArtifactWaypoint implements Waypoint { * * @return Long timestamp or null if a value was not found. * - * @throws TskCoreException + * @throws GeoLocationDataException */ - private static Long getTimestampFromArtifact(BlackboardArtifact artifact) throws TskCoreException { + private static Long getTimestampFromArtifact(BlackboardArtifact artifact) throws GeoLocationDataException { if (artifact == null) { return null; } - return AttributeUtils.getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME); + return ArtifactUtils.getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME); } /** @@ -212,11 +211,11 @@ class ArtifactWaypoint implements Waypoint { * @return Returns a label for the waypoint based on artifact type, or empty * string if no label was found. * - * @throws TskCoreException + * @throws GeoLocationDataException */ - private static String getLabelFromArtifact(BlackboardArtifact artifact) throws TskCoreException { + private static String getLabelFromArtifact(BlackboardArtifact artifact) throws GeoLocationDataException { - String typeLabel = AttributeUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); + String typeLabel = ArtifactUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); if (typeLabel == null) { typeLabel = ""; } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/EXIFWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/EXIFWaypoint.java index 37a1a2c7e2..c4846595dc 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/EXIFWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/EXIFWaypoint.java @@ -33,9 +33,9 @@ final class EXIFWaypoint extends ArtifactWaypoint { * * @param artifact BlackboardArtifact for waypoint * - * @throws TskCoreException + * @throws GeoLocationDataException */ - protected EXIFWaypoint(BlackboardArtifact artifact) throws TskCoreException { + protected EXIFWaypoint(BlackboardArtifact artifact) throws GeoLocationDataException { this(artifact, getImageFromArtifact(artifact)); } @@ -45,12 +45,12 @@ final class EXIFWaypoint extends ArtifactWaypoint { * @param artifact Waypoint BlackboardArtifact * @param image EXIF AbstractFile image * - * @throws TskCoreException + * @throws GeoLocationDataException */ - private EXIFWaypoint(BlackboardArtifact artifact, AbstractFile image) throws TskCoreException { + private EXIFWaypoint(BlackboardArtifact artifact, AbstractFile image) throws GeoLocationDataException { super(artifact, image != null ? image.getName() : "", - AttributeUtils.getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED), + ArtifactUtils.getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED), image, Waypoint.Type.METADATA_EXIF); } @@ -63,16 +63,21 @@ final class EXIFWaypoint extends ArtifactWaypoint { * @return AbstractFile image for this waypoint or null if one is not * available * - * @throws TskCoreException + * @throws GeoLocationDataException */ - private static AbstractFile getImageFromArtifact(BlackboardArtifact artifact) throws TskCoreException { + private static AbstractFile getImageFromArtifact(BlackboardArtifact artifact) throws GeoLocationDataException { + AbstractFile abstractFile = null; BlackboardArtifact.ARTIFACT_TYPE artifactType = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID()); - if (artifactType == BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF) { - return artifact.getSleuthkitCase().getAbstractFileById(artifact.getObjectID()); + + try{ + abstractFile = artifact.getSleuthkitCase().getAbstractFileById(artifact.getObjectID()); + } catch(TskCoreException ex) { + throw new GeoLocationDataException(String.format("Unable to getAbstractFileByID for artifact: %d", artifact.getArtifactID(), artifact.getArtifactID()), ex); + } } - return null; + return abstractFile; } } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GPSSearchWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GPSSearchWaypoint.java index 28bd7127a0..f076aa0d22 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GPSSearchWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GPSSearchWaypoint.java @@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.geolocation.datamodel; import org.openide.util.NbBundle.Messages; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.TskCoreException; /** * A GPSSearchWaypoint is a subclass of ArtifactWaypoint. @@ -34,8 +33,10 @@ final class GPSSearchWaypoint extends ArtifactWaypoint { /** * Construct a GPS Search waypoint. + * + * @throws GeoLocationDataException */ - public GPSSearchWaypoint(BlackboardArtifact artifact) throws TskCoreException { + GPSSearchWaypoint(BlackboardArtifact artifact) throws GeoLocationDataException { super(artifact, getLabelFromArtifact(artifact), Waypoint.Type.SEARCH); } @@ -46,13 +47,13 @@ final class GPSSearchWaypoint extends ArtifactWaypoint { * * @return String label for the artifacts way point. * - * @throws TskCoreException + * @throws GeoLocationDataException */ - private static String getLabelFromArtifact(BlackboardArtifact artifact) throws TskCoreException { - String typeLabel = AttributeUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); + private static String getLabelFromArtifact(BlackboardArtifact artifact) throws GeoLocationDataException { + String typeLabel = ArtifactUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); if (typeLabel == null || typeLabel.isEmpty()) { - typeLabel = AttributeUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION); + typeLabel = ArtifactUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION); } if (typeLabel == null || typeLabel.isEmpty()) { diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeoLocationDataException.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeoLocationDataException.java new file mode 100755 index 0000000000..24e8d142bc --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeoLocationDataException.java @@ -0,0 +1,41 @@ +/* + * 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.datamodel; + +/** + * + * + */ +public class GeoLocationDataException extends Exception { + + private static final long serialVersionUID = 1L; + + /** + * Default constructor when error message is not available + */ + public GeoLocationDataException() { + super("No error message available."); + } + + /** + * Create exception containing the error message + * + * @param msg the message + */ + public GeoLocationDataException(String msg) { + super(msg); + } + + /** + * Create exception containing the error message and cause exception + * + * @param msg the message + * @param ex cause exception + */ + public GeoLocationDataException(String msg, Exception ex) { + super(msg, ex); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtils.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtils.java index 19e42c5707..51a7f73107 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtils.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtils.java @@ -22,7 +22,6 @@ import java.util.ArrayList; import java.util.List; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.TskCoreException; /** * GeolocationUtilis class for common to be share across in the package @@ -66,14 +65,14 @@ final class GeolocationUtils { * * @return A List of Waypoint.Property objects * - * @throws TskCoreException + * @throws GeoLocationDataException */ - static List getOtherGeolocationProperties(BlackboardArtifact artifact) throws TskCoreException { + static List getOtherGeolocationProperties(BlackboardArtifact artifact) throws GeoLocationDataException { List list = new ArrayList<>(); for (BlackboardAttribute.ATTRIBUTE_TYPE type : OTHER_GEO_ATTRIBUTES) { String key = type.getDisplayName(); - String value = AttributeUtils.getString(artifact, type); + String value = ArtifactUtils.getString(artifact, type); if (value == null) { value = ""; diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/LastKnownWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/LastKnownWaypoint.java index 6ef47f76a2..3d95000677 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/LastKnownWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/LastKnownWaypoint.java @@ -30,7 +30,7 @@ import org.sleuthkit.datamodel.TskCoreException; "LastKnownWaypoint_Label=Last Known Location",}) final class LastKnownWaypoint extends ArtifactWaypoint { - protected LastKnownWaypoint(BlackboardArtifact artifact) throws TskCoreException { + protected LastKnownWaypoint(BlackboardArtifact artifact) throws GeoLocationDataException { super(artifact, getLabelFromArtifact(artifact), Waypoint.Type.LAST_KNOWN_LOCATION); } @@ -41,10 +41,10 @@ final class LastKnownWaypoint extends ArtifactWaypoint { * * @return String value from attribute TSK_NAME or LastKnownWaypoint_Label * - * @throws TskCoreException + * @throws GeoLocationDataException */ - private static String getLabelFromArtifact(BlackboardArtifact artifact) throws TskCoreException { - String label = AttributeUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); + private static String getLabelFromArtifact(BlackboardArtifact artifact) throws GeoLocationDataException { + String label = ArtifactUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); if (label == null || label.isEmpty()) { label = Bundle.LastKnownWaypoint_Label(); diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java index 4eefc72bca..bf93fe8803 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java @@ -26,7 +26,6 @@ import org.openide.util.NbBundle.Messages; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.TskCoreException; /** * A Route represents a TSK_GPS_ROUTE artifact which has a start and end point @@ -39,7 +38,7 @@ import org.sleuthkit.datamodel.TskCoreException; "Route_Start_Label=Start", "Route_End_Label=End" }) -public class Route { +public final class Route { private final List points; private final Long timestamp; @@ -57,11 +56,12 @@ public class Route { * @return List of Route objects, empty list will be returned if no Routes * where found * - * @throws TskCoreException + * @throws GeoLocationDataException */ - static public List getGPSRoutes(SleuthkitCase skCase) throws TskCoreException { + static public List getGPSRoutes(SleuthkitCase skCase) throws GeoLocationDataException { + List artifacts = ArtifactUtils.getArtifactsForType(skCase, BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE); + List routes = new ArrayList<>(); - List artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE); for (BlackboardArtifact artifact : artifacts) { Route route = new Route(artifact); routes.add(route); @@ -74,7 +74,7 @@ public class Route { * * @param artifact TSK_GPS_ROUTE artifact object */ - protected Route(BlackboardArtifact artifact) throws TskCoreException { + Route(BlackboardArtifact artifact) throws GeoLocationDataException { points = new ArrayList<>(); Waypoint point = getRouteStartPoint(artifact); @@ -96,10 +96,10 @@ public class Route { /** * Get the list of way points for this route; * - * @return List of ArtifactWaypoints for this route + * @return List an unmodifiableList of ArtifactWaypoints for this route */ public List getRoute() { - return points; + return Collections.unmodifiableList(points); } /** @@ -149,19 +149,15 @@ public class Route { * @return Start RoutePoint or null if valid longitude and latitude are not * found * - * @throws TskCoreException + * @throws GeoLocationDataException */ - private Waypoint getRouteStartPoint(BlackboardArtifact artifact) throws TskCoreException { + private Waypoint getRouteStartPoint(BlackboardArtifact artifact) throws GeoLocationDataException { Double latitude; Double longitude; - BlackboardAttribute attribute; RoutePoint point = null; - attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START)); - latitude = attribute != null ? attribute.getValueDouble() : null; - - attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START)); - longitude = attribute != null ? attribute.getValueDouble() : null; + latitude = ArtifactUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START); + longitude = ArtifactUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START); if (latitude != null && longitude != null) { point = new RoutePoint(this, latitude, longitude, Bundle.Route_Start_Label()); @@ -179,19 +175,15 @@ public class Route { * @return End RoutePoint or null if valid longitude and latitude are not * found * - * @throws TskCoreException + * @throws GeoLocationDataException */ - private Waypoint getRouteEndPoint(BlackboardArtifact artifact) throws TskCoreException { + private Waypoint getRouteEndPoint(BlackboardArtifact artifact) throws GeoLocationDataException { Double latitude; Double longitude; - BlackboardAttribute attribute; RoutePoint point = null; - attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END)); - latitude = attribute != null ? attribute.getValueDouble() : null; - - attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END)); - longitude = attribute != null ? attribute.getValueDouble() : null; + latitude = ArtifactUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END); + longitude = ArtifactUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END); if (latitude != null && longitude != null) { point = new RoutePoint(this, latitude, longitude, Bundle.Route_End_Label()); @@ -208,11 +200,10 @@ public class Route { * * @return The Altitude, or null if none was found * - * @throws TskCoreException + * @throws GeoLocationDataException */ - private Double getRouteAltitude(BlackboardArtifact artifact) throws TskCoreException { - BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE)); - return attribute != null ? attribute.getValueDouble() : null; + private Double getRouteAltitude(BlackboardArtifact artifact) throws GeoLocationDataException { + return ArtifactUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE); } /** @@ -223,11 +214,10 @@ public class Route { * * @return The timestamp attribute, or null if none was found * - * @throws TskCoreException + * @throws GeoLocationDataException */ - private Long getRouteTimestamp(BlackboardArtifact artifact) throws TskCoreException { - BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME)); - return attribute != null ? attribute.getValueLong() : null; + private Long getRouteTimestamp(BlackboardArtifact artifact) throws GeoLocationDataException { + return ArtifactUtils.getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME); } } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/TrackpointWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/TrackpointWaypoint.java index b4b1205c77..377477bef6 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/TrackpointWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/TrackpointWaypoint.java @@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.geolocation.datamodel; import org.openide.util.NbBundle.Messages; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.TskCoreException; /** * A wrapper class for TSK_GPS_TRACKPOINT artifacts. @@ -34,8 +33,10 @@ final class TrackpointWaypoint extends ArtifactWaypoint { /** * Construct a waypoint for trackpoints. + * + * @throws GeoLocationDataException */ - TrackpointWaypoint(BlackboardArtifact artifact) throws TskCoreException { + TrackpointWaypoint(BlackboardArtifact artifact) throws GeoLocationDataException { super(artifact, getLabelFromArtifact(artifact), Waypoint.Type.TRACKPOINT); } @@ -47,18 +48,18 @@ final class TrackpointWaypoint extends ArtifactWaypoint { * * @return String label for the artifacts way point. * - * @throws TskCoreException + * @throws GeoLocationDataException */ - private static String getLabelFromArtifact(BlackboardArtifact artifact) throws TskCoreException { + private static String getLabelFromArtifact(BlackboardArtifact artifact) throws GeoLocationDataException { - String typeLabel = AttributeUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); + String typeLabel = ArtifactUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); if (typeLabel == null || typeLabel.isEmpty()) { - typeLabel = AttributeUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME); + typeLabel = ArtifactUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME); } if (typeLabel == null || typeLabel.isEmpty()) { - typeLabel = AttributeUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FLAG); + typeLabel = ArtifactUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FLAG); } if (typeLabel == null || typeLabel.isEmpty()) { diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java index 2bf606b18b..3dc3f3304d 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java @@ -24,7 +24,6 @@ import org.openide.util.NbBundle.Messages; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.TskCoreException; /** * The basic details of a waypoint. @@ -43,9 +42,12 @@ public interface Waypoint { }) /** - * Get the timestamp for this BlackboardArtifact. + * Interface to describe a waypoint. A waypoint is made up of + * a longitude, latitude, label, timestamp, type, image and altitude. + * + * A good way point should have at minimum a longitude and latutude. * - * @return Timestamp in epoch seconds or null if none was set. + * @return Timestamp in java/unix epoch seconds or null if none was set. */ Long getTimestamp(); @@ -71,16 +73,16 @@ public interface Waypoint { Double getLongitude(); /** - * Get the Altitude for this point. + * Get the altitude for this point. * - * @return Returns the Altitude for the point or null if none was set + * @return Returns the altitude for the point or null if none was set */ Double getAltitude(); /** * Gets an unmodifiable List of other properties that may be interesting to this way point. - * The List will not include properties for which there are getter functions - * for. + * The List will not include properties for which getter functions + * exist. * * @return A List of waypoint properties */ @@ -89,7 +91,7 @@ public interface Waypoint { /** * Get the image for this waypoint. * - * @return AbstractFile image + * @return AbstractFile image or null if one was not set */ AbstractFile getImage(); @@ -111,9 +113,9 @@ public interface Waypoint { * * @return List of Waypoint * - * @throws TskCoreException + * @throws GeoLocationDataException */ - static List getAllWaypoints(SleuthkitCase skCase) throws TskCoreException { + static List getAllWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { List points = new ArrayList<>(); points.addAll(getTrackpointWaypoints(skCase)); @@ -132,11 +134,12 @@ public interface Waypoint { * * @return List of Waypoint * - * @throws TskCoreException + * @throws GeoLocationDataException */ - static List getTrackpointWaypoints(SleuthkitCase skCase) throws TskCoreException { + static List getTrackpointWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { + List artifacts = ArtifactUtils.getArtifactsForType(skCase, BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT); + List points = new ArrayList<>(); - List artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT); for (BlackboardArtifact artifact : artifacts) { ArtifactWaypoint point = new TrackpointWaypoint(artifact); // Only add to the list if the point has a valid latitude @@ -155,11 +158,12 @@ public interface Waypoint { * * @return List of Waypoint * - * @throws TskCoreException + * @throws GeoLocationDataException */ - static List getEXIFWaypoints(SleuthkitCase skCase) throws TskCoreException { + static List getEXIFWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { + List artifacts = ArtifactUtils.getArtifactsForType(skCase, BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF); + List points = new ArrayList<>(); - List artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF); if (artifacts != null) { for (BlackboardArtifact artifact : artifacts) { ArtifactWaypoint point = new EXIFWaypoint(artifact); @@ -180,11 +184,12 @@ public interface Waypoint { * * @return List of Waypoint * - * @throws TskCoreException + * @throws GeoLocationDataException */ - static List getSearchWaypoints(SleuthkitCase skCase) throws TskCoreException { + static List getSearchWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { + List artifacts = ArtifactUtils.getArtifactsForType(skCase, BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH); + List points = new ArrayList<>(); - List artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH); if (artifacts != null) { for (BlackboardArtifact artifact : artifacts) { ArtifactWaypoint point = new GPSSearchWaypoint(artifact); @@ -205,12 +210,14 @@ public interface Waypoint { * * @return List of Waypoint * - * @throws TskCoreException + * @throws GeoLocationDataException */ - static List getLastKnownWaypoints(SleuthkitCase skCase) throws TskCoreException { + static List getLastKnownWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { + List artifacts = ArtifactUtils.getArtifactsForType(skCase, BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION); + List points = new ArrayList<>(); - List artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION); if (artifacts != null) { + for (BlackboardArtifact artifact : artifacts) { ArtifactWaypoint point = new LastKnownWaypoint(artifact); // Only add to the list if the point has a valid latitude @@ -230,11 +237,12 @@ public interface Waypoint { * * @return List of Waypoint * - * @throws TskCoreException + * @throws GeoLocationDataException */ - static List getBookmarkWaypoints(SleuthkitCase skCase) throws TskCoreException { + static List getBookmarkWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { + List artifacts = ArtifactUtils.getArtifactsForType(skCase, BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK); + List points = new ArrayList<>(); - List artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK); if (artifacts != null) { for (BlackboardArtifact artifact : artifacts) { ArtifactWaypoint point = new ArtifactWaypoint(artifact, Waypoint.Type.BOOKMARK); 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 ff5d1c35ba..ebcc9853f0 100644 --- a/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java +++ b/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java @@ -45,6 +45,7 @@ import org.jdom2.output.XMLOutputter; import org.jdom2.CDATA; import org.openide.filesystems.FileUtil; 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.report.ReportBranding; @@ -154,7 +155,7 @@ class KMLReport implements GeneralReportModule { try { makeRoutes(skCase); addLocationsToReport(skCase, baseReportDir); - } catch (TskCoreException | IOException ex) { + } catch (GeoLocationDataException | IOException ex) { errorMessage = "Failed to complete report."; logger.log(Level.SEVERE, errorMessage, ex); //NON-NLS result = ReportProgressPanel.ReportStatus.ERROR; @@ -316,7 +317,7 @@ class KMLReport implements GeneralReportModule { * @throws TskCoreException * @throws IOException */ - void addLocationsToReport(SleuthkitCase skCase, String baseReportDir) throws TskCoreException, IOException { + void addLocationsToReport(SleuthkitCase skCase, String baseReportDir) throws GeoLocationDataException, IOException { List points = Waypoint.getAllWaypoints(skCase); for (Waypoint point : points) { @@ -357,7 +358,7 @@ class KMLReport implements GeneralReportModule { * * @throws TskCoreException */ - void makeRoutes(SleuthkitCase skCase) throws TskCoreException { + void makeRoutes(SleuthkitCase skCase) throws GeoLocationDataException { List routes = Route.getGPSRoutes(skCase); if(routes == null) { From 585d1897f07e9264a2324e59f4280d67126c143d Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Mon, 28 Oct 2019 11:09:59 -0400 Subject: [PATCH 088/134] Removed Type from Waypoint class --- .../datamodel/ArtifactWaypoint.java | 26 +-- .../datamodel/Bundle.properties-MERGED | 6 - .../geolocation/datamodel/EXIFWaypoint.java | 3 +- .../datamodel/GPSSearchWaypoint.java | 2 +- .../datamodel/LastKnownWaypoint.java | 3 +- .../geolocation/datamodel/RoutePoint.java | 5 - .../datamodel/TrackpointWaypoint.java | 2 +- .../geolocation/datamodel/Waypoint.java | 53 +----- .../modules/kml/Bundle.properties-MERGED | 7 + .../autopsy/report/modules/kml/KMLReport.java | 164 +++++++++++------- 10 files changed, 118 insertions(+), 153 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactWaypoint.java index 511ad23b6d..f30b2637b2 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactWaypoint.java @@ -37,7 +37,6 @@ class ArtifactWaypoint implements Waypoint { final private String label; final private AbstractFile image; final private BlackboardArtifact artifact; - final private Waypoint.Type type; // This list is not expected to change after construction so the // constructor will take care of creating an unmodifiable List @@ -56,10 +55,9 @@ class ArtifactWaypoint implements Waypoint { * * @throws GeoLocationDataException */ - protected ArtifactWaypoint(BlackboardArtifact artifact, Waypoint.Type type) throws GeoLocationDataException { + protected ArtifactWaypoint(BlackboardArtifact artifact) throws GeoLocationDataException { this(artifact, - getLabelFromArtifact(artifact), - type); + getLabelFromArtifact(artifact)); } /** @@ -73,12 +71,11 @@ class ArtifactWaypoint implements Waypoint { * * @throws GeoLocationDataException */ - protected ArtifactWaypoint(BlackboardArtifact artifact, String label, Waypoint.Type type) throws GeoLocationDataException { + protected ArtifactWaypoint(BlackboardArtifact artifact, String label) throws GeoLocationDataException { this(artifact, label, getTimestampFromArtifact(artifact), - null, - type); + null); } /** @@ -96,15 +93,14 @@ class ArtifactWaypoint implements Waypoint { * * @throws GeoLocationDataException */ - protected ArtifactWaypoint(BlackboardArtifact artifact, String label, Long timestamp, AbstractFile image, Waypoint.Type type) throws GeoLocationDataException { + protected ArtifactWaypoint(BlackboardArtifact artifact, String label, Long timestamp, AbstractFile image) throws GeoLocationDataException { this(artifact, label, timestamp, ArtifactUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE), ArtifactUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE), ArtifactUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE), - image, - type); + image); } /** @@ -121,10 +117,9 @@ class ArtifactWaypoint implements Waypoint { * * @throws GeoLocationDataException */ - private ArtifactWaypoint(BlackboardArtifact artifact, String label, Long timestamp, Double latitude, Double longitude, Double altitude, AbstractFile image, Waypoint.Type type) throws GeoLocationDataException { + private ArtifactWaypoint(BlackboardArtifact artifact, String label, Long timestamp, Double latitude, Double longitude, Double altitude, AbstractFile image) throws GeoLocationDataException { this.artifact = artifact; this.label = label; - this.type = type; this.image = image; this.timestamp = timestamp; this.longitude = longitude; @@ -172,12 +167,7 @@ class ArtifactWaypoint implements Waypoint { public AbstractFile getImage() { return image; } - - @Override - public Waypoint.Type getType() { - return type; - } - + @Override public List getOtherProperties() { return immutablePropertiesList; diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Bundle.properties-MERGED index 60345f2e57..86acb2fdb1 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Bundle.properties-MERGED @@ -4,9 +4,3 @@ Route_Label=As-the-crow-flies Route Route_Start_Label=Start SearchWaypoint_DisplayLabel=GPS Search TrackpointWaypoint_DisplayLabel=GPS Trackpoint -Waypoint_Bookmark_Display_String=GPS Bookmark -Waypoint_EXIF_Display_String=EXIF Metadata With Location -Waypoint_Last_Known_Display_String=GPS Last Known Location -Waypoint_Route_Point_Display_String=GPS Individual Route Point -Waypoint_Search_Display_String=GPS Search -Waypoint_Trackpoint_Display_String=GPS Trackpoint diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/EXIFWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/EXIFWaypoint.java index c4846595dc..2ffdc92d23 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/EXIFWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/EXIFWaypoint.java @@ -51,8 +51,7 @@ final class EXIFWaypoint extends ArtifactWaypoint { super(artifact, image != null ? image.getName() : "", ArtifactUtils.getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED), - image, - Waypoint.Type.METADATA_EXIF); + image); } /** diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GPSSearchWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GPSSearchWaypoint.java index f076aa0d22..1c5efec3f4 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GPSSearchWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GPSSearchWaypoint.java @@ -37,7 +37,7 @@ final class GPSSearchWaypoint extends ArtifactWaypoint { * @throws GeoLocationDataException */ GPSSearchWaypoint(BlackboardArtifact artifact) throws GeoLocationDataException { - super(artifact, getLabelFromArtifact(artifact), Waypoint.Type.SEARCH); + super(artifact, getLabelFromArtifact(artifact)); } /** diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/LastKnownWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/LastKnownWaypoint.java index 3d95000677..32aec6f041 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/LastKnownWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/LastKnownWaypoint.java @@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.geolocation.datamodel; import org.openide.util.NbBundle.Messages; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.TskCoreException; /** * A Last Know Location Waypoint object. @@ -31,7 +30,7 @@ import org.sleuthkit.datamodel.TskCoreException; final class LastKnownWaypoint extends ArtifactWaypoint { protected LastKnownWaypoint(BlackboardArtifact artifact) throws GeoLocationDataException { - super(artifact, getLabelFromArtifact(artifact), Waypoint.Type.LAST_KNOWN_LOCATION); + super(artifact, getLabelFromArtifact(artifact)); } /** diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/RoutePoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/RoutePoint.java index 25eac21422..f3d8f5afcc 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/RoutePoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/RoutePoint.java @@ -82,9 +82,4 @@ final class RoutePoint implements Waypoint { public AbstractFile getImage() { return null; } - - @Override - public Waypoint.Type getType() { - return Waypoint.Type.ROUTE_POINT; - } } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/TrackpointWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/TrackpointWaypoint.java index 377477bef6..3ef3279239 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/TrackpointWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/TrackpointWaypoint.java @@ -37,7 +37,7 @@ final class TrackpointWaypoint extends ArtifactWaypoint { * @throws GeoLocationDataException */ TrackpointWaypoint(BlackboardArtifact artifact) throws GeoLocationDataException { - super(artifact, getLabelFromArtifact(artifact), Waypoint.Type.TRACKPOINT); + super(artifact, getLabelFromArtifact(artifact)); } /** diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java index 3dc3f3304d..aaf5a1afe5 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java @@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.geolocation.datamodel; import java.util.ArrayList; import java.util.List; -import org.openide.util.NbBundle.Messages; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.SleuthkitCase; @@ -30,17 +29,6 @@ import org.sleuthkit.datamodel.SleuthkitCase; * */ public interface Waypoint { - - // Display names are from the original KML Report - @Messages({ - "Waypoint_Bookmark_Display_String=GPS Bookmark", - "Waypoint_Last_Known_Display_String=GPS Last Known Location", - "Waypoint_EXIF_Display_String=EXIF Metadata With Location", - "Waypoint_Route_Point_Display_String=GPS Individual Route Point", - "Waypoint_Search_Display_String=GPS Search", - "Waypoint_Trackpoint_Display_String=GPS Trackpoint" - }) - /** * Interface to describe a waypoint. A waypoint is made up of * a longitude, latitude, label, timestamp, type, image and altitude. @@ -95,13 +83,6 @@ public interface Waypoint { */ AbstractFile getImage(); - /** - * Get the type of waypoint - * - * @return WaypointType value - */ - Type getType(); - /** * Returns a list of Waypoints for the artifacts with geolocation * information. @@ -245,7 +226,7 @@ public interface Waypoint { List points = new ArrayList<>(); if (artifacts != null) { for (BlackboardArtifact artifact : artifacts) { - ArtifactWaypoint point = new ArtifactWaypoint(artifact, Waypoint.Type.BOOKMARK); + ArtifactWaypoint point = new ArtifactWaypoint(artifact); // Only add to the list if the point has a valid latitude // and longitude. if (point.getLatitude() != null && point.getLongitude() != null) { @@ -256,38 +237,6 @@ public interface Waypoint { return points; } - /** - * An enum to keep track of the type of a way point. - */ - enum Type { - BOOKMARK(Bundle.Waypoint_Bookmark_Display_String()), - LAST_KNOWN_LOCATION(Bundle.Waypoint_Last_Known_Display_String()), - METADATA_EXIF(Bundle.Waypoint_EXIF_Display_String()), - ROUTE_POINT(Bundle.Waypoint_Route_Point_Display_String()), - SEARCH(Bundle.Waypoint_Search_Display_String()), - TRACKPOINT(Bundle.Waypoint_Trackpoint_Display_String()); - - private final String displayName; - - /** - * Constructs a Waypoint.Type enum value - * - * @param displayName String value title for enum - */ - Type(String displayName) { - this.displayName = displayName; - } - - /** - * Returns the display name for the type - * - * @return String display name - */ - public String getDisplayName() { - return displayName; - } - } - /** * Simple property class for waypoint properties that a purely * informational. diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/kml/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/report/modules/kml/Bundle.properties-MERGED index 0823cd171b..3cbb7f6506 100755 --- a/Core/src/org/sleuthkit/autopsy/report/modules/kml/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/report/modules/kml/Bundle.properties-MERGED @@ -23,3 +23,10 @@ ReportKML.genReport.srcModuleName.text=Geospatial Data ReportKML.genReport.reportName=KML Report ReportKML.latLongStartPoint={0};{1};;{2} (Start)\n ReportKML.latLongEndPoint={0};{1};;{2} (End)\n +Route_Details_Header=GPS Route +Waypoint_Bookmark_Display_String=GPS Bookmark +Waypoint_EXIF_Display_String=EXIF Metadata With Location +Waypoint_Last_Known_Display_String=GPS Last Known Location +Waypoint_Route_Point_Display_String=GPS Individual Route Point +Waypoint_Search_Display_String=GPS Search +Waypoint_Trackpoint_Display_String=GPS Trackpoint 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 ebcc9853f0..b72ad14f73 100644 --- a/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java +++ b/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java @@ -21,11 +21,9 @@ package org.sleuthkit.autopsy.report.modules.kml; import org.sleuthkit.autopsy.report.GeneralReportModule; import javax.swing.JPanel; - import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.datamodel.*; import org.sleuthkit.autopsy.ingest.IngestManager; import java.io.File; import java.io.FileOutputStream; @@ -44,13 +42,18 @@ import org.jdom2.output.Format; import org.jdom2.output.XMLOutputter; import org.jdom2.CDATA; import org.openide.filesystems.FileUtil; +import org.openide.util.NbBundle.Messages; 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.report.ReportBranding; import org.sleuthkit.autopsy.report.ReportProgressPanel; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.ReadContentInputStream; import org.sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; /** * Generates a KML file based on geospatial information from the BlackBoard. @@ -111,7 +114,7 @@ class KMLReport implements GeneralReportModule { * @param baseReportDir path to save the report * @param progressPanel panel to update the report's progress */ - @NbBundle.Messages({ + @Messages({ "KMLReport.unableToExtractPhotos=Could not extract photo information.", "KMLReport.exifPhotoError=Could not extract photos with EXIF metadata.", "KMLReport.bookmarkError=Could not extract Bookmark information.", @@ -127,8 +130,16 @@ class KMLReport implements GeneralReportModule { "KMLReport.kmlFileWriteError=Could not write the KML file.", "# {0} - filePath", "KMLReport.errorGeneratingReport=Error adding {0} to case as a report.", - "KMLReport.unableToOpenCase=Exception while getting open case." + "KMLReport.unableToOpenCase=Exception while getting open case.", + "Waypoint_Bookmark_Display_String=GPS Bookmark", + "Waypoint_Last_Known_Display_String=GPS Last Known Location", + "Waypoint_EXIF_Display_String=EXIF Metadata With Location", + "Waypoint_Route_Point_Display_String=GPS Individual Route Point", + "Waypoint_Search_Display_String=GPS Search", + "Waypoint_Trackpoint_Display_String=GPS Trackpoint", + "Route_Details_Header=GPS Route" }) + @Override public void generateReport(String baseReportDir, ReportProgressPanel progressPanel) { try { @@ -285,27 +296,29 @@ class KMLReport implements GeneralReportModule { * * @throws IOException */ - void addExifMetadataContent(Waypoint point, String baseReportDirectory) throws IOException { - Element mapPoint = makePoint(point); - if (mapPoint == null) { - return; - } + void addExifMetadataContent(List points, String baseReportDirectory) throws IOException { + for(Waypoint point: points) { + Element mapPoint = makePoint(point); + if (mapPoint == null) { + return; + } - AbstractFile abstractFile = point.getImage(); - String details = getFormattedDetails(point); + AbstractFile abstractFile = point.getImage(); + String details = getFormattedDetails(point, Bundle.Waypoint_EXIF_Display_String()); - Path path; - copyFileUsingStream(abstractFile, Paths.get(baseReportDirectory, abstractFile.getName()).toFile()); - try { - path = Paths.get(removeLeadingImgAndVol(abstractFile.getUniquePath())); - } catch (TskCoreException ex) { - path = Paths.get(abstractFile.getParentPath(), abstractFile.getName()); - } - if (path == null) { - path = Paths.get(abstractFile.getName()); - } + Path path; + copyFileUsingStream(abstractFile, Paths.get(baseReportDirectory, abstractFile.getName()).toFile()); + try { + path = Paths.get(removeLeadingImgAndVol(abstractFile.getUniquePath())); + } catch (TskCoreException ex) { + path = Paths.get(abstractFile.getParentPath(), abstractFile.getName()); + } + if (path == null) { + path = Paths.get(abstractFile.getName()); + } - gpsExifMetadataFolder.addContent(makePlacemarkWithPicture(abstractFile.getName(), FeatureColor.RED, details, point.getTimestamp(), mapPoint, path, formattedCoordinates(point.getLatitude(), point.getLongitude()))); + gpsExifMetadataFolder.addContent(makePlacemarkWithPicture(abstractFile.getName(), FeatureColor.RED, details, point.getTimestamp(), mapPoint, path, formattedCoordinates(point.getLatitude(), point.getLongitude()))); + } } /** @@ -318,36 +331,43 @@ class KMLReport implements GeneralReportModule { * @throws IOException */ void addLocationsToReport(SleuthkitCase skCase, String baseReportDir) throws GeoLocationDataException, IOException { - List points = Waypoint.getAllWaypoints(skCase); - - for (Waypoint point : points) { - Element reportPoint = makePoint(point); - if (reportPoint == null) { - continue; - } - - String formattedCords = formattedCoordinates(point.getLatitude(), point.getLongitude()); - - switch (point.getType()) { - case METADATA_EXIF: - addExifMetadataContent(point, baseReportDir); - break; - case BOOKMARK: - gpsBookmarksFolder.addContent(makePlacemark(point.getLabel(), FeatureColor.BLUE, getFormattedDetails(point), point.getTimestamp(), reportPoint, formattedCords)); - break; - case LAST_KNOWN_LOCATION: - gpsLastKnownLocationFolder.addContent(makePlacemark(point.getLabel(), FeatureColor.PURPLE, getFormattedDetails(point), point.getTimestamp(), reportPoint, formattedCords)); - break; - case SEARCH: - gpsSearchesFolder.addContent(makePlacemark(point.getLabel(), FeatureColor.WHITE, getFormattedDetails(point), point.getTimestamp(), reportPoint, formattedCords)); - break; - case TRACKPOINT: - gpsTrackpointsFolder.addContent(makePlacemark(point.getLabel(), FeatureColor.WHITE, getFormattedDetails(point), point.getTimestamp(), reportPoint, formattedCords)); - break; - default: - // default is here to make codacy happy. - break; - } + 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()); + } + + /** + * For each point in the waypoint list an Element to represent the given waypoint + * is created and added it to the given Element folder. + * + * @param points List of waypoints to add to the report + * @param folder The Element folder to add the points to + * @param waypointColor The color the waypoint should appear in the report + */ + void addWaypoints(List points, Element folder, FeatureColor waypointColor, String headerLabel) { + for(Waypoint point: points) { + addContent(folder, point.getLabel(), waypointColor, String.format("

    %s

    ", headerLabel), point.getTimestamp(), makePoint(point), point.getLatitude(), point.getLongitude()); + } + } + + /** + * Adds the waypoint Element with details to the report in the given folder. + * + * @param folder Element folder to add the waypoint to + * @param waypointLabel String waypoint Label + * @param waypointColor FeatureColor for the waypoint + * @param formattedDetails String HTML formatted waypoint details + * @param timestamp Long timestamp (unix\jave epoch seconds) + * @param point Element point object + * @param latitude Double latitude value + * @param longitude Double longitude value + */ + void addContent(Element folder, String waypointLabel, FeatureColor waypointColor, String formattedDetails, Long timestamp, Element point, Double latitude, Double longitude) { + if(folder != null && point != null) { + String formattedCords = formattedCoordinates(latitude, longitude); + folder.addContent(makePlacemark(waypointLabel, waypointColor, formattedDetails, timestamp, point, formattedCords)); } } @@ -402,11 +422,16 @@ class KMLReport implements GeneralReportModule { } if (startingPoint != null) { - gpsRouteFolder.addContent(makePlacemark(start.getLabel(), FeatureColor.GREEN, getFormattedDetails(start), start.getTimestamp(), startingPoint, formattedStart)); //NON-NLS + gpsRouteFolder.addContent(makePlacemark(start.getLabel(), + FeatureColor.GREEN, getFormattedDetails(start, Bundle.Waypoint_Route_Point_Display_String()), + start.getTimestamp(), startingPoint, formattedStart)); //NON-NLS } if (endingPoint != null) { - gpsRouteFolder.addContent(makePlacemark(end.getLabel(), FeatureColor.GREEN, getFormattedDetails(end), end.getTimestamp(), endingPoint, formattedEnd)); //NON-NLS + gpsRouteFolder.addContent(makePlacemark(end.getLabel(), + FeatureColor.GREEN, + getFormattedDetails(end, Bundle.Waypoint_Route_Point_Display_String()), + end.getTimestamp(), endingPoint, formattedEnd)); //NON-NLS } } @@ -660,9 +685,17 @@ class KMLReport implements GeneralReportModule { return strbuf.toString(); } - private String getFormattedDetails(Waypoint point) { + /** + * Get the nicely formatted details for the given waypoint. + * + * @param point Waypoint object + * @param header String details header + * + * @return HTML formatted String of details for given waypoint + */ + private String getFormattedDetails(Waypoint point, String header) { StringBuilder result = new StringBuilder(); //NON-NLS - result.append(getDetailsHeader(point)) + result.append(String.format("

    %s

    ", header)) .append(formatAttribute("Name", point.getLabel())); Long timestamp = point.getTimestamp(); @@ -692,15 +725,6 @@ class KMLReport implements GeneralReportModule { return String.format(HTML_PROP_FORMAT, title, value); } - /* - * This current retains the headers from the original report. There is - * probably a better way to do this using the display value of the - * blackboard artifact. - */ - private String getDetailsHeader(Waypoint point) { - return String.format("

    %s

    ", point.getType().getDisplayName()); - } - /** * Returns an HTML formatted string of all the * @@ -712,7 +736,7 @@ class KMLReport implements GeneralReportModule { List points = route.getRoute(); StringBuilder result = new StringBuilder(); //NON-NLS - result.append(String.format("

    %s

    ", "GPS Route")) + result.append(String.format("

    %s

    ", Bundle.Route_Details_Header())) .append(formatAttribute("Name", route.getLabel())); Long timestamp = route.getTimestamp(); @@ -745,6 +769,14 @@ class KMLReport implements GeneralReportModule { return result.toString(); } + /** + * Helper functions for consistently formatting longitude and latitude. + * + * @param latitude Double latitude value + * @param longitude Double longitude value + * + * @return String Nicely formatted double values separated by a comma + */ private String formattedCoordinates(Double latitude, Double longitude) { if (latitude == null || longitude == null) { return ""; From 9f658a2ebd82f0f21bc679b742929dc31325a168 Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Mon, 28 Oct 2019 11:17:36 -0400 Subject: [PATCH 089/134] Small fixed to KMLReport --- .../src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 b72ad14f73..7f205da890 100644 --- a/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java +++ b/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java @@ -348,7 +348,7 @@ class KMLReport implements GeneralReportModule { */ void addWaypoints(List points, Element folder, FeatureColor waypointColor, String headerLabel) { for(Waypoint point: points) { - addContent(folder, point.getLabel(), waypointColor, String.format("

    %s

    ", headerLabel), point.getTimestamp(), makePoint(point), point.getLatitude(), point.getLongitude()); + addContent(folder, point.getLabel(), waypointColor, getFormattedDetails(point, headerLabel), point.getTimestamp(), makePoint(point), point.getLatitude(), point.getLongitude()); } } From a17bbacf9701a0c4769eb83884d1c8d8ab253972 Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Mon, 28 Oct 2019 11:57:28 -0400 Subject: [PATCH 090/134] Change getGPSRoutes to getRoutes --- Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java | 2 +- .../src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java index bf93fe8803..9d91cf8d9c 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java @@ -58,7 +58,7 @@ public final class Route { * * @throws GeoLocationDataException */ - static public List getGPSRoutes(SleuthkitCase skCase) throws GeoLocationDataException { + static public List getRoutes(SleuthkitCase skCase) throws GeoLocationDataException { List artifacts = ArtifactUtils.getArtifactsForType(skCase, BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE); List routes = new ArrayList<>(); 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 7f205da890..002215bf04 100644 --- a/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java +++ b/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java @@ -379,7 +379,7 @@ class KMLReport implements GeneralReportModule { * @throws TskCoreException */ void makeRoutes(SleuthkitCase skCase) throws GeoLocationDataException { - List routes = Route.getGPSRoutes(skCase); + List routes = Route.getRoutes(skCase); if(routes == null) { return; From 74130ea1079e9e06da6828a0dacda0d8b2356252 Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Tue, 29 Oct 2019 08:14:37 -0400 Subject: [PATCH 091/134] Getting attributes as a list instead of individually --- .../geolocation/datamodel/ArtifactUtils.java | 151 +++--------------- .../datamodel/ArtifactWaypoint.java | 144 ++++++----------- .../geolocation/datamodel/EXIFWaypoint.java | 26 +-- .../datamodel/GPSSearchWaypoint.java | 66 -------- .../datamodel/GeoLocationDataException.java | 28 ++-- .../datamodel/GeolocationUtils.java | 56 ++++--- .../datamodel/LastKnownWaypoint.java | 36 ++++- .../autopsy/geolocation/datamodel/Route.java | 104 +++++------- .../geolocation/datamodel/SearchWaypoint.java | 77 +++++++++ .../datamodel/TrackpointWaypoint.java | 34 ++-- .../geolocation/datamodel/Waypoint.java | 94 +++++++---- 11 files changed, 364 insertions(+), 452 deletions(-) delete mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GPSSearchWaypoint.java create mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/datamodel/SearchWaypoint.java diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactUtils.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactUtils.java index e99350072e..b5cf4ab75c 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactUtils.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactUtils.java @@ -18,155 +18,48 @@ */ package org.sleuthkit.autopsy.geolocation.datamodel; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; /** - * Utilities for simplifying and reducing redundant when getting Artifact - * attributes. + * Utilities for simplifying the use of Waypoint Artifacts. */ final class ArtifactUtils { - + /** * Private constructor for this Utility class. */ private ArtifactUtils() { - + } /** - * Helper function for getting a String attribute from an artifact. This - * will work for all attributes + * Gets the list of attributes from the artifact and puts them into a map + * with the ATRIBUTE_TYPE as the key. * - * @param artifact The BlackboardArtifact to get the attributeType - * @param attributeType BlackboardAttribute attributeType + * @param artifact BlackboardArtifact current artifact * - * @return String value for the given attribute or null if attribute was not - * set for the given artifact + * @return A Map of BlackboardAttributes for the given artifact with + * ATTRIBUTE_TYPE as the key. * - * @throws TskCoreException + * @throws GeoLocationDataException */ - static String getString(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) throws GeoLocationDataException { - if (artifact == null) { - return null; + static Map getAttributesFromArtifactAsMap(BlackboardArtifact artifact) throws GeoLocationDataException { + Map attributeMap = new HashMap<>(); + try { + List attributeList = artifact.getAttributes(); + for (BlackboardAttribute attribute : attributeList) { + BlackboardAttribute.ATTRIBUTE_TYPE type = BlackboardAttribute.ATTRIBUTE_TYPE.fromID(attribute.getAttributeType().getTypeID()); + attributeMap.put(type, attribute); + } + } catch (TskCoreException ex) { + throw new GeoLocationDataException("Unable to get attributes from artifact", ex); } - BlackboardAttribute attribute; - try{ - attribute = artifact.getAttribute(new BlackboardAttribute.Type(attributeType)); - } catch(TskCoreException ex) { - throw new GeoLocationDataException(String.format("Failed to get double attribute for artifact: %d", artifact.getArtifactID()), ex); - } - return (attribute != null ? attribute.getDisplayString() : null); + return attributeMap; } - - /** - * Helper function for getting a Double attribute from an artifact. - * - * @param artifact The BlackboardArtifact to get the attributeType - * @param attributeType BlackboardAttribute attributeType - * - * @return Double value for the given attribute. - * - * @throws TskCoreException - */ - static Double getDouble(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) throws GeoLocationDataException { - if (artifact == null) { - return null; - } - - if (attributeType.getValueType() != BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DOUBLE) { - return null; - } - - BlackboardAttribute attribute; - try{ - attribute = artifact.getAttribute(new BlackboardAttribute.Type(attributeType)); - } catch(TskCoreException ex) { - throw new GeoLocationDataException(String.format("Failed to get double attribute for artifact: %d", artifact.getArtifactID()), ex); - } - return (attribute != null ? attribute.getValueDouble() : null); - } - - /** - * Helper function for getting a Long attribute from an artifact. - * - * @param artifact The BlackboardArtifact to get the attributeType - * @param attributeType BlackboardAttribute attributeType - * - * @return Long value for the given attribute. - * - * @throws TskCoreException - */ - static Long getLong(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) throws GeoLocationDataException { - if (artifact == null) { - return null; - } - - if (attributeType.getValueType() != BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.LONG - || attributeType.getValueType() != BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME) { - return null; - } - - BlackboardAttribute attribute; - try{ - attribute = artifact.getAttribute(new BlackboardAttribute.Type(attributeType)); - } catch(TskCoreException ex) { - throw new GeoLocationDataException(String.format("Failed to get double attribute for artifact: %d", artifact.getArtifactID()), ex); - } - return (attribute != null ? attribute.getValueLong() : null); - } - - /** - * Helper function for getting a Integer attribute from an artifact. - * - * @param artifact The BlackboardArtifact to get the attributeType - * @param attributeType BlackboardAttribute attributeType - * - * @return Integer value for the given attribute. - * - * @throws TskCoreException - */ - static Integer getInteger(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) throws GeoLocationDataException { - if (artifact == null) { - return null; - } - - if (attributeType.getValueType() != BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.INTEGER) { - return null; - } - - BlackboardAttribute attribute; - try{ - attribute = artifact.getAttribute(new BlackboardAttribute.Type(attributeType)); - } catch(TskCoreException ex) { - throw new GeoLocationDataException(String.format("Failed to get double attribute for artifact: %d", artifact.getArtifactID()), ex); - } - return (attribute != null ? attribute.getValueInt() : null); - } - - /** - * Get a list of artifacts for the given BlackboardArtifact.Type. - * - * @param skCase Currently open case - * @param type BlackboardArtifact.Type to retrieve - * - * @return List of BlackboardArtifacts - * - * @throws GeoLocationDataException - */ - static List getArtifactsForType(SleuthkitCase skCase, BlackboardArtifact.ARTIFACT_TYPE type) throws GeoLocationDataException { - List artifacts; - try{ - artifacts = skCase.getBlackboardArtifacts(type); - } catch(TskCoreException ex) { - throw new GeoLocationDataException(String.format("Unable to get artifacts for type: %s", type.getLabel()), ex); - } - - return artifacts; - } - } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactWaypoint.java index f30b2637b2..1ce27b28fc 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactWaypoint.java @@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.geolocation.datamodel; import java.util.Collections; import java.util.List; +import java.util.Map; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -43,81 +44,39 @@ class ArtifactWaypoint implements Waypoint { final private List immutablePropertiesList; /** - * Construct a simple waypoint with the given artifact and assign the given - * type. - * - * This constructor is for use with artifacts that use the basic attributes - * of: TSK_NAME TSK_GEO_LONGITUDE TSK_GEO_LATITUDE TSK_GEO_ALITUDE - * TSK_DATETIME + * Construct a waypoint with the given artifact. * * @param artifact BlackboardArtifact for this waypoint - * @param type Waypoint type * - * @throws GeoLocationDataException + * @throws GeoLocationDataException Exception will be thrown if artifact did + * not have a valid longitude and latitude. */ protected ArtifactWaypoint(BlackboardArtifact artifact) throws GeoLocationDataException { this(artifact, - getLabelFromArtifact(artifact)); + ArtifactUtils.getAttributesFromArtifactAsMap(artifact)); } /** - * For use by subclasses that want to customize the label, but use the basic - * attributes of: TSK_GEO_LONGITUDE TSK_GEO_LATITUDE TSK_GEO_ALITUDE - * TSK_DATETIME + * Constructor that sets all of the member variables. * - * @param artifact BlackboardArtifact for this waypoint - * @param label String label for this waypoint - * @param type Waypoint type + * @param artifact BlackboardArtifact for this waypoint + * @param label String waypoint label + * @param timestamp Long timestamp, epoch seconds + * @param latitude Double waypoint latitude + * @param longitude Double waypoint longitude + * @param altitude Double waypoint altitude + * @param image AbstractFile image for waypoint, this maybe null + * @param type Waypoint.Type value for waypoint + * @param attributeMap A Map of attributes for the given artifact * - * @throws GeoLocationDataException + * @throws GeoLocationDataException Exception will be thrown if artifact did + * not have a valid longitude and latitude. */ - protected ArtifactWaypoint(BlackboardArtifact artifact, String label) throws GeoLocationDataException { - this(artifact, - label, - getTimestampFromArtifact(artifact), - null); - } + protected ArtifactWaypoint(BlackboardArtifact artifact, String label, Long timestamp, Double latitude, Double longitude, Double altitude, AbstractFile image, Map attributeMap) throws GeoLocationDataException { + if (longitude == null || latitude == null) { + throw new GeoLocationDataException("Invalid waypoint, null value passed for longitude or latitude"); + } - /** - * Constructor for use by Waypoint subclasses that want to customize the - * label, specify the timestamp or supply and image. - * - * Uses the following attributes to set longitude, latitude, altitude: - * TSK_GEO_LONGITUDE TSK_GEO_LATITUDE TSK_GEO_ALITUDE - * - * @param artifact BlackboardArtifact for this waypoint - * @param label String waypoint label - * @param timestamp Long timestamp, epoch seconds - * @param image AbstractFile image for waypoint, this maybe null - * @param type Waypoint.Type value for waypoint - * - * @throws GeoLocationDataException - */ - protected ArtifactWaypoint(BlackboardArtifact artifact, String label, Long timestamp, AbstractFile image) throws GeoLocationDataException { - this(artifact, - label, - timestamp, - ArtifactUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE), - ArtifactUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE), - ArtifactUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE), - image); - } - - /** - * Private constructor that sets all of the member variables. - * - * @param artifact BlackboardArtifact for this waypoint - * @param label String waypoint label - * @param timestamp Long timestamp, epoch seconds - * @param latitude Double waypoint latitude - * @param longitude Double waypoint longitude - * @param altitude Double waypoint altitude - * @param image AbstractFile image for waypoint, this maybe null - * @param type Waypoint.Type value for waypoint - * - * @throws GeoLocationDataException - */ - private ArtifactWaypoint(BlackboardArtifact artifact, String label, Long timestamp, Double latitude, Double longitude, Double altitude, AbstractFile image) throws GeoLocationDataException { this.artifact = artifact; this.label = label; this.image = image; @@ -126,7 +85,26 @@ class ArtifactWaypoint implements Waypoint { this.latitude = latitude; this.altitude = altitude; - immutablePropertiesList = Collections.unmodifiableList(GeolocationUtils.getOtherGeolocationProperties(artifact)); + immutablePropertiesList = Collections.unmodifiableList(GeolocationUtils.createGeolocationProperties(attributeMap)); + } + + /** + * Constructs a new ArtifactWaypoint. + * + * @param artifact BlackboardArtifact for this waypoint + * @param attributeMap A Map of the BlackboardAttributes for the given + * artifact. + * + * @throws GeoLocationDataException + */ + private ArtifactWaypoint(BlackboardArtifact artifact, Map attributeMap) throws GeoLocationDataException { + this(artifact, + getLabelFromArtifact(attributeMap), + attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME).getValueLong() : null, + attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE).getValueDouble() : null, + attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE).getValueDouble() : null, + attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE).getValueDouble() : null, + null, attributeMap); } /** @@ -167,48 +145,26 @@ class ArtifactWaypoint implements Waypoint { public AbstractFile getImage() { return image; } - + @Override public List getOtherProperties() { return immutablePropertiesList; } /** - * Get the timestamp attribute based on type for the given artifact. + * Gets the label for this waypoint. * * @param artifact BlackboardArtifact for waypoint * - * @return Long timestamp or null if a value was not found. - * - * @throws GeoLocationDataException + * @return Returns a label for the waypoint, or empty string if no label was + * found. */ - private static Long getTimestampFromArtifact(BlackboardArtifact artifact) throws GeoLocationDataException { - if (artifact == null) { - return null; + private static String getLabelFromArtifact(Map attributeMap) { + BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); + if (attribute != null) { + return attribute.getDisplayString(); } - return ArtifactUtils.getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME); - } - - /** - * Gets the label for this waypoint based on the artifact type. - * - * This is the original waypoint naming code from the KML report, we may - * what to thinki about better ways to name some of the point. - * - * @param artifact BlackboardArtifact for waypoint - * - * @return Returns a label for the waypoint based on artifact type, or empty - * string if no label was found. - * - * @throws GeoLocationDataException - */ - private static String getLabelFromArtifact(BlackboardArtifact artifact) throws GeoLocationDataException { - - String typeLabel = ArtifactUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); - if (typeLabel == null) { - typeLabel = ""; - } - return typeLabel; + return ""; } } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/EXIFWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/EXIFWaypoint.java index 2ffdc92d23..399ae17ea1 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/EXIFWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/EXIFWaypoint.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.geolocation.datamodel; +import java.util.Map; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -36,22 +37,26 @@ final class EXIFWaypoint extends ArtifactWaypoint { * @throws GeoLocationDataException */ protected EXIFWaypoint(BlackboardArtifact artifact) throws GeoLocationDataException { - this(artifact, getImageFromArtifact(artifact)); + this(artifact, ArtifactUtils.getAttributesFromArtifactAsMap(artifact), getImageFromArtifact(artifact)); } /** - * Private constructor to help with the construction of EXIFWaypoints. + * Constructs new waypoint using the given artifact and attribute map. * - * @param artifact Waypoint BlackboardArtifact - * @param image EXIF AbstractFile image + * @param artifact Waypoint BlackboardArtifact + * @param attributeMap Map of artifact attributes + * @param image EXIF AbstractFile image * * @throws GeoLocationDataException */ - private EXIFWaypoint(BlackboardArtifact artifact, AbstractFile image) throws GeoLocationDataException { + private EXIFWaypoint(BlackboardArtifact artifact, Map attributeMap, AbstractFile image) throws GeoLocationDataException { super(artifact, image != null ? image.getName() : "", - ArtifactUtils.getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED), - image); + attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED).getValueLong() : null, + attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE).getValueDouble() : null, + attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE).getValueDouble() : null, + attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE).getValueDouble() : null, + image, attributeMap); } /** @@ -68,15 +73,14 @@ final class EXIFWaypoint extends ArtifactWaypoint { AbstractFile abstractFile = null; BlackboardArtifact.ARTIFACT_TYPE artifactType = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID()); if (artifactType == BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF) { - - try{ + + try { abstractFile = artifact.getSleuthkitCase().getAbstractFileById(artifact.getObjectID()); - } catch(TskCoreException ex) { + } catch (TskCoreException ex) { throw new GeoLocationDataException(String.format("Unable to getAbstractFileByID for artifact: %d", artifact.getArtifactID(), artifact.getArtifactID()), ex); } } return abstractFile; } - } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GPSSearchWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GPSSearchWaypoint.java deleted file mode 100755 index 1c5efec3f4..0000000000 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GPSSearchWaypoint.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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 org.openide.util.NbBundle.Messages; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardAttribute; - -/** - * A GPSSearchWaypoint is a subclass of ArtifactWaypoint. - */ -final class GPSSearchWaypoint extends ArtifactWaypoint { - - @Messages({ - "SearchWaypoint_DisplayLabel=GPS Search" - }) - - /** - * Construct a GPS Search waypoint. - * - * @throws GeoLocationDataException - */ - GPSSearchWaypoint(BlackboardArtifact artifact) throws GeoLocationDataException { - super(artifact, getLabelFromArtifact(artifact)); - } - - /** - * Returns a Label for a GPS_SEARCH artifact. - * - * @param artifact BlackboardArtifact for waypoint - * - * @return String label for the artifacts way point. - * - * @throws GeoLocationDataException - */ - private static String getLabelFromArtifact(BlackboardArtifact artifact) throws GeoLocationDataException { - String typeLabel = ArtifactUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); - - if (typeLabel == null || typeLabel.isEmpty()) { - typeLabel = ArtifactUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION); - } - - if (typeLabel == null || typeLabel.isEmpty()) { - typeLabel = Bundle.SearchWaypoint_DisplayLabel(); - } - - return typeLabel; - } - -} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeoLocationDataException.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeoLocationDataException.java index 24e8d142bc..44aad62b58 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeoLocationDataException.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeoLocationDataException.java @@ -1,25 +1,31 @@ /* - * 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.datamodel; /** - * + * An exception class for the Geolocation dateModel; * */ public class GeoLocationDataException extends Exception { private static final long serialVersionUID = 1L; - /** - * Default constructor when error message is not available - */ - public GeoLocationDataException() { - super("No error message available."); - } - /** * Create exception containing the error message * diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtils.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtils.java index 51a7f73107..644c65dacc 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtils.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtils.java @@ -20,36 +20,31 @@ package org.sleuthkit.autopsy.geolocation.datamodel; import java.util.ArrayList; import java.util.List; -import org.sleuthkit.datamodel.BlackboardArtifact; +import java.util.Map; +import java.util.Set; import org.sleuthkit.datamodel.BlackboardAttribute; /** - * GeolocationUtilis class for common to be share across in the package + * GeolocationUtilis class for common shared between Routes and Waypoints. * */ final class GeolocationUtils { /** - * This is a list of attributes that are related to a geolocation waypoint - * but are for information\artifact properties purpose. They are not needed - * for the placement of a point on a map; + * This is a list of attributes that are already being handled by the + * waypoint classes and will have get functions. */ - private static final BlackboardAttribute.ATTRIBUTE_TYPE[] OTHER_GEO_ATTRIBUTES = { - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION, - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START, - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_END, + private static final BlackboardAttribute.ATTRIBUTE_TYPE[] ALREADY_HANDLED_ATTRIBUTES = { + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED, - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_VELOCITY, - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_BEARING, - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_HPRECISION, - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_VPRECISION, - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_MAPDATUM, - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME, - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_SOURCE, - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FLAG, - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_MAKE, - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_MODEL - }; + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END,}; /** * This is a Utility class that should not be constructed. @@ -59,7 +54,9 @@ final class GeolocationUtils { } /** - * Get a list of Waypoint.Property objects for the given artifact. + * Get a list of Waypoint.Property objects for the given artifact. This list + * will not include attributes that the Waypoint interfact has get functions + * for. * * @param artifact Blackboard artifact to get attributes\properties from * @@ -67,20 +64,21 @@ final class GeolocationUtils { * * @throws GeoLocationDataException */ - static List getOtherGeolocationProperties(BlackboardArtifact artifact) throws GeoLocationDataException { + static List createGeolocationProperties(Map attributeMap) throws GeoLocationDataException { List list = new ArrayList<>(); - for (BlackboardAttribute.ATTRIBUTE_TYPE type : OTHER_GEO_ATTRIBUTES) { - String key = type.getDisplayName(); - String value = ArtifactUtils.getString(artifact, type); + Set keys = attributeMap.keySet(); - if (value == null) { - value = ""; - } + for (BlackboardAttribute.ATTRIBUTE_TYPE type : ALREADY_HANDLED_ATTRIBUTES) { + keys.remove(type); + } + + for (BlackboardAttribute.ATTRIBUTE_TYPE type : keys) { + String key = type.getDisplayName(); + String value = attributeMap.get(type).getDisplayString(); list.add(new Waypoint.Property(key, value)); } - return list; } } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/LastKnownWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/LastKnownWaypoint.java index 32aec6f041..4f44cb956d 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/LastKnownWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/LastKnownWaypoint.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.geolocation.datamodel; +import java.util.Map; import org.openide.util.NbBundle.Messages; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -29,21 +30,48 @@ import org.sleuthkit.datamodel.BlackboardAttribute; "LastKnownWaypoint_Label=Last Known Location",}) final class LastKnownWaypoint extends ArtifactWaypoint { + /** + * Constructs a new waypoint. + * + * @param artifact BlackboardArtifact from which to construct the waypoint + * + * @throws GeoLocationDataException + */ protected LastKnownWaypoint(BlackboardArtifact artifact) throws GeoLocationDataException { - super(artifact, getLabelFromArtifact(artifact)); + this(artifact, ArtifactUtils.getAttributesFromArtifactAsMap(artifact)); + } + + /** + * Constructs a new waypoint with the given artifact and attribute map. + * + * @param artifact BlackboardArtifact from which to construct the + * waypoint + * @param attributeMap Map of artifact attributes + * + * @throws GeoLocationDataException + */ + private LastKnownWaypoint(BlackboardArtifact artifact, Map attributeMap) throws GeoLocationDataException { + super(artifact, + getLabelFromArtifact(attributeMap), + attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME).getValueLong() : null, + attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE).getValueDouble() : null, + attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE).getValueDouble() : null, + attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE).getValueDouble() : null, + null, attributeMap); } /** * Gets the label for a TSK_LAST_KNOWN_LOCATION. * - * @param artifact BlackboardArtifact to get label from + * @param attributeMap Map of artifact attributes for this waypoint * * @return String value from attribute TSK_NAME or LastKnownWaypoint_Label * * @throws GeoLocationDataException */ - private static String getLabelFromArtifact(BlackboardArtifact artifact) throws GeoLocationDataException { - String label = ArtifactUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); + private static String getLabelFromArtifact(Map attributeMap) throws GeoLocationDataException { + BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); + String label = attribute.getDisplayString(); if (label == null || label.isEmpty()) { label = Bundle.LastKnownWaypoint_Label(); diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java index 9d91cf8d9c..a7d9c651cf 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java @@ -22,10 +22,12 @@ package org.sleuthkit.autopsy.geolocation.datamodel; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import org.openide.util.NbBundle.Messages; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; /** * A Route represents a TSK_GPS_ROUTE artifact which has a start and end point @@ -58,9 +60,14 @@ public final class Route { * * @throws GeoLocationDataException */ - static public List getRoutes(SleuthkitCase skCase) throws GeoLocationDataException { - List artifacts = ArtifactUtils.getArtifactsForType(skCase, BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE); - + static public List getRoutes(SleuthkitCase skCase) throws GeoLocationDataException { + List artifacts = null; + try { + artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE); + } catch (TskCoreException ex) { + throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_BOOKMARK", ex); + } + List routes = new ArrayList<>(); for (BlackboardArtifact artifact : artifacts) { Route route = new Route(artifact); @@ -76,21 +83,28 @@ public final class Route { */ Route(BlackboardArtifact artifact) throws GeoLocationDataException { points = new ArrayList<>(); - Waypoint point = getRouteStartPoint(artifact); + + Map attributeMap = ArtifactUtils.getAttributesFromArtifactAsMap(artifact); + + Waypoint point = getRouteStartPoint(attributeMap); if (point != null) { points.add(point); } - point = getRouteEndPoint(artifact); + point = getRouteEndPoint(attributeMap); if (point != null) { points.add(point); } - altitude = getRouteAltitude(artifact); - timestamp = getRouteTimestamp(artifact); - immutablePropertiesList = Collections.unmodifiableList(GeolocationUtils.getOtherGeolocationProperties(artifact)); + BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE); + altitude = attribute != null ? attribute.getValueDouble() : null; + + attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME); + timestamp = attribute != null ? attribute.getValueLong() : null; + + immutablePropertiesList = Collections.unmodifiableList(GeolocationUtils.createGeolocationProperties(attributeMap)); } /** @@ -143,81 +157,41 @@ public final class Route { /** * Get the route start point. * - * @param artifact The BlackboardARtifact object from which this route is - * created + * @param attributeMap Map of artifact attributes for this waypoint * - * @return Start RoutePoint or null if valid longitude and latitude are not - * found + * @return Start RoutePoint * - * @throws GeoLocationDataException + * @throws GeoLocationDataException when longitude or latitude is null */ - private Waypoint getRouteStartPoint(BlackboardArtifact artifact) throws GeoLocationDataException { - Double latitude; - Double longitude; - RoutePoint point = null; - - latitude = ArtifactUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START); - longitude = ArtifactUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START); + private Waypoint getRouteStartPoint(Map attributeMap) throws GeoLocationDataException { + BlackboardAttribute latitude = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START); + BlackboardAttribute longitude = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START); if (latitude != null && longitude != null) { - point = new RoutePoint(this, latitude, longitude, Bundle.Route_Start_Label()); + return new RoutePoint(this, latitude.getValueDouble(), longitude.getValueDouble(), Bundle.Route_Start_Label()); + } else { + throw new GeoLocationDataException("Unable to create route start point, invalid longitude and/or latitude"); } - - return point; } /** * Get the route End point. * - * @param artifact The BlackboardARtifact object from which this route is - * created + * @param attributeMap Map of artifact attributes for this waypoint * * @return End RoutePoint or null if valid longitude and latitude are not * found * - * @throws GeoLocationDataException + * @throws GeoLocationDataException when longitude or latitude is null */ - private Waypoint getRouteEndPoint(BlackboardArtifact artifact) throws GeoLocationDataException { - Double latitude; - Double longitude; - RoutePoint point = null; - - latitude = ArtifactUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END); - longitude = ArtifactUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END); + private Waypoint getRouteEndPoint(Map attributeMap) throws GeoLocationDataException { + BlackboardAttribute latitude = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END); + BlackboardAttribute longitude = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END); if (latitude != null && longitude != null) { - point = new RoutePoint(this, latitude, longitude, Bundle.Route_End_Label()); + return new RoutePoint(this, latitude.getValueDouble(), longitude.getValueDouble(), Bundle.Route_End_Label()); + }else { + throw new GeoLocationDataException("Unable to create route end point, invalid longitude and/or latitude"); } - - return point; } - - /** - * Get the Altitude for this route. - * - * @param artifact The BlackboardARtifact object from which this route is - * created - * - * @return The Altitude, or null if none was found - * - * @throws GeoLocationDataException - */ - private Double getRouteAltitude(BlackboardArtifact artifact) throws GeoLocationDataException { - return ArtifactUtils.getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE); - } - - /** - * Get the timestamp for this route. - * - * @param artifact The BlackboardARtifact object from which this route is - * created - * - * @return The timestamp attribute, or null if none was found - * - * @throws GeoLocationDataException - */ - private Long getRouteTimestamp(BlackboardArtifact artifact) throws GeoLocationDataException { - return ArtifactUtils.getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME); - } - } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/SearchWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/SearchWaypoint.java new file mode 100755 index 0000000000..0041c05870 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/SearchWaypoint.java @@ -0,0 +1,77 @@ +/* + * 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.util.Map; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; + +/** + * A SearchWaypoint is a subclass of ArtifactWaypoint. + */ +final class SearchWaypoint extends ArtifactWaypoint { + + @Messages({ + "SearchWaypoint_DisplayLabel=GPS Search" + }) + + /** + * Construct a waypoint for TSK_GPS_SEARCH artifact. + * + * @throws GeoLocationDataException + */ + SearchWaypoint(BlackboardArtifact artifact) throws GeoLocationDataException { + this(artifact, ArtifactUtils.getAttributesFromArtifactAsMap(artifact)); + } + + private SearchWaypoint(BlackboardArtifact artifact, Map attributeMap) throws GeoLocationDataException { + super(artifact, + getLabelFromArtifact(attributeMap), + attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME).getValueLong() : null, + attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE).getValueDouble() : null, + attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE).getValueDouble() : null, + attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE).getValueDouble() : null, + null, attributeMap); + } + + /** + * Returns a Label for a GPS_SEARCH artifact. + * + * @param attributeMap Map of artifact attributes + * + * @return String label for the artifacts way point. + * + * @throws GeoLocationDataException + */ + private static String getLabelFromArtifact(Map attributeMap) throws GeoLocationDataException { + BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); + if (attribute != null) { + return attribute.getDisplayString(); + } + + attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION); + if (attribute != null) { + return attribute.getDisplayString(); + } + + return Bundle.SearchWaypoint_DisplayLabel(); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/TrackpointWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/TrackpointWaypoint.java index 3ef3279239..01b2f6557e 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/TrackpointWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/TrackpointWaypoint.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.geolocation.datamodel; +import java.util.Map; import org.openide.util.NbBundle.Messages; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -37,7 +38,17 @@ final class TrackpointWaypoint extends ArtifactWaypoint { * @throws GeoLocationDataException */ TrackpointWaypoint(BlackboardArtifact artifact) throws GeoLocationDataException { - super(artifact, getLabelFromArtifact(artifact)); + this(artifact, ArtifactUtils.getAttributesFromArtifactAsMap(artifact)); + } + + private TrackpointWaypoint(BlackboardArtifact artifact, Map attributeMap) throws GeoLocationDataException { + super(artifact, + getLabelFromArtifact(attributeMap), + attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME).getValueLong() : null, + attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE).getValueDouble() : null, + attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE).getValueDouble() : null, + attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE).getValueDouble() : null, + null, attributeMap); } /** @@ -50,23 +61,24 @@ final class TrackpointWaypoint extends ArtifactWaypoint { * * @throws GeoLocationDataException */ - private static String getLabelFromArtifact(BlackboardArtifact artifact) throws GeoLocationDataException { + private static String getLabelFromArtifact(Map attributeMap) throws GeoLocationDataException { - String typeLabel = ArtifactUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); - - if (typeLabel == null || typeLabel.isEmpty()) { - typeLabel = ArtifactUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME); + BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); + if (attribute != null) { + return attribute.getDisplayString(); } - if (typeLabel == null || typeLabel.isEmpty()) { - typeLabel = ArtifactUtils.getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FLAG); + attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME); + if (attribute != null) { + return attribute.getDisplayString(); } - if (typeLabel == null || typeLabel.isEmpty()) { - typeLabel = Bundle.TrackpointWaypoint_DisplayLabel(); + attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FLAG); + if (attribute != null) { + return attribute.getDisplayString(); } - return typeLabel; + return Bundle.TrackpointWaypoint_DisplayLabel(); } } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java index aaf5a1afe5..d9affaebe6 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java @@ -20,15 +20,19 @@ package org.sleuthkit.autopsy.geolocation.datamodel; import java.util.ArrayList; import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; /** * The basic details of a waypoint. * */ public interface Waypoint { + static final Logger logger = Logger.getLogger(Waypoint.class.getName()); /** * Interface to describe a waypoint. A waypoint is made up of * a longitude, latitude, label, timestamp, type, image and altitude. @@ -118,16 +122,21 @@ public interface Waypoint { * @throws GeoLocationDataException */ static List getTrackpointWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { - List artifacts = ArtifactUtils.getArtifactsForType(skCase, BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT); + 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) { - ArtifactWaypoint point = new TrackpointWaypoint(artifact); - // Only add to the list if the point has a valid latitude - // and longitude. - if (point.getLatitude() != null && point.getLongitude() != null) { + try{ + ArtifactWaypoint 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; } @@ -142,17 +151,24 @@ public interface Waypoint { * @throws GeoLocationDataException */ static List getEXIFWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { - List artifacts = ArtifactUtils.getArtifactsForType(skCase, BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF); + 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) { - ArtifactWaypoint point = new EXIFWaypoint(artifact); - // Only add to the list if the point has a valid latitude - // and longitude. - if (point.getLatitude() != null && point.getLongitude() != null) { + try{ + ArtifactWaypoint 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; @@ -168,17 +184,22 @@ public interface Waypoint { * @throws GeoLocationDataException */ static List getSearchWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { - List artifacts = ArtifactUtils.getArtifactsForType(skCase, BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH); + 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) { - ArtifactWaypoint point = new GPSSearchWaypoint(artifact); - // Only add to the list if the point has a valid latitude - // and longitude. - if (point.getLatitude() != null && point.getLongitude() != null) { + try{ + ArtifactWaypoint 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; @@ -194,18 +215,22 @@ public interface Waypoint { * @throws GeoLocationDataException */ static List getLastKnownWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { - List artifacts = ArtifactUtils.getArtifactsForType(skCase, BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION); + 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) { - ArtifactWaypoint point = new LastKnownWaypoint(artifact); - // Only add to the list if the point has a valid latitude - // and longitude. - if (point.getLatitude() != null && point.getLongitude() != null) { + for (BlackboardArtifact artifact : artifacts) { + try{ + ArtifactWaypoint 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; @@ -221,17 +246,22 @@ public interface Waypoint { * @throws GeoLocationDataException */ static List getBookmarkWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { - List artifacts = ArtifactUtils.getArtifactsForType(skCase, BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK); + 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) { - ArtifactWaypoint point = new ArtifactWaypoint(artifact); - // Only add to the list if the point has a valid latitude - // and longitude. - if (point.getLatitude() != null && point.getLongitude() != null) { + try{ + ArtifactWaypoint point = new ArtifactWaypoint(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; From e145592d38346730304868d0ddfcd2304a2059cf Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Tue, 29 Oct 2019 11:40:06 -0400 Subject: [PATCH 092/134] Modified based on comments from Richard --- .../geolocation/datamodel/ArtifactUtils.java | 65 -------------- .../datamodel/ArtifactWaypoint.java | 41 +++++++-- .../geolocation/datamodel/EXIFWaypoint.java | 7 +- .../datamodel/GeolocationUtils.java | 84 ------------------- .../datamodel/LastKnownWaypoint.java | 10 +-- .../autopsy/geolocation/datamodel/Route.java | 42 ++++------ .../geolocation/datamodel/RoutePoint.java | 2 +- .../geolocation/datamodel/SearchWaypoint.java | 11 ++- .../datamodel/TrackpointWaypoint.java | 10 +-- .../geolocation/datamodel/Waypoint.java | 50 +++++++++++ 10 files changed, 119 insertions(+), 203 deletions(-) delete mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactUtils.java delete mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtils.java diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactUtils.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactUtils.java deleted file mode 100755 index b5cf4ab75c..0000000000 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactUtils.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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.util.HashMap; -import java.util.List; -import java.util.Map; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * Utilities for simplifying the use of Waypoint Artifacts. - */ -final class ArtifactUtils { - - /** - * Private constructor for this Utility class. - */ - private ArtifactUtils() { - - } - - /** - * Gets the list of attributes from the artifact and puts them into a map - * with the ATRIBUTE_TYPE as the key. - * - * @param artifact BlackboardArtifact current artifact - * - * @return A Map of BlackboardAttributes for the given artifact with - * ATTRIBUTE_TYPE as the key. - * - * @throws GeoLocationDataException - */ - static Map getAttributesFromArtifactAsMap(BlackboardArtifact artifact) throws GeoLocationDataException { - Map attributeMap = new HashMap<>(); - try { - List attributeList = artifact.getAttributes(); - for (BlackboardAttribute attribute : attributeList) { - BlackboardAttribute.ATTRIBUTE_TYPE type = BlackboardAttribute.ATTRIBUTE_TYPE.fromID(attribute.getAttributeType().getTypeID()); - attributeMap.put(type, attribute); - } - } catch (TskCoreException ex) { - throw new GeoLocationDataException("Unable to get attributes from artifact", ex); - } - - return attributeMap; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactWaypoint.java index 1ce27b28fc..a2a0f3315f 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactWaypoint.java @@ -19,11 +19,13 @@ package org.sleuthkit.autopsy.geolocation.datamodel; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.TskCoreException; /** * Representation of a Waypoint created from a BlackboardArtifact. @@ -42,7 +44,7 @@ class ArtifactWaypoint implements Waypoint { // This list is not expected to change after construction so the // constructor will take care of creating an unmodifiable List final private List immutablePropertiesList; - + /** * Construct a waypoint with the given artifact. * @@ -51,9 +53,9 @@ class ArtifactWaypoint implements Waypoint { * @throws GeoLocationDataException Exception will be thrown if artifact did * not have a valid longitude and latitude. */ - protected ArtifactWaypoint(BlackboardArtifact artifact) throws GeoLocationDataException { + ArtifactWaypoint(BlackboardArtifact artifact) throws GeoLocationDataException { this(artifact, - ArtifactUtils.getAttributesFromArtifactAsMap(artifact)); + getAttributesFromArtifactAsMap(artifact)); } /** @@ -61,18 +63,17 @@ class ArtifactWaypoint implements Waypoint { * * @param artifact BlackboardArtifact for this waypoint * @param label String waypoint label - * @param timestamp Long timestamp, epoch seconds + * @param timestamp Long timestamp, unix/java epoch seconds * @param latitude Double waypoint latitude * @param longitude Double waypoint longitude * @param altitude Double waypoint altitude * @param image AbstractFile image for waypoint, this maybe null - * @param type Waypoint.Type value for waypoint * @param attributeMap A Map of attributes for the given artifact * * @throws GeoLocationDataException Exception will be thrown if artifact did * not have a valid longitude and latitude. */ - protected ArtifactWaypoint(BlackboardArtifact artifact, String label, Long timestamp, Double latitude, Double longitude, Double altitude, AbstractFile image, Map attributeMap) throws GeoLocationDataException { + ArtifactWaypoint(BlackboardArtifact artifact, String label, Long timestamp, Double latitude, Double longitude, Double altitude, AbstractFile image, Map attributeMap) throws GeoLocationDataException { if (longitude == null || latitude == null) { throw new GeoLocationDataException("Invalid waypoint, null value passed for longitude or latitude"); } @@ -85,7 +86,7 @@ class ArtifactWaypoint implements Waypoint { this.latitude = latitude; this.altitude = altitude; - immutablePropertiesList = Collections.unmodifiableList(GeolocationUtils.createGeolocationProperties(attributeMap)); + immutablePropertiesList = Collections.unmodifiableList(Waypoint.createGeolocationProperties(attributeMap)); } /** @@ -167,4 +168,30 @@ class ArtifactWaypoint implements Waypoint { return ""; } + + /** + * Gets the list of attributes from the artifact and puts them into a map + * with the ATRIBUTE_TYPE as the key. + * + * @param artifact BlackboardArtifact current artifact + * + * @return A Map of BlackboardAttributes for the given artifact with + * ATTRIBUTE_TYPE as the key. + * + * @throws GeoLocationDataException + */ + static Map getAttributesFromArtifactAsMap(BlackboardArtifact artifact) throws GeoLocationDataException { + Map attributeMap = new HashMap<>(); + try { + List attributeList = artifact.getAttributes(); + for (BlackboardAttribute attribute : attributeList) { + BlackboardAttribute.ATTRIBUTE_TYPE type = BlackboardAttribute.ATTRIBUTE_TYPE.fromID(attribute.getAttributeType().getTypeID()); + attributeMap.put(type, attribute); + } + } catch (TskCoreException ex) { + throw new GeoLocationDataException("Unable to get attributes from artifact", ex); + } + + return attributeMap; + } } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/EXIFWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/EXIFWaypoint.java index 399ae17ea1..927b91b1d4 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/EXIFWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/EXIFWaypoint.java @@ -36,8 +36,8 @@ final class EXIFWaypoint extends ArtifactWaypoint { * * @throws GeoLocationDataException */ - protected EXIFWaypoint(BlackboardArtifact artifact) throws GeoLocationDataException { - this(artifact, ArtifactUtils.getAttributesFromArtifactAsMap(artifact), getImageFromArtifact(artifact)); + EXIFWaypoint(BlackboardArtifact artifact) throws GeoLocationDataException { + this(artifact, getAttributesFromArtifactAsMap(artifact), getImageFromArtifact(artifact)); } /** @@ -73,11 +73,10 @@ final class EXIFWaypoint extends ArtifactWaypoint { AbstractFile abstractFile = null; BlackboardArtifact.ARTIFACT_TYPE artifactType = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID()); if (artifactType == BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF) { - try { abstractFile = artifact.getSleuthkitCase().getAbstractFileById(artifact.getObjectID()); } catch (TskCoreException ex) { - throw new GeoLocationDataException(String.format("Unable to getAbstractFileByID for artifact: %d", artifact.getArtifactID(), artifact.getArtifactID()), ex); + throw new GeoLocationDataException(String.format("Unable to getAbstractFileByID for artifactID: %d", artifact.getArtifactID()), ex); } } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtils.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtils.java deleted file mode 100755 index 644c65dacc..0000000000 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/GeolocationUtils.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * 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.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import org.sleuthkit.datamodel.BlackboardAttribute; - -/** - * GeolocationUtilis class for common shared between Routes and Waypoints. - * - */ -final class GeolocationUtils { - - /** - * This is a list of attributes that are already being handled by the - * waypoint classes and will have get functions. - */ - private static final BlackboardAttribute.ATTRIBUTE_TYPE[] ALREADY_HANDLED_ATTRIBUTES = { - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME, - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE, - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE, - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE, - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED, - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START, - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START, - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END, - BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END,}; - - /** - * This is a Utility class that should not be constructed. - */ - private GeolocationUtils() { - - } - - /** - * Get a list of Waypoint.Property objects for the given artifact. This list - * will not include attributes that the Waypoint interfact has get functions - * for. - * - * @param artifact Blackboard artifact to get attributes\properties from - * - * @return A List of Waypoint.Property objects - * - * @throws GeoLocationDataException - */ - static List createGeolocationProperties(Map attributeMap) throws GeoLocationDataException { - List list = new ArrayList<>(); - - Set keys = attributeMap.keySet(); - - for (BlackboardAttribute.ATTRIBUTE_TYPE type : ALREADY_HANDLED_ATTRIBUTES) { - keys.remove(type); - } - - for (BlackboardAttribute.ATTRIBUTE_TYPE type : keys) { - String key = type.getDisplayName(); - String value = attributeMap.get(type).getDisplayString(); - - list.add(new Waypoint.Property(key, value)); - } - return list; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/LastKnownWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/LastKnownWaypoint.java index 4f44cb956d..3c27e81f5d 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/LastKnownWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/LastKnownWaypoint.java @@ -24,10 +24,8 @@ import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; /** - * A Last Know Location Waypoint object. + * A Last Known Location Waypoint object. */ -@Messages({ - "LastKnownWaypoint_Label=Last Known Location",}) final class LastKnownWaypoint extends ArtifactWaypoint { /** @@ -37,8 +35,8 @@ final class LastKnownWaypoint extends ArtifactWaypoint { * * @throws GeoLocationDataException */ - protected LastKnownWaypoint(BlackboardArtifact artifact) throws GeoLocationDataException { - this(artifact, ArtifactUtils.getAttributesFromArtifactAsMap(artifact)); + LastKnownWaypoint(BlackboardArtifact artifact) throws GeoLocationDataException { + this(artifact, getAttributesFromArtifactAsMap(artifact)); } /** @@ -69,6 +67,8 @@ final class LastKnownWaypoint extends ArtifactWaypoint { * * @throws GeoLocationDataException */ + @Messages({ + "LastKnownWaypoint_Label=Last Known Location",}) private static String getLabelFromArtifact(Map attributeMap) throws GeoLocationDataException { BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); String label = attribute.getDisplayString(); diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java index a7d9c651cf..8b6d3a968b 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java @@ -35,13 +35,7 @@ import org.sleuthkit.datamodel.TskCoreException; * more that two points. * */ -@Messages({ - "Route_Label=As-the-crow-flies Route", - "Route_Start_Label=Start", - "Route_End_Label=End" -}) public final class Route { - private final List points; private final Long timestamp; private final Double altitude; @@ -56,7 +50,7 @@ public final class Route { * @param skCase Currently open SleuthkitCase * * @return List of Route objects, empty list will be returned if no Routes - * where found + * were found * * @throws GeoLocationDataException */ @@ -84,19 +78,9 @@ public final class Route { Route(BlackboardArtifact artifact) throws GeoLocationDataException { points = new ArrayList<>(); - Map attributeMap = ArtifactUtils.getAttributesFromArtifactAsMap(artifact); - - Waypoint point = getRouteStartPoint(attributeMap); - - if (point != null) { - points.add(point); - } - - point = getRouteEndPoint(attributeMap); - - if (point != null) { - points.add(point); - } + Map attributeMap = ArtifactWaypoint.getAttributesFromArtifactAsMap(artifact); + points.add(getRouteStartPoint(attributeMap)); + points.add(getRouteEndPoint(attributeMap)); BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE); altitude = attribute != null ? attribute.getValueDouble() : null; @@ -104,7 +88,7 @@ public final class Route { attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME); timestamp = attribute != null ? attribute.getValueLong() : null; - immutablePropertiesList = Collections.unmodifiableList(GeolocationUtils.createGeolocationProperties(attributeMap)); + immutablePropertiesList = Collections.unmodifiableList(Waypoint.createGeolocationProperties(attributeMap)); } /** @@ -119,7 +103,7 @@ public final class Route { /** * Get the timestamp for this Route * - * @return The timestamp (epoch seconds) or null if none was set. + * @return The timestamp (java/unix epoch seconds) or null if none was set. */ public Long getTimestamp() { return timestamp; @@ -146,10 +130,12 @@ public final class Route { /** * Get the route label. - * - * This will return the original hard coded label from the KML report: - * As-the-crow-flies Route */ + @Messages({ + // This is the original static hardcoded label from the + // original kml-report code + "Route_Label=As-the-crow-flies Route" + }) public String getLabel() { return Bundle.Route_Label(); } @@ -163,6 +149,9 @@ public final class Route { * * @throws GeoLocationDataException when longitude or latitude is null */ + @Messages({ + "Route_Start_Label=Start" + }) private Waypoint getRouteStartPoint(Map attributeMap) throws GeoLocationDataException { BlackboardAttribute latitude = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START); BlackboardAttribute longitude = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START); @@ -184,6 +173,9 @@ public final class Route { * * @throws GeoLocationDataException when longitude or latitude is null */ + @Messages({ + "Route_End_Label=End" + }) private Waypoint getRouteEndPoint(Map attributeMap) throws GeoLocationDataException { BlackboardAttribute latitude = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END); BlackboardAttribute longitude = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END); diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/RoutePoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/RoutePoint.java index f3d8f5afcc..50087de873 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/RoutePoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/RoutePoint.java @@ -41,7 +41,7 @@ final class RoutePoint implements Waypoint { * @param longitude Longitude for point * @param label Way point label. */ - protected RoutePoint(Route parent, double latitude, double longitude, String label) { + RoutePoint(Route parent, double latitude, double longitude, String label) { this.longitude = longitude; this.latitude = latitude; this.label = label; diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/SearchWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/SearchWaypoint.java index 0041c05870..f4a27ef34e 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/SearchWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/SearchWaypoint.java @@ -27,18 +27,13 @@ import org.sleuthkit.datamodel.BlackboardAttribute; * A SearchWaypoint is a subclass of ArtifactWaypoint. */ final class SearchWaypoint extends ArtifactWaypoint { - - @Messages({ - "SearchWaypoint_DisplayLabel=GPS Search" - }) - /** * Construct a waypoint for TSK_GPS_SEARCH artifact. * * @throws GeoLocationDataException */ SearchWaypoint(BlackboardArtifact artifact) throws GeoLocationDataException { - this(artifact, ArtifactUtils.getAttributesFromArtifactAsMap(artifact)); + this(artifact, getAttributesFromArtifactAsMap(artifact)); } private SearchWaypoint(BlackboardArtifact artifact, Map attributeMap) throws GeoLocationDataException { @@ -60,6 +55,10 @@ final class SearchWaypoint extends ArtifactWaypoint { * * @throws GeoLocationDataException */ + @Messages({ + "SearchWaypoint_DisplayLabel=GPS Search" + }) + private static String getLabelFromArtifact(Map attributeMap) throws GeoLocationDataException { BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); if (attribute != null) { diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/TrackpointWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/TrackpointWaypoint.java index 01b2f6557e..ea724f2821 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/TrackpointWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/TrackpointWaypoint.java @@ -27,18 +27,13 @@ import org.sleuthkit.datamodel.BlackboardAttribute; * A wrapper class for TSK_GPS_TRACKPOINT artifacts. */ final class TrackpointWaypoint extends ArtifactWaypoint { - - @Messages({ - "TrackpointWaypoint_DisplayLabel=GPS Trackpoint" - }) - /** * Construct a waypoint for trackpoints. * * @throws GeoLocationDataException */ TrackpointWaypoint(BlackboardArtifact artifact) throws GeoLocationDataException { - this(artifact, ArtifactUtils.getAttributesFromArtifactAsMap(artifact)); + this(artifact, getAttributesFromArtifactAsMap(artifact)); } private TrackpointWaypoint(BlackboardArtifact artifact, Map attributeMap) throws GeoLocationDataException { @@ -61,6 +56,9 @@ final class TrackpointWaypoint extends ArtifactWaypoint { * * @throws GeoLocationDataException */ + @Messages({ + "TrackpointWaypoint_DisplayLabel=GPS Trackpoint" + }) private static String getLabelFromArtifact(Map attributeMap) throws GeoLocationDataException { BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java index d9affaebe6..45c8816574 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java @@ -20,10 +20,13 @@ package org.sleuthkit.autopsy.geolocation.datamodel; import java.util.ArrayList; 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; @@ -33,6 +36,23 @@ import org.sleuthkit.datamodel.TskCoreException; */ public interface Waypoint { static final Logger logger = Logger.getLogger(Waypoint.class.getName()); + + /** + * This is a list of attributes that are already being handled by the + * waypoint classes and will have get functions. + */ + BlackboardAttribute.ATTRIBUTE_TYPE[] ALREADY_HANDLED_ATTRIBUTES = { + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END, + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END,}; + /** * Interface to describe a waypoint. A waypoint is made up of * a longitude, latitude, label, timestamp, type, image and altitude. @@ -266,6 +286,36 @@ public interface Waypoint { } return points; } + + + /** + * Get a list of Waypoint.Property objects for the given artifact. This list + * will not include attributes that the Waypoint interfact has get functions + * for. + * + * @param artifact Blackboard artifact to get attributes\properties from + * + * @return A List of Waypoint.Property objects + * + * @throws GeoLocationDataException + */ + static List createGeolocationProperties(Map attributeMap) throws GeoLocationDataException { + List list = new ArrayList<>(); + + Set keys = attributeMap.keySet(); + + for (BlackboardAttribute.ATTRIBUTE_TYPE type : ALREADY_HANDLED_ATTRIBUTES) { + keys.remove(type); + } + + for (BlackboardAttribute.ATTRIBUTE_TYPE type : keys) { + String key = type.getDisplayName(); + String value = attributeMap.get(type).getDisplayString(); + + list.add(new Waypoint.Property(key, value)); + } + return list; + } /** * Simple property class for waypoint properties that a purely From 4bc0eba02e55b8d4afa796a51b3ee1eddc57ba79 Mon Sep 17 00:00:00 2001 From: Mark McKinnon Date: Tue, 29 Oct 2019 13:41:13 -0400 Subject: [PATCH 093/134] Update CaseMetadata.java Add UTF-8 Encoding so it writes to the aut file correctly for non-latin characters. --- Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java index 20dfabd93c..6f93b984a3 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java @@ -24,6 +24,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.StringWriter; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.nio.file.Paths; import java.text.DateFormat; @@ -427,9 +428,10 @@ public final class CaseMetadata { transformer.transform(source, streamResult); /* - * Write the DOM to the metadata file. + * Write the DOM to the metadata file. Add UTF-8 Characterset so it writes to the file + * correctly for non-latin charadters */ - try (BufferedWriter fileWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(metadataFilePath.toFile())))) { + try (BufferedWriter fileWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(metadataFilePath.toFile()), StandardCharsets.UTF_8))) { fileWriter.write(stringWriter.toString()); fileWriter.flush(); } From e984b4e363adeaf8f57c5fa933e0d3407bba08fb Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Tue, 29 Oct 2019 14:31:45 -0400 Subject: [PATCH 094/134] Added comments to MapWaypoint --- .../autopsy/geolocation/MapWaypoint.java | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java index b965074055..e93c871ef8 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java @@ -14,7 +14,8 @@ import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint; /** - * + * A Wrapper for the datamodel Waypoint class that implements the jxmapviewer + * Waypoint interfact for use in the map. * */ final class MapWaypoint implements org.jxmapviewer.viewer.Waypoint{ @@ -22,11 +23,25 @@ final class MapWaypoint implements org.jxmapviewer.viewer.Waypoint{ private final Waypoint dataModelWaypoint; private final GeoPosition position; + /** + * Private constructor for MapWaypoint + * + * @param dataModelWaypoint The datamodel waypoint to wrap + */ private MapWaypoint(Waypoint dataModelWaypoint) { this.dataModelWaypoint = dataModelWaypoint; position = new GeoPosition(dataModelWaypoint.getLatitude(), dataModelWaypoint.getLongitude()); } + /** + * Gets a list of jxmapviewer waypoints from the current case. + * + * @param skCase Current case + * + * @return List of jxmapviewer waypoints + * + * @throws GeoLocationDataException + */ static List getWaypoints(SleuthkitCase skCase) throws GeoLocationDataException{ List points = Waypoint.getAllWaypoints(skCase); @@ -44,13 +59,11 @@ final class MapWaypoint implements org.jxmapviewer.viewer.Waypoint{ return mapPoints; } + /** + * {@inheritDoc} + */ @Override public GeoPosition getPosition() { return position; } - - String getDisplayName() { - return dataModelWaypoint.getLabel(); - } - } From 88c10eeed87a704aad59f801cff8e160bdafb058 Mon Sep 17 00:00:00 2001 From: Mark McKinnon Date: Tue, 29 Oct 2019 16:29:53 -0400 Subject: [PATCH 095/134] Update CaseMetadata.java Fix typo --- Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java index 6f93b984a3..5f7ffe9ea7 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java @@ -429,7 +429,7 @@ public final class CaseMetadata { /* * Write the DOM to the metadata file. Add UTF-8 Characterset so it writes to the file - * correctly for non-latin charadters + * correctly for non-latin characters */ try (BufferedWriter fileWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(metadataFilePath.toFile()), StandardCharsets.UTF_8))) { fileWriter.write(stringWriter.toString()); From fcd4dace0ae89a39f96fa5b09a7b51c1c0069d70 Mon Sep 17 00:00:00 2001 From: Mark McKinnon Date: Tue, 29 Oct 2019 22:13:23 -0400 Subject: [PATCH 096/134] 5078-HTML-viewer-not-correctly-interpreting-Unicode Determine encoding of html file and display html file using encoding. --- Core/ivy.xml | 1 + Core/nbproject/project.properties | 1 + Core/nbproject/project.xml | 12 +++++--- .../autopsy/contentviewers/HtmlViewer.java | 29 +++++++++++++++++-- 4 files changed, 37 insertions(+), 6 deletions(-) diff --git a/Core/ivy.xml b/Core/ivy.xml index 9ba4a3c371..8ff512127b 100644 --- a/Core/ivy.xml +++ b/Core/ivy.xml @@ -44,6 +44,7 @@ + diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties index 2a952926ba..a04f1516f5 100644 --- a/Core/nbproject/project.properties +++ b/Core/nbproject/project.properties @@ -35,6 +35,7 @@ file.reference.java-libpst-0.8.1.jar=release\\modules\\ext\\java-libpst-0.8.1.ja file.reference.javax.activation-1.2.0.jar=release\\modules\\ext\\javax.activation-1.2.0.jar file.reference.javax.annotation-api-1.3.2.jar=release\\modules\\ext\\javax.annotation-api-1.3.2.jar file.reference.jbig2-imageio-3.0.2.jar=release\\modules\\ext\\jbig2-imageio-3.0.2.jar +file.reference.jchardet-1.0.jar=release/modules/ext/jchardet-1.0.jar file.reference.jcl-over-slf4j-1.7.25.jar=release\\modules\\ext\\jcl-over-slf4j-1.7.25.jar file.reference.jdom-2.0.5-contrib.jar=release/modules/ext/jdom-2.0.5-contrib.jar file.reference.jdom-2.0.5.jar=release/modules/ext/jdom-2.0.5.jar diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index c33dabfbc7..9a473f40e7 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -517,6 +517,14 @@ ext/google-http-client-1.29.0.jar release/modules/ext/google-http-client-1.29.0.jar + + ext/sleuthkit-postgresql-4.7.0.jar + release/modules/ext/sleuthkit-postgresql-4.7.0.jar + + + ext/jchardet-1.0.jar + release/modules/ext/jchardet-1.0.jar + ext/bcpkix-jdk15on-1.60.jar release\modules\ext\bcpkix-jdk15on-1.60.jar @@ -605,10 +613,6 @@ ext/jbig2-imageio-3.0.2.jar release\modules\ext\jbig2-imageio-3.0.2.jar - - ext/sleuthkit-postgresql-4.7.0.jar - release/modules/ext/sleuthkit-postgresql-4.7.0.jar - ext/apache-mime4j-dom-0.8.2.jar release\modules\ext\apache-mime4j-dom-0.8.2.jar diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlViewer.java index fb88ed9312..c9e4411437 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlViewer.java @@ -20,9 +20,11 @@ package org.sleuthkit.autopsy.contentviewers; import java.awt.Component; import java.awt.Cursor; +import java.io.UnsupportedEncodingException; import java.util.Arrays; import java.util.List; import java.util.logging.Level; +import org.mozilla.universalchardet.UniversalDetector; import org.openide.util.NbBundle; import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.coreutils.Logger; @@ -65,14 +67,37 @@ final class HtmlViewer extends javax.swing.JPanel implements FileTypeViewer { int fileSize = (int) abstractFile.getSize(); byte[] buffer = new byte[fileSize]; abstractFile.read(buffer, 0, fileSize); - return new String(buffer); - } catch (TskCoreException ex) { + String encoding = determineEncoding(buffer); + if (encoding != null) { + return new String(buffer, encoding); + } else { + return new String(buffer); + } + } catch (TskCoreException | UnsupportedEncodingException ex) { logger.log(Level.SEVERE, String.format("Unable to read from file '%s' (id=%d).", abstractFile.getName(), abstractFile.getId()), ex); return String.format("

    %s

    ", Bundle.HtmlViewer_file_error()); } } + /** + * This method will try and determine the encoding of the html file based on its contents + * + * @param buffer byte array of the html file to check + * + * @return encoding type, null if encoding could not be determined + */ + private String determineEncoding(byte[] buffer) { + UniversalDetector detector = new UniversalDetector(null); + + detector.handleData(buffer, 0, buffer.length - 1); + detector.dataEnd(); + + String encoding = detector.getDetectedCharset(); + detector.reset(); + return encoding; + } + /** * 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 From 39696906dac985e3eb6bd991be6bf0954ae06116 Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Wed, 30 Oct 2019 12:07:21 -0400 Subject: [PATCH 097/134] Updated after review comments --- .../geolocation/GeolocationTopComponent.java | 6 +++--- .../autopsy/geolocation/MapPanel.java | 12 +++--------- .../autopsy/geolocation/MapWaypoint.java | 19 ++++++++++++++++--- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java index 328aa048b2..a33bc1d15e 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java @@ -80,7 +80,7 @@ public final class GeolocationTopComponent extends TopComponent { this.ingestListener = pce -> { String eventType = pce.getPropertyName(); if (eventType.equals(DATA_ADDED.toString())) { - // Indicate that a refresh may be needed, unless the data added is Keyword or Hashset hits + // Indicate that a refresh may be needed for GPS data. ModuleDataEvent eventData = (ModuleDataEvent) pce.getOldValue(); if (null != eventData && (eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT.getTypeID() @@ -182,10 +182,10 @@ public final class GeolocationTopComponent extends TopComponent { mapPanel.setCenterLocation(waypoints.get(0)); } catch (ExecutionException ex) { - logger.log(Level.WARNING, "An exception occured while initalizing waypoints for geolocation window.", ex); + logger.log(Level.WARNING, "An exception occured while initializing waypoints for geolocation window.", ex); MessageNotifyUtil.Message.error(Bundle.GLTopComponent_initilzation_error()); } catch (InterruptedException ex) { - logger.log(Level.WARNING, "The initilization thread for geolocation window was interrupted.", ex); + logger.log(Level.WARNING, "The initializing thread for geolocation window was interrupted.", ex); } } } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java index 9aace08818..fdc8b7c8f1 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java @@ -44,7 +44,6 @@ final class MapPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; private boolean zoomChanging; - private final boolean sliderReversed; // Using a DefaultListModel to store the way points because we get // a lot of functionality for free, like listeners. @@ -55,8 +54,7 @@ final class MapPanel extends javax.swing.JPanel { */ MapPanel() { waypointListModel = new DefaultListModel<>(); - sliderReversed = false; - zoomChanging = false; + zoomChanging = false; initComponents(); initMap(); @@ -91,7 +89,7 @@ final class MapPanel extends javax.swing.JPanel { zoomSlider.setMaximum(tileFactory.getInfo().getMaximumZoomLevel()); - mapViewer.setZoom(tileFactory.getInfo().getMaximumZoomLevel()- 1); + setZoom(tileFactory.getInfo().getMaximumZoomLevel()- 1); mapViewer.setAddressLocation(new GeoPosition(0, 0)); // Listener for new way points being added to the map. @@ -149,11 +147,7 @@ final class MapPanel extends javax.swing.JPanel { void setZoom(int zoom) { zoomChanging = true; mapViewer.setZoom(zoom); - if (sliderReversed) { - zoomSlider.setValue(zoomSlider.getMaximum() - zoom); - } else { - zoomSlider.setValue(zoom); - } + zoomSlider.setValue((zoomSlider.getMaximum() + zoomSlider.getMinimum())- zoom); zoomChanging = false; } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java index e93c871ef8..d58223d98f 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.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; From a650ac6b21cd84a5c8b8fa72974b0fe22c74c492 Mon Sep 17 00:00:00 2001 From: Mark McKinnon Date: Wed, 30 Oct 2019 12:37:53 -0400 Subject: [PATCH 098/134] Remove jchardet and add tiki text detection Remove jchardet text detection and add tiki text detection. --- Core/ivy.xml | 1 - Core/nbproject/project.properties | 1 - Core/nbproject/project.xml | 4 ---- .../autopsy/contentviewers/HtmlViewer.java | 23 +++++++++---------- 4 files changed, 11 insertions(+), 18 deletions(-) diff --git a/Core/ivy.xml b/Core/ivy.xml index 8ff512127b..9ba4a3c371 100644 --- a/Core/ivy.xml +++ b/Core/ivy.xml @@ -44,7 +44,6 @@ - diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties index a04f1516f5..2a952926ba 100644 --- a/Core/nbproject/project.properties +++ b/Core/nbproject/project.properties @@ -35,7 +35,6 @@ file.reference.java-libpst-0.8.1.jar=release\\modules\\ext\\java-libpst-0.8.1.ja file.reference.javax.activation-1.2.0.jar=release\\modules\\ext\\javax.activation-1.2.0.jar file.reference.javax.annotation-api-1.3.2.jar=release\\modules\\ext\\javax.annotation-api-1.3.2.jar file.reference.jbig2-imageio-3.0.2.jar=release\\modules\\ext\\jbig2-imageio-3.0.2.jar -file.reference.jchardet-1.0.jar=release/modules/ext/jchardet-1.0.jar file.reference.jcl-over-slf4j-1.7.25.jar=release\\modules\\ext\\jcl-over-slf4j-1.7.25.jar file.reference.jdom-2.0.5-contrib.jar=release/modules/ext/jdom-2.0.5-contrib.jar file.reference.jdom-2.0.5.jar=release/modules/ext/jdom-2.0.5.jar diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index 9a473f40e7..50254259f0 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -521,10 +521,6 @@ ext/sleuthkit-postgresql-4.7.0.jar release/modules/ext/sleuthkit-postgresql-4.7.0.jar
    - - ext/jchardet-1.0.jar - release/modules/ext/jchardet-1.0.jar - ext/bcpkix-jdk15on-1.60.jar release\modules\ext\bcpkix-jdk15on-1.60.jar diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlViewer.java index c9e4411437..23fe65dbbf 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlViewer.java @@ -20,11 +20,12 @@ package org.sleuthkit.autopsy.contentviewers; import java.awt.Component; import java.awt.Cursor; -import java.io.UnsupportedEncodingException; +import java.io.IOException; import java.util.Arrays; import java.util.List; import java.util.logging.Level; -import org.mozilla.universalchardet.UniversalDetector; +import org.apache.tika.parser.txt.CharsetDetector; +import org.apache.tika.parser.txt.CharsetMatch; import org.openide.util.NbBundle; import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.coreutils.Logger; @@ -73,7 +74,7 @@ final class HtmlViewer extends javax.swing.JPanel implements FileTypeViewer { } else { return new String(buffer); } - } catch (TskCoreException | UnsupportedEncodingException ex) { + } catch (TskCoreException | IOException ex) { logger.log(Level.SEVERE, String.format("Unable to read from file '%s' (id=%d).", abstractFile.getName(), abstractFile.getId()), ex); return String.format("

    %s

    ", Bundle.HtmlViewer_file_error()); @@ -87,15 +88,13 @@ final class HtmlViewer extends javax.swing.JPanel implements FileTypeViewer { * * @return encoding type, null if encoding could not be determined */ - private String determineEncoding(byte[] buffer) { - UniversalDetector detector = new UniversalDetector(null); - - detector.handleData(buffer, 0, buffer.length - 1); - detector.dataEnd(); - - String encoding = detector.getDetectedCharset(); - detector.reset(); - return encoding; + private String determineEncoding(byte[] buffer) throws IOException { + + CharsetDetector detector = new CharsetDetector(); + detector.setText(buffer); + CharsetMatch match = detector.detect(); + return match.getName(); + } /** From af56179eba6e2e8ab4f960cd47ee3c9ba3b73195 Mon Sep 17 00:00:00 2001 From: esaunders Date: Wed, 30 Oct 2019 13:23:05 -0400 Subject: [PATCH 099/134] Added support for Mac OS and addressed issue with Travis Mac OS builds failing silently when 'set -e' command is used in the .travis.yml file by moving the script into travis_build.sh. --- .travis.yml | 27 +++++++++++++++++++++------ travis_build.sh | 12 ++++++++++++ 2 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 travis_build.sh diff --git a/.travis.yml b/.travis.yml index 56c8b7bbbd..5e7c79a9c0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,11 @@ language: java sudo: required -dist: bionic -os: - - linux + +jobs: + include: + - os: linux + dist bionic + - os: osx env: global: @@ -12,6 +15,7 @@ addons: apt: update: true packages: + - testdisk - libafflib-dev - libewf-dev - libpq-dev @@ -29,11 +33,13 @@ addons: update: true packages: - ant - - ant-optional + - wget + - libpq - libewf - gettext - cppunit - afflib + - testdisk python: - "2.7" @@ -43,7 +49,6 @@ before_install: - python setupSleuthkitBranch.py install: - - sudo apt-get install testdisk - cd sleuthkit/sleuthkit - ./travis_install_libs.sh @@ -54,12 +59,22 @@ before_script: export PATH=/usr/bin:$PATH; unset JAVA_HOME; fi + - if [ $TRAVIS_OS_NAME = osx ]; then + brew uninstall java --force; + brew cask uninstall java --force; + brew tap adoptopenjdk/openjdk; + brew cask install adoptopenjdk8; + wget https://cdn.azul.com/zulu/bin/zulu8.40.0.25-ca-fx-jdk8.0.222-macosx_x64.tar.gz; + sudo tar xf zulu8.40.0.25-ca-fx-jdk8.0.222-macosx_x64.tar.gz --strip-components=1 -C /Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home; + export JAVA_HOME=/Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home; + fi + - java -version script: - set -e - echo "Building TSK..." - ./bootstrap && ./configure --prefix=/usr && make - - pushd bindings/java/ && ant -q dist-PostgreSQL && popd + - pushd bindings/java && ant dist-PostgreSQL && popd - echo "Building Autopsy..." && echo -en 'travis_fold:start:script.build\\r' - cd $TRAVIS_BUILD_DIR/ - ant build diff --git a/travis_build.sh b/travis_build.sh new file mode 100644 index 0000000000..d25d52d6ae --- /dev/null +++ b/travis_build.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -e +echo "Building TSK..." +cd sleuthkit/sleuthkit +./bootstrap && configure --prefix=/usr && make +pushd bindings/java && ant -q dist-PostgreSQL && popd + +echo "Building Autopsy..." && echo -en 'travis_fold:start:script.build\\r' +cd $TRAVIS_BUILD_DIR/ +ant build +echo -en 'travis_fold:end:script.build\\r' From af3f20db9504b6d6ef5867e8925e874016fc62c7 Mon Sep 17 00:00:00 2001 From: esaunders Date: Wed, 30 Oct 2019 13:25:21 -0400 Subject: [PATCH 100/134] Forgot to add updated .travis.yml file on previous commit. --- .travis.yml | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5e7c79a9c0..be8df28a5d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -49,8 +49,7 @@ before_install: - python setupSleuthkitBranch.py install: - - cd sleuthkit/sleuthkit - - ./travis_install_libs.sh + - pushd sleuthkit/sleuthkit && ./travis_install_libs.sh && popd before_script: - if [ $TRAVIS_OS_NAME = linux ]; then @@ -70,12 +69,4 @@ before_script: fi - java -version -script: - - set -e - - echo "Building TSK..." - - ./bootstrap && ./configure --prefix=/usr && make - - pushd bindings/java && ant dist-PostgreSQL && popd - - echo "Building Autopsy..." && echo -en 'travis_fold:start:script.build\\r' - - cd $TRAVIS_BUILD_DIR/ - - ant build - - echo -en 'travis_fold:end:script.build\\r' +script: ./travis_build.sh From 8ec2f2b28160148616ccd9b31e32c28e2b2c17cd Mon Sep 17 00:00:00 2001 From: esaunders Date: Wed, 30 Oct 2019 14:39:03 -0400 Subject: [PATCH 101/134] Missed a : --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index be8df28a5d..b907afbd81 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ sudo: required jobs: include: - os: linux - dist bionic + dist: bionic - os: osx env: From ac434d35239f46e86f9598d34006536a11938087 Mon Sep 17 00:00:00 2001 From: Mark McKinnon Date: Wed, 30 Oct 2019 15:34:25 -0400 Subject: [PATCH 102/134] Update code to suggestions Update code using suggestions --- .../contentviewers/Bundle.properties-MERGED | 1 + .../autopsy/contentviewers/HtmlViewer.java | 33 +++++++------------ 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED index ba39d1d419..2d9df0ae33 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED @@ -23,6 +23,7 @@ GstVideoPanel.noOpenCase.errMsg=No open case available. Html_text_display_error=The HTML text cannot be displayed, it may not be correctly formed HTML. HtmlPanel_showImagesToggleButton_hide=Hide Images HtmlPanel_showImagesToggleButton_show=Download Images +HtmlViewer_encoding_error=This file has unsupported encoding HtmlViewer_file_error=This file is missing or unreadable. MediaFileViewer.initGst.gstException.msg=Error initializing gstreamer for audio/video viewing and frame extraction capabilities. Video and audio viewing will be disabled. GstVideoPanel.setupVideo.infoLabel.text=Playback of deleted videos is not supported, use an external player. diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlViewer.java index 23fe65dbbf..138e70ae47 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlViewer.java @@ -20,7 +20,7 @@ package org.sleuthkit.autopsy.contentviewers; import java.awt.Component; import java.awt.Cursor; -import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.util.Arrays; import java.util.List; import java.util.logging.Level; @@ -62,41 +62,30 @@ final class HtmlViewer extends javax.swing.JPanel implements FileTypeViewer { * @return The text content of the file. */ @NbBundle.Messages({ - "HtmlViewer_file_error=This file is missing or unreadable.",}) + "HtmlViewer_file_error=This file is missing or unreadable.", + "HtmlViewer_encoding_error=This file has unsupported encoding"}) private String getHtmlText(AbstractFile abstractFile) { try { int fileSize = (int) abstractFile.getSize(); byte[] buffer = new byte[fileSize]; abstractFile.read(buffer, 0, fileSize); - String encoding = determineEncoding(buffer); - if (encoding != null) { - return new String(buffer, encoding); + CharsetMatch match = new CharsetDetector().setText(buffer).detect(); + if (match != null) { + return new String(buffer, match.getName()); } else { return new String(buffer); } - } catch (TskCoreException | IOException ex) { + } catch (TskCoreException ex) { logger.log(Level.SEVERE, String.format("Unable to read from file '%s' (id=%d).", abstractFile.getName(), abstractFile.getId()), ex); return String.format("

    %s

    ", Bundle.HtmlViewer_file_error()); + } catch (UnsupportedEncodingException ex) { + logger.log(Level.SEVERE, String.format("Unsupported encoding for file '%s' (id=%d).", + abstractFile.getName(), abstractFile.getId()), ex); + return String.format("

    %s

    ", Bundle.HtmlViewer_encoding_error()); } } - /** - * This method will try and determine the encoding of the html file based on its contents - * - * @param buffer byte array of the html file to check - * - * @return encoding type, null if encoding could not be determined - */ - private String determineEncoding(byte[] buffer) throws IOException { - - CharsetDetector detector = new CharsetDetector(); - detector.setText(buffer); - CharsetMatch match = detector.detect(); - return match.getName(); - - } - /** * 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 From ca5558993a74ea7e1f36a263fd4af8d999673c59 Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Thu, 31 Oct 2019 10:00:00 -0400 Subject: [PATCH 103/134] Added getLabel function --- Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java index d58223d98f..d765d17ff5 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java @@ -79,4 +79,8 @@ final class MapWaypoint implements org.jxmapviewer.viewer.Waypoint{ public GeoPosition getPosition() { return position; } + + String getLabel() { + return dataModelWaypoint.getLabel(); + } } From cccbde6452a041dc5b0bc669e88edfa42b259efc Mon Sep 17 00:00:00 2001 From: Raman Arora Date: Thu, 31 Oct 2019 13:50:16 -0400 Subject: [PATCH 104/134] 5706: Message attachments. --- .../DataContentViewerArtifact.java | 14 +++++++ .../datamodel/ArtifactStringContent.java | 1 + .../PortableCaseReportModule.java | 1 + .../autopsy/test/CustomArtifactType.java | 4 ++ .../autoingest/FileExporterSettingsPanel.java | 2 + InternalPythonModules/android/imo.py | 38 ++++++++++++++++--- 6 files changed, 55 insertions(+), 5 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java index 39d3203b13..8c8e722bce 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java @@ -56,6 +56,10 @@ import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskException; import org.netbeans.swing.etable.ETable; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; /** * Instances of this class display the BlackboardArtifacts associated with the @@ -552,6 +556,16 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat value = dateFormatter.format(new java.util.Date(epoch * 1000)); } break; + case JSON: + // @TODO: 5726 - return a multilevel bulleted list instead of prettyprint JSON + String jsonVal = attr.getValueString(); + + JsonParser parser = new JsonParser(); + JsonObject json = parser.parse(jsonVal).getAsJsonObject(); + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + + value = gson.toJson(json); + break; } /* * Attribute sources column. diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ArtifactStringContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/ArtifactStringContent.java index 774d1f2f9e..e2950e5b12 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ArtifactStringContent.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ArtifactStringContent.java @@ -122,6 +122,7 @@ public class ArtifactStringContent implements StringContent { case LONG: case DOUBLE: case BYTE: + case JSON: default: value = attr.getDisplayString(); break; diff --git a/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModule.java b/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModule.java index de3350ad88..38e53b5eb6 100644 --- a/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModule.java +++ b/Core/src/org/sleuthkit/autopsy/report/modules/portablecase/PortableCaseReportModule.java @@ -768,6 +768,7 @@ public class PortableCaseReportModule implements ReportModule { oldAttr.getValueLong())); break; case STRING: + case JSON: newAttrs.add(new BlackboardAttribute(newAttributeType, String.join(",", oldAttr.getSources()), oldAttr.getValueString())); break; diff --git a/Core/src/org/sleuthkit/autopsy/test/CustomArtifactType.java b/Core/src/org/sleuthkit/autopsy/test/CustomArtifactType.java index b27fa29dd6..49784b46a7 100644 --- a/Core/src/org/sleuthkit/autopsy/test/CustomArtifactType.java +++ b/Core/src/org/sleuthkit/autopsy/test/CustomArtifactType.java @@ -50,6 +50,8 @@ final class CustomArtifactType { private static final String BYTES_ATTR_DISPLAY_NAME = "Custom Bytes"; private static final String STRING_ATTR_TYPE_NAME = "CUSTOM_STRING_ATTRIBUTE"; private static final String STRING_ATTR_DISPLAY_NAME = "Custom String"; + private static final String JSON_ATTR_TYPE_NAME = "CUSTOM_JSON_ATTRIBUTE"; + private static final String JSON_ATTR_DISPLAY_NAME = "Custom Json"; private static BlackboardArtifact.Type artifactType; private static BlackboardAttribute.Type intAttrType; private static BlackboardAttribute.Type doubleAttrType; @@ -57,6 +59,7 @@ final class CustomArtifactType { private static BlackboardAttribute.Type dateTimeAttrType; private static BlackboardAttribute.Type bytesAttrType; private static BlackboardAttribute.Type stringAttrType; + private static BlackboardAttribute.Type jsonAttrType; /** * Adds the custom artifact type, with its associated custom attribute @@ -73,6 +76,7 @@ final class CustomArtifactType { dateTimeAttrType = blackboard.getOrAddAttributeType(DATETIME_ATTR_TYPE_NAME, BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME, DATETIME_ATTR_DISPLAY_NAME); bytesAttrType = blackboard.getOrAddAttributeType(BYTES_ATTR_TYPE_NAME, BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.BYTE, BYTES_ATTR_DISPLAY_NAME); stringAttrType = blackboard.getOrAddAttributeType(STRING_ATTR_TYPE_NAME, BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.STRING, STRING_ATTR_DISPLAY_NAME); + jsonAttrType = blackboard.getOrAddAttributeType(JSON_ATTR_TYPE_NAME, BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.JSON, JSON_ATTR_DISPLAY_NAME); } /** diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/FileExporterSettingsPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/FileExporterSettingsPanel.java index 517c1e46a6..001ad7271d 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/FileExporterSettingsPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/FileExporterSettingsPanel.java @@ -244,6 +244,8 @@ public final class FileExporterSettingsPanel extends JPanel { comboBoxValueType.addItem(BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.STRING.getLabel()); comboBoxValueType.addItem(BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME.getLabel()); comboBoxValueType.addItem(BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.BYTE.getLabel()); + comboBoxValueType.addItem(BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.JSON.getLabel()); + comboBoxValueType.addItem(UNSET); load(); diff --git a/InternalPythonModules/android/imo.py b/InternalPythonModules/android/imo.py index 25dd750c1e..5a1bd89778 100644 --- a/InternalPythonModules/android/imo.py +++ b/InternalPythonModules/android/imo.py @@ -43,10 +43,14 @@ from org.sleuthkit.datamodel import Content 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 FileAttachment +from org.sleuthkit.datamodel.blackboardutils import URLAttachment +from org.sleuthkit.datamodel.blackboardutils import MessageAttachments from org.sleuthkit.datamodel.blackboardutils import CommunicationArtifactsHelper from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import MessageReadStatus from org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper import CommunicationDirection +import json import traceback import general @@ -66,6 +70,8 @@ class IMOAnalyzer(general.AndroidComponentAnalyzer): -- A messages table which stores the message details --- sender/receiver buid, timestamp, message_type (1: incoming, 0: outgoing), message_read... --- 'imdata' column stores a json structure with all the message details, including attachments + ---- attachment file path may be specified in local_path or original_path. Original path, if available is a better candidate. + ---- For sent files, files seem to get uploaded to IMO Servers. There is no URL available in the imdata though. """ @@ -156,7 +162,7 @@ class IMOAnalyzer(general.AndroidComponentAnalyzer): msgReadStatus = MessageReadStatus.UNKNOWN timeStamp = messagesResultSet.getLong("timestamp") / 1000000000 - + msgBody = messagesResultSet.getString("last_message") messageArtifact = friendsDBHelper.addMessage( self._MESSAGE_TYPE, @@ -166,12 +172,34 @@ class IMOAnalyzer(general.AndroidComponentAnalyzer): timeStamp, msgReadStatus, "", # subject - messagesResultSet.getString("last_message"), + msgBody, "") # thread id - - # TBD: parse the imdata JSON structure to figure out if there is an attachment. - # If one exists, add the attachment as a derived file and a child of the message artifact. + + # Parse the imdata JSON structure to check if there is an attachment. + # If one exists, create an attachment and add to the message. + fileAttachments = ArrayList() + urlAttachments = ArrayList() + + imdataJsonStr = messagesResultSet.getString("imdata") + if imdataJsonStr is not None: + imdata_dict = json.loads(imdataJsonStr) + + # set to none if the key doesn't exist in the dict + attachmentOriginalPath = imdata_dict.get('original_path', None) + attachmentLocalPath = imdata_dict.get('local_path', None) + if attachmentOriginalPath: + attachmentPath = attachmentOriginalPath + else: + attachmentPath = attachmentLocalPath + + if attachmentPath: + # Create a file attachment with given path + fileAttachment = FileAttachment(current_case.getSleuthkitCase(), friendsDb.getDBFile().getDataSource(), attachmentPath) + fileAttachments.add(fileAttachment) + + msgAttachments = MessageAttachments(fileAttachments, []) + attachmentArtifact = friendsDBHelper.addAttachments(messageArtifact, msgAttachments) except SQLException as ex: self._logger.log(Level.WARNING, "Error processing query result for IMO friends", ex) From 1a07c9d3709a903844cca8ad4c43cdd106c93a8a Mon Sep 17 00:00:00 2001 From: Raman Arora Date: Thu, 31 Oct 2019 14:04:07 -0400 Subject: [PATCH 105/134] Addressed Codacy comment. --- Core/src/org/sleuthkit/autopsy/test/CustomArtifactType.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/src/org/sleuthkit/autopsy/test/CustomArtifactType.java b/Core/src/org/sleuthkit/autopsy/test/CustomArtifactType.java index 49784b46a7..e584c6bdfc 100644 --- a/Core/src/org/sleuthkit/autopsy/test/CustomArtifactType.java +++ b/Core/src/org/sleuthkit/autopsy/test/CustomArtifactType.java @@ -97,6 +97,7 @@ final class CustomArtifactType { attributes.add(new BlackboardAttribute(dateTimeAttrType, MODULE_NAME, 60L)); attributes.add(new BlackboardAttribute(bytesAttrType, MODULE_NAME, DatatypeConverter.parseHexBinary("ABCD"))); attributes.add(new BlackboardAttribute(stringAttrType, MODULE_NAME, "Zero")); + attributes.add(new BlackboardAttribute(jsonAttrType, MODULE_NAME, "{\"fruit\": \"Apple\",\"size\": \"Large\",\"color\": \"Red\"}")); artifact.addAttributes(attributes); /* From d80dafc478408674c9c6abc8b904fccdeb6cb3e3 Mon Sep 17 00:00:00 2001 From: Raman Arora Date: Thu, 31 Oct 2019 15:06:18 -0400 Subject: [PATCH 106/134] Don't add caller/sender to the callee/recipient list. --- InternalPythonModules/android/fbmessenger.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/InternalPythonModules/android/fbmessenger.py b/InternalPythonModules/android/fbmessenger.py index e3c27d1765..2347144f09 100644 --- a/InternalPythonModules/android/fbmessenger.py +++ b/InternalPythonModules/android/fbmessenger.py @@ -182,11 +182,13 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer): contactsDb.close() - ## Extracts recipeint id from 'user_key' column and adds recipient to given list. - def addRecipientToList(self, user_key, recipientList): + ## Extracts recipeint id from 'user_key' column and adds recipient to given list, + ## if the recipeint id is not the same as sender id + def addRecipientToList(self, user_key, senderId, recipientList): if user_key is not None: - recipientId = user_key.replace('FACEBOOK:', '') - recipientList.append(recipientId) + recipientId = user_key.replace('FACEBOOK:', '') + if recipientId != senderId: + recipientList.append(recipientId) ## Extracts sender id from the json in 'sender' column. @@ -268,7 +270,7 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer): direction = self.deduceDirectionFromSenderId(fromId) # Get recipient and add to list - self.addRecipientToList(messagesResultSet.getString("user_key"), + self.addRecipientToList(messagesResultSet.getString("user_key"), fromId, recipientIdsList) timeStamp = messagesResultSet.getLong("timestamp_ms") / 1000 @@ -285,7 +287,7 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer): threadId = messagesResultSet.getString("thread_key") else: # same msgId as last, just collect recipient from current row - self.addRecipientToList(messagesResultSet.getString("user_key"), + self.addRecipientToList(messagesResultSet.getString("user_key"), fromId, recipientIdsList) @@ -373,7 +375,7 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer): direction = self.deduceDirectionFromSenderId(callerId) # Get recipient and add to list - self.addRecipientToList(messagesResultSet.getString("user_key"), + self.addRecipientToList(messagesResultSet.getString("user_key"), callerId, calleeIdsList) # the timestamp from call ended msg is used as end timestamp @@ -391,7 +393,7 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer): startTimeStamp = endTimeStamp - duration else: # same msgId as last, just collect callee from current row - self.addRecipientToList(messagesResultSet.getString("user_key"), + self.addRecipientToList(messagesResultSet.getString("user_key"), callerId, calleeIdsList) # at the end of the loop, add last message From 81c5db99713ca84a210f13b573daaee8063e7a26 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Fri, 1 Nov 2019 17:07:10 -0400 Subject: [PATCH 107/134] Implemented and tested xry report reading --- .../autopsy/xryparser/XRYRecordParser.java | 34 +++ .../autopsy/xryparser/XRYReport.java | 131 ++++++++++ .../autopsy/xryparser/XRYReportExtractor.java | 105 ++++++++ .../xryparser/XRYReportExtractorTest.java | 228 ++++++++++++++++++ .../autopsy/xryparser/XRYReportTest.java | 112 +++++++++ 5 files changed, 610 insertions(+) create mode 100755 Core/src/org/sleuthkit/autopsy/xryparser/XRYRecordParser.java create mode 100755 Core/src/org/sleuthkit/autopsy/xryparser/XRYReport.java create mode 100755 Core/src/org/sleuthkit/autopsy/xryparser/XRYReportExtractor.java create mode 100755 Core/test/unit/src/org/sleuthkit/autopsy/xryparser/XRYReportExtractorTest.java create mode 100755 Core/test/unit/src/org/sleuthkit/autopsy/xryparser/XRYReportTest.java diff --git a/Core/src/org/sleuthkit/autopsy/xryparser/XRYRecordParser.java b/Core/src/org/sleuthkit/autopsy/xryparser/XRYRecordParser.java new file mode 100755 index 0000000000..da1d79ce66 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/xryparser/XRYRecordParser.java @@ -0,0 +1,34 @@ +/* + * 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.xryparser; + +import org.sleuthkit.datamodel.BlackboardArtifact; + +/** + * + */ +public interface XRYRecordParser { + + /** + * + * @param xryRecord + * @return + */ + public BlackboardArtifact makeArtifact(String xryRecord); +} diff --git a/Core/src/org/sleuthkit/autopsy/xryparser/XRYReport.java b/Core/src/org/sleuthkit/autopsy/xryparser/XRYReport.java new file mode 100755 index 0000000000..e38586e15a --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/xryparser/XRYReport.java @@ -0,0 +1,131 @@ +/* + * 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.xryparser; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; +import java.util.stream.Stream; + +/** + * + */ +public class XRYReport { + + //Number of lines that make up the header of the report file. + private final static int LINES_IN_HEADER = 5; + + //Header line number that corresponds to the report type. + private final static int LINE_WITH_REPORT_TYPE = 3; + + //Encoding of the XRY Report file. + private final static Charset REPORT_ENCODING = StandardCharsets.UTF_16LE; + + //Path to the physical report file + private final Path reportPath; + + //XRY Report type (Calls, Messages, etc) + private final String reportType; + + /** + * + * @param reportPath + * @throws IOException + */ + public XRYReport(Path reportPath) throws IOException { + this.reportPath = reportPath; + this.reportType = parseType(reportPath); + } + + /** + * + * @return + */ + public String getType() { + return reportType; + } + + /** + * + * @return + */ + public Path getPath() { + return reportPath; + } + + /** + * + * @return + */ + public Charset getEncoding() { + return REPORT_ENCODING; + } + + /** + * + * @return + */ + public int getTotalLinesInHeader() { + return LINES_IN_HEADER; + } + + /** + * + * @return + */ + public int getReportTypeLineNumber() { + return LINE_WITH_REPORT_TYPE; + } + + /** + * + * @param report + * @return + * @throws IOException + */ + private String parseType(Path report) throws IOException { + try { + BufferedReader reader = Files.newBufferedReader(report, this.getEncoding()); + + //Limit this stream to only the length of the header + //and skip to the line just before the type information. + Stream xryReportHeader = reader.lines() + .limit(this.getTotalLinesInHeader()) + .skip(this.getReportTypeLineNumber() - 1); + + Optional type = xryReportHeader.findFirst(); + if(!type.isPresent()) { + throw new IllegalArgumentException("Report did not have a type."); + } + + if(type.get().isEmpty()) { + throw new IllegalArgumentException("Report did not have a type."); + } + + return type.get(); + } catch (UncheckedIOException ex) { + throw ex.getCause(); + } + } +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/xryparser/XRYReportExtractor.java b/Core/src/org/sleuthkit/autopsy/xryparser/XRYReportExtractor.java new file mode 100755 index 0000000000..9dd8c90de4 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/xryparser/XRYReportExtractor.java @@ -0,0 +1,105 @@ +/* + * 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.xryparser; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; +import org.sleuthkit.datamodel.BlackboardArtifact; + +/** + * This class is responsible for extracting XRY records from a specified + * XRYReport. XRY records are the numbered, blank line separated 'groups' in an + * XRY report. + * + * Example: + * + * Calls # 1 + * Call Type: Missed + * Time: 1/2/2019 1:23:45 PM (Device) + * From + * Tel: 12345678 + * + */ +public final class XRYReportExtractor { + + private final XRYReport xryReport; + + /** + * Creates an XRYReportExtractor. + * + * @param report Report to be extracted. + */ + public XRYReportExtractor(XRYReport report) { + this.xryReport = report; + } + + /** + * + * @param parser + * @throws IOException + */ + public List extract(XRYRecordParser parser) throws IOException { + try { + BufferedReader reader = Files.newBufferedReader(xryReport.getPath(), xryReport.getEncoding()); + + //Get a stream of all lines in the file. Skip the first n header lines. + Stream xryReportStream = reader.lines().skip(xryReport.getTotalLinesInHeader()); + + StringBuilder xryRecord = new StringBuilder(); + List artifacts = new ArrayList<>(); + xryReportStream.forEach((line) -> { + if (this.isEndOfXRYRecord(line)) { + //Pass only non empty XRY records to the parser. + if (xryRecord.length() > 0) { + artifacts.add(parser.makeArtifact(xryRecord.toString())); + xryRecord.setLength(0); + } + } else { + xryRecord.append(line).append("\n"); + } + }); + + //The file may have ended without a blank line (which is used to delimit + //records). The last XRY record would not have been processed. + if (xryRecord.length() > 0) { + artifacts.add(parser.makeArtifact(xryRecord.toString())); + } + + return artifacts; + } catch (UncheckedIOException ex) { + throw ex.getCause(); + } + } + + /** + * Determines if the line encountered during file reading signifies the end + * of an XRYRecord. + * + * @param line + * @return + */ + private boolean isEndOfXRYRecord(String line) { + return line.isEmpty(); + } +} diff --git a/Core/test/unit/src/org/sleuthkit/autopsy/xryparser/XRYReportExtractorTest.java b/Core/test/unit/src/org/sleuthkit/autopsy/xryparser/XRYReportExtractorTest.java new file mode 100755 index 0000000000..19a4ee747a --- /dev/null +++ b/Core/test/unit/src/org/sleuthkit/autopsy/xryparser/XRYReportExtractorTest.java @@ -0,0 +1,228 @@ +/* + * 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.xryparser; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashSet; +import java.util.Set; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.sleuthkit.datamodel.BlackboardArtifact; +import static org.junit.Assert.*; + +/** + * + */ +public class XRYReportExtractorTest { + + private final Path reportDirectory = Paths.get("C:", "Users", "dsmyda", "Downloads", "2019-10-23-XRYSamples", "files"); + + public XRYReportExtractorTest() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + @Test + public void testCallLogsSample() throws IOException { + Path reportPath = reportDirectory.resolve("Calls.txt"); + XRYReportExtractor extractor = new XRYReportExtractor(new XRYReport(reportPath)); + Set expectation = new HashSet() { + { + add("Calls # 1\n" + + "Call Type: Missed\n" + + "Time: 1/2/2019 1:23:45 PM (Device)\n" + + "From\n" + + "Tel: 12345678\n"); + add("Calls # 2\n" + + "Call Type: Dialed\n" + + "Time: 1/2/2019 2:34:56 PM (Device)\n" + + "Duration: 00:00:05\n" + + "To\n" + + "Tel: 23456789\n"); + add("Calls # 3\n" + + "Call Type: Last Dialed\n" + + "Number: 1234\n" + + "Storage: SIM\n" + + "To\n"); + add("Calls # 4\n" + + "Call Type: Received\n" + + "Time: 1/2/2019 2:34:56 AM (Device)\n" + + "Duration: 00:00:20\n" + + "From\n" + + "Tel: 34567890\n"); + } + }; + MockXRYRecordParser mockParser = new MockXRYRecordParser(expectation); + extractor.extract(mockParser); + assertEquals(expectation.size(), mockParser.getCount()); + } + + @Test + public void testMessagesSample() throws IOException { + Path reportPath = reportDirectory.resolve("Messages-SMS.txt"); + XRYReportExtractor extractor = new XRYReportExtractor(new XRYReport(reportPath)); + Set expectation = new HashSet() { + { + add("Messages-SMS # 1\n" + + "Text: Hello, this is my message. \n" + + "It has multiple lines.\n" + + "Time: 1/23/2019 1:23:45 PM UTC (Network)\n" + + "Type: Deliver\n" + + "Reference Number: 22\n" + + "Segment Number: 1\n" + + "Segments: 1\n" + + "From\n" + + "Tel: 12345678\n"); + add("Messages-SMS # 2\n" + + "Text: Hello, this is another message. one line.\n" + + "Time: 1/2/2019 1:33:44 PM (Device)\n" + + "Type: Submit\n" + + "To\n" + + "Tel: 1234\n"); + add("Messages-SMS # 3\n" + + "Text: Text goes here\n" + + "Time: 1/3/2019 2:33:22 PM (Device)\n" + + "Type: Status Report\n" + + "Participant\n" + + "Tel: 12345\n"); + } + }; + MockXRYRecordParser mockParser = new MockXRYRecordParser(expectation); + extractor.extract(mockParser); + assertEquals(expectation.size(), mockParser.getCount()); + } + + @Test + public void testContactsSample() throws IOException { + Path reportPath = reportDirectory.resolve("Contacts-Contacts.txt"); + XRYReportExtractor extractor = new XRYReportExtractor(new XRYReport(reportPath)); + Set expectation = new HashSet() { + { + add("Contacts-Contacts # 1\n" + + "Name: Abc\n" + + "Tel: +123456\n" + + "Storage: Device\n"); + add("Contacts-Contacts # 2\n" + + "Name: Xyz\n" + + "Tel: +34567\n" + + "Storage: SIM\n"); + } + }; + MockXRYRecordParser mockParser = new MockXRYRecordParser(expectation); + extractor.extract(mockParser); + assertEquals(expectation.size(), mockParser.getCount()); + } + + @Test + public void testWebBookmarksSample() throws IOException { + Path reportPath = reportDirectory.resolve("Web-Bookmarks.txt"); + XRYReportExtractor extractor = new XRYReportExtractor(new XRYReport(reportPath)); + Set expectation = new HashSet() { + { + add("Web-Bookmarks # 1\n" + + "Web Address: http://www.google.com\n" + + "Domain: Google Search\n"); + } + }; + MockXRYRecordParser mockParser = new MockXRYRecordParser(expectation); + extractor.extract(mockParser); + assertEquals(expectation.size(), mockParser.getCount()); + } + + @Test + public void testDeviceSample() throws IOException { + Path reportPath = reportDirectory.resolve("Device-General Information.txt"); + XRYReportExtractor extractor = new XRYReportExtractor(new XRYReport(reportPath)); + Set expectation = new HashSet() { + { + add("Device-General Information # 1\n" + + "Data: c:\\Path To Something I forget what though\\Maybe the Storage folder\n"); + add("Device-General Information # 2\n" + + "Data: Nokia XYZ\n" + + "Attribute: Device Name\n"); + add("Device-General Information # 3\n" + + "Data: Phone\n" + + "Attribute: Device Family\n"); + add("Device-General Information # 4\n" + + "Data: XYZ\n" + + "Attribute: Device Type\n"); + add("Device-General Information # 5\n" + + "Data: 123456\n" + + "Attribute: Mobile Id (IMEI)\n"); + add("Device-General Information # 6\n" + + "Data: 12345\n" + + "Attribute: Security Code\n"); + add("Device-General Information # 7\n" + + "Data: SIM Card\n" + + "Attribute: Device Name\n"); + } + }; + MockXRYRecordParser mockParser = new MockXRYRecordParser(expectation); + extractor.extract(mockParser); + assertEquals(expectation.size(), mockParser.getCount()); + } + + /** + * Mock XRYRecordParser. Rather than creating BlackboardArtifacts, we are + * instead verifying that the XRY Records are being parsed out correctly. + */ + private class MockXRYRecordParser implements XRYRecordParser { + + private final Set allRecords; + private int recordCount; + + public MockXRYRecordParser(Set allRecords) { + this.allRecords = allRecords; + recordCount = 0; + } + + @Override + public BlackboardArtifact makeArtifact(String xryRecord) { + assertNotNull(allRecords); + assertNotNull(xryRecord); + recordCount++; + assertTrue("More records than expected: " + recordCount + ". Expected at most: "+allRecords.size(), recordCount <= allRecords.size()); + assertTrue("Did not find the following record: " + xryRecord, allRecords.contains(xryRecord)); + return null; + } + + public int getCount() { + return recordCount; + } + } +} \ No newline at end of file diff --git a/Core/test/unit/src/org/sleuthkit/autopsy/xryparser/XRYReportTest.java b/Core/test/unit/src/org/sleuthkit/autopsy/xryparser/XRYReportTest.java new file mode 100755 index 0000000000..85c791b7db --- /dev/null +++ b/Core/test/unit/src/org/sleuthkit/autopsy/xryparser/XRYReportTest.java @@ -0,0 +1,112 @@ +/* + * 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.xryparser; + +import java.util.ArrayList; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + */ +public class XRYReportTest { + + private final Path reportDirectory = Paths.get("C:", "Users", "dsmyda", "Downloads", "2019-10-23-XRYSamples", "files"); + + public XRYReportTest() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + @Test + public void testParseType() { + List reportTestFiles = new ArrayList() { + { + add(reportDirectory.resolve("Calls.txt")); + add(reportDirectory.resolve("Messages-SMS.txt")); + add(reportDirectory.resolve("Contacts-Contacts.txt")); + add(reportDirectory.resolve("Web-Bookmarks.txt")); + add(reportDirectory.resolve("Device-General Information.txt")); + } + }; + + List expectedTypes = new ArrayList() { + { + add("Calls"); + add("Messages/SMS"); + add("Contacts/Contacts"); + add("Web/Bookmarks"); + add("Device/General Information"); + } + }; + + List actualTypes = new ArrayList<>(); + + reportTestFiles.forEach((Path reportFile) -> { + try { + XRYReport reportObj = new MockXRYReport(reportFile); + actualTypes.add(reportObj.getType()); + } catch (IOException ex) { + fail(ex.getMessage()); + } + }); + + assertArrayEquals("Types did not match.", expectedTypes.toArray(), actualTypes.toArray()); + } + + /** + * Mock the valid XRY Report encoding to UTF-8 so that the test files can be + * run unmodified (they are currently UTF-8). + */ + private class MockXRYReport extends XRYReport { + + public MockXRYReport(Path reportPath) throws IOException { + super(reportPath); + } + + @Override + public Charset getEncoding() { + return StandardCharsets.UTF_8; + } + } +} From 7b0e72cd0d2c12118ee3feb6ad5b8434c3763807 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Mon, 4 Nov 2019 11:07:20 -0500 Subject: [PATCH 108/134] 5665 bring over ExtractAction changes from file-search branch --- .../autopsy/directorytree/Bundle.properties | 8 - .../directorytree/Bundle.properties-MERGED | 9 - .../autopsy/directorytree/ExtractAction.java | 327 +--------------- .../directorytree/ExtractUnallocAction.java | 2 +- .../actionhelpers/Bundle.properties | 8 + .../actionhelpers/Bundle.properties-MERGED | 9 + .../actionhelpers/ExtractActionHelper.java | 357 ++++++++++++++++++ 7 files changed, 382 insertions(+), 338 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/directorytree/actionhelpers/Bundle.properties create mode 100644 Core/src/org/sleuthkit/autopsy/directorytree/actionhelpers/Bundle.properties-MERGED create mode 100644 Core/src/org/sleuthkit/autopsy/directorytree/actionhelpers/ExtractActionHelper.java diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties index 4badcfe9d8..100b2fd67b 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties @@ -65,14 +65,6 @@ ExplorerNodeActionVisitor.volDetail.noVolMatchErr=Error: No Volume Matches. ExplorerNodeActionVisitor.imgDetail.noVolMatchesErr=Error: No Volume Matches. ExplorerNodeActionVisitor.exception.probGetParent.text=Problem getting parent from {0}: {1} ExtractAction.title.extractFiles.text=Extract File(s) -ExtractAction.extractFiles.cantCreateFolderErr.msg=Could not create selected folder. -ExtractAction.confDlg.destFileExist.msg=Destination file {0} already exists, overwrite? -ExtractAction.confDlg.destFileExist.title=File Exists -ExtractAction.msgDlg.cantOverwriteFile.msg=Could not overwrite existing file {0} -ExtractAction.notifyDlg.noFileToExtr.msg=No file(s) to extract. -ExtractAction.progress.extracting=Extracting -ExtractAction.progress.cancellingExtraction={0} (Cancelling...) -ExtractAction.done.notifyMsg.fileExtr.text=File(s) extracted. ExtractUnallocAction.notifyMsg.unallocAlreadyBeingExtr.msg=Unallocated Space is already being extracted on this Image. Please select a different Image. ExtractUnallocAction.msgDlg.folderDoesntExist.msg=Folder does not exist. Please choose a valid folder before continuing ExtractUnallocAction.dlgTitle.selectDirToSaveTo.msg=Select directory to save to diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED index f70a374bc5..63b416b176 100755 --- a/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/directorytree/Bundle.properties-MERGED @@ -25,7 +25,6 @@ ExternalViewerAction.actionPerformed.failure.support.message=This platform (oper ExternalViewerAction.actionPerformed.failure.title=Open File Failure {0} ExternalViewerAction.actionPerformed.urlFailure.title=Open URL Failure ExternalViewerShortcutAction.title.text=Open in External Viewer Ctrl+E -ExtractAction.noOpenCase.errMsg=No open case available. ExtractUnallocAction.imageError=Error extracting unallocated space from image ExtractUnallocAction.noFiles=No unallocated files found on volume ExtractUnallocAction.noOpenCase.errMsg=No open case available. @@ -107,14 +106,6 @@ ExplorerNodeActionVisitor.volDetail.noVolMatchErr=Error: No Volume Matches. ExplorerNodeActionVisitor.imgDetail.noVolMatchesErr=Error: No Volume Matches. ExplorerNodeActionVisitor.exception.probGetParent.text=Problem getting parent from {0}: {1} ExtractAction.title.extractFiles.text=Extract File(s) -ExtractAction.extractFiles.cantCreateFolderErr.msg=Could not create selected folder. -ExtractAction.confDlg.destFileExist.msg=Destination file {0} already exists, overwrite? -ExtractAction.confDlg.destFileExist.title=File Exists -ExtractAction.msgDlg.cantOverwriteFile.msg=Could not overwrite existing file {0} -ExtractAction.notifyDlg.noFileToExtr.msg=No file(s) to extract. -ExtractAction.progress.extracting=Extracting -ExtractAction.progress.cancellingExtraction={0} (Cancelling...) -ExtractAction.done.notifyMsg.fileExtr.text=File(s) extracted. ExtractUnallocAction.notifyMsg.unallocAlreadyBeingExtr.msg=Unallocated Space is already being extracted on this Image. Please select a different Image. ExtractUnallocAction.msgDlg.folderDoesntExist.msg=Folder does not exist. Please choose a valid folder before continuing ExtractUnallocAction.dlgTitle.selectDirToSaveTo.msg=Select directory to save to diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExtractAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractAction.java index 46c2161838..0efe3bdcfb 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExtractAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-2018 Basis Technology Corp. + * Copyright 2013-2019 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,32 +18,13 @@ */ package org.sleuthkit.autopsy.directorytree; -import java.awt.Component; +import org.sleuthkit.autopsy.directorytree.actionhelpers.ExtractActionHelper; import java.awt.event.ActionEvent; -import java.io.File; -import java.util.ArrayList; import java.util.Collection; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; -import java.util.concurrent.ExecutionException; -import java.util.logging.Level; import javax.swing.AbstractAction; -import javax.swing.JFileChooser; -import javax.swing.JOptionPane; -import javax.swing.SwingWorker; -import org.netbeans.api.progress.ProgressHandle; -import org.openide.util.Cancellable; import org.openide.util.NbBundle; import org.openide.util.Utilities; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.coreutils.FileUtil; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import org.sleuthkit.autopsy.datamodel.ContentUtils; -import org.sleuthkit.autopsy.datamodel.ContentUtils.ExtractFscContentVisitor; +import org.openide.util.Lookup; import org.sleuthkit.datamodel.AbstractFile; /** @@ -51,10 +32,6 @@ import org.sleuthkit.datamodel.AbstractFile; */ public final class ExtractAction extends AbstractAction { - private Logger logger = Logger.getLogger(ExtractAction.class.getName()); - - private String userDefinedExportPath; - // This class is a singleton to support multi-selection of nodes, since // org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick up an Action if every // node in the array returns a reference to the same action object from Node.getActions(boolean). @@ -82,300 +59,10 @@ public final class ExtractAction extends AbstractAction { */ @Override public void actionPerformed(ActionEvent e) { - Collection selectedFiles = Utilities.actionsGlobalContext().lookupAll(AbstractFile.class); - if (selectedFiles.size() > 1) { - extractFiles(e, selectedFiles); - } else if (selectedFiles.size() == 1) { - AbstractFile source = selectedFiles.iterator().next(); - if (source.isDir()) { - extractFiles(e, selectedFiles); - } else { - extractFile(e, selectedFiles.iterator().next()); - } - } - } + Lookup lookup = Utilities.actionsGlobalContext(); + Collection selectedFiles =lookup.lookupAll(AbstractFile.class); + ExtractActionHelper extractor = new ExtractActionHelper(); + extractor.extract(e, selectedFiles); - /** - * Called when user has selected a single file to extract - * - * @param event - * @param selectedFile Selected file - */ - @NbBundle.Messages({"ExtractAction.noOpenCase.errMsg=No open case available."}) - private void extractFile(ActionEvent event, AbstractFile selectedFile) { - Case openCase; - try { - openCase = Case.getCurrentCaseThrows(); - } catch (NoCurrentCaseException ex) { - JOptionPane.showMessageDialog((Component) event.getSource(), Bundle.ExtractAction_noOpenCase_errMsg()); - logger.log(Level.INFO, "Exception while getting open case.", ex); //NON-NLS - return; - } - JFileChooser fileChooser = new JFileChooser(); - fileChooser.setCurrentDirectory(new File(getExportDirectory(openCase))); - // If there is an attribute name, change the ":". Otherwise the extracted file will be hidden - fileChooser.setSelectedFile(new File(FileUtil.escapeFileName(selectedFile.getName()))); - if (fileChooser.showSaveDialog((Component) event.getSource()) == JFileChooser.APPROVE_OPTION) { - updateExportDirectory(fileChooser.getSelectedFile().getParent(), openCase); - - ArrayList fileExtractionTasks = new ArrayList<>(); - fileExtractionTasks.add(new FileExtractionTask(selectedFile, fileChooser.getSelectedFile())); - runExtractionTasks(event, fileExtractionTasks); - } - } - - /** - * Called when a user has selected multiple files to extract - * - * @param event - * @param selectedFiles Selected files - */ - private void extractFiles(ActionEvent event, Collection selectedFiles) { - Case openCase; - try { - openCase = Case.getCurrentCaseThrows(); - } catch (NoCurrentCaseException ex) { - JOptionPane.showMessageDialog((Component) event.getSource(), Bundle.ExtractAction_noOpenCase_errMsg()); - logger.log(Level.INFO, "Exception while getting open case.", ex); //NON-NLS - return; - } - JFileChooser folderChooser = new JFileChooser(); - folderChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); - folderChooser.setCurrentDirectory(new File(getExportDirectory(openCase))); - if (folderChooser.showSaveDialog((Component) event.getSource()) == JFileChooser.APPROVE_OPTION) { - File destinationFolder = folderChooser.getSelectedFile(); - if (!destinationFolder.exists()) { - try { - destinationFolder.mkdirs(); - } catch (Exception ex) { - JOptionPane.showMessageDialog((Component) event.getSource(), NbBundle.getMessage(this.getClass(), - "ExtractAction.extractFiles.cantCreateFolderErr.msg")); - logger.log(Level.INFO, "Unable to create folder(s) for user " + destinationFolder.getAbsolutePath(), ex); //NON-NLS - return; - } - } - updateExportDirectory(destinationFolder.getPath(), openCase); - - /* - * get the unique set of files from the list. A user once reported - * extraction taking days because it was extracting the same PST - * file 20k times. They selected 20k email messages in the tree and - * chose to extract them. - */ - Set uniqueFiles = new HashSet<>(selectedFiles); - - // make a task for each file - ArrayList fileExtractionTasks = new ArrayList<>(); - for (AbstractFile source : uniqueFiles) { - // If there is an attribute name, change the ":". Otherwise the extracted file will be hidden - fileExtractionTasks.add(new FileExtractionTask(source, new File(destinationFolder, source.getId() + "-" + FileUtil.escapeFileName(source.getName())))); - } - runExtractionTasks(event, fileExtractionTasks); - } - } - - /** - * Get the export directory path. - * - * @param openCase The current case. - * - * @return The export directory path. - */ - private String getExportDirectory(Case openCase) { - String caseExportPath = openCase.getExportDirectory(); - - if (userDefinedExportPath == null) { - return caseExportPath; - } - - File file = new File(userDefinedExportPath); - if (file.exists() == false || file.isDirectory() == false) { - return caseExportPath; - } - - return userDefinedExportPath; - } - - /** - * Update the default export directory. If the directory path matches the - * case export directory, then the directory used will always match the - * export directory of any given case. Otherwise, the path last used will be - * saved. - * - * @param exportPath The export path. - * @param openCase The current case. - */ - private void updateExportDirectory(String exportPath, Case openCase) { - if (exportPath.equalsIgnoreCase(openCase.getExportDirectory())) { - userDefinedExportPath = null; - } else { - userDefinedExportPath = exportPath; - } - } - - /** - * Execute a series of file extraction tasks. - * - * @param event ActionEvent whose source will be used for - * centering popup dialogs. - * @param fileExtractionTasks List of file extraction tasks. - */ - private void runExtractionTasks(ActionEvent event, List fileExtractionTasks) { - - // verify all of the sources and destinations are OK - for (Iterator it = fileExtractionTasks.iterator(); it.hasNext();) { - FileExtractionTask task = it.next(); - - if (ContentUtils.isDotDirectory(task.source)) { - //JOptionPane.showMessageDialog((Component) e.getSource(), "Cannot extract virtual " + task.source.getName() + " directory.", "File is Virtual Directory", JOptionPane.WARNING_MESSAGE); - it.remove(); - continue; - } - - /* - * This code assumes that each destination is unique. We previously - * satisfied that by adding the unique ID. - */ - if (task.destination.exists()) { - if (JOptionPane.showConfirmDialog((Component) event.getSource(), - NbBundle.getMessage(this.getClass(), "ExtractAction.confDlg.destFileExist.msg", task.destination.getAbsolutePath()), - NbBundle.getMessage(this.getClass(), "ExtractAction.confDlg.destFileExist.title"), - JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) { - if (!FileUtil.deleteFileDir(task.destination)) { - JOptionPane.showMessageDialog((Component) event.getSource(), - NbBundle.getMessage(this.getClass(), "ExtractAction.msgDlg.cantOverwriteFile.msg", task.destination.getAbsolutePath())); - it.remove(); - } - } else { - it.remove(); - } - } - } - - // launch a thread to do the work - if (!fileExtractionTasks.isEmpty()) { - try { - FileExtracter extracter = new FileExtracter(fileExtractionTasks); - extracter.execute(); - } catch (Exception ex) { - logger.log(Level.WARNING, "Unable to start background file extraction thread", ex); //NON-NLS - } - } else { - MessageNotifyUtil.Message.info( - NbBundle.getMessage(this.getClass(), "ExtractAction.notifyDlg.noFileToExtr.msg")); - } - } - - /** - * Stores source and destination for file extraction. - */ - private class FileExtractionTask { - - AbstractFile source; - File destination; - - /** - * Create an instance of the FileExtractionTask. - * - * @param source The file to be extracted. - * @param destination The destination for the extraction. - */ - FileExtractionTask(AbstractFile source, File destination) { - this.source = source; - this.destination = destination; - } - } - - /** - * Thread that does the actual extraction work - */ - private class FileExtracter extends SwingWorker { - - private final Logger logger = Logger.getLogger(FileExtracter.class.getName()); - private ProgressHandle progress; - private final List extractionTasks; - - /** - * Create an instance of the FileExtracter. - * - * @param extractionTasks List of file extraction tasks. - */ - FileExtracter(List extractionTasks) { - this.extractionTasks = extractionTasks; - } - - @Override - protected Object doInBackground() throws Exception { - if (extractionTasks.isEmpty()) { - return null; - } - - // Setup progress bar. - final String displayName = NbBundle.getMessage(this.getClass(), "ExtractAction.progress.extracting"); - progress = ProgressHandle.createHandle(displayName, new Cancellable() { - @Override - public boolean cancel() { - if (progress != null) { - progress.setDisplayName( - NbBundle.getMessage(this.getClass(), "ExtractAction.progress.cancellingExtraction", displayName)); - } - return ExtractAction.FileExtracter.this.cancel(true); - } - }); - progress.start(); - progress.switchToIndeterminate(); - - /* - * @@@ Add back in -> Causes exceptions int workUnits = 0; for - * (FileExtractionTask task : extractionTasks) { workUnits += - * calculateProgressBarWorkUnits(task.source); } - * progress.switchToDeterminate(workUnits); - */ - // Do the extraction tasks. - for (FileExtractionTask task : this.extractionTasks) { - // @@@ Note, we are no longer passing in progress - ExtractFscContentVisitor.extract(task.source, task.destination, null, this); - } - - return null; - } - - @Override - protected void done() { - boolean msgDisplayed = false; - try { - super.get(); - } catch (InterruptedException | ExecutionException ex) { - logger.log(Level.SEVERE, "Fatal error during file extraction", ex); //NON-NLS - MessageNotifyUtil.Message.info( - NbBundle.getMessage(this.getClass(), "ExtractAction.done.notifyMsg.extractErr", ex.getMessage())); - msgDisplayed = true; - } finally { - progress.finish(); - if (!this.isCancelled() && !msgDisplayed) { - MessageNotifyUtil.Message.info( - NbBundle.getMessage(this.getClass(), "ExtractAction.done.notifyMsg.fileExtr.text")); - } - } - } - - /** - * Calculate the number of work units for the progress bar. - * - * @param file File whose children will be reviewed to get the number of - * work units. - * - * @return The number of work units. - */ - /* - * private int calculateProgressBarWorkUnits(AbstractFile file) { int - * workUnits = 0; if (file.isFile()) { workUnits += file.getSize(); } - * else { try { for (Content child : file.getChildren()) { if (child - * instanceof AbstractFile) { workUnits += - * calculateProgressBarWorkUnits((AbstractFile) child); } } } catch - * (TskCoreException ex) { logger.log(Level.SEVERE, "Could not get - * children of content", ex); //NON-NLS } } return workUnits; - } - */ } } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java index b2a3a93abf..58afb28fbe 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java @@ -138,7 +138,7 @@ final class ExtractUnallocAction extends AbstractAction { try { openCase = Case.getCurrentCaseThrows(); } catch (NoCurrentCaseException ex) { - MessageNotifyUtil.Message.info(Bundle.ExtractAction_noOpenCase_errMsg()); + MessageNotifyUtil.Message.info(Bundle.ExtractUnallocAction_noOpenCase_errMsg()); return; } List copyList = new ArrayList() { diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/actionhelpers/Bundle.properties b/Core/src/org/sleuthkit/autopsy/directorytree/actionhelpers/Bundle.properties new file mode 100644 index 0000000000..310cf0a475 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/directorytree/actionhelpers/Bundle.properties @@ -0,0 +1,8 @@ +ExtractActionHelper.extractFiles.cantCreateFolderErr.msg=Could not create selected folder. +ExtractActionHelper.confDlg.destFileExist.msg=Destination file {0} already exists, overwrite? +ExtractActionHelper.confDlg.destFileExist.title=File Exists +ExtractActionHelper.msgDlg.cantOverwriteFile.msg=Could not overwrite existing file {0} +ExtractActionHelper.notifyDlg.noFileToExtr.msg=No file(s) to extract. +ExtractActionHelper.progress.extracting=Extracting +ExtractActionHelper.progress.cancellingExtraction={0} (Cancelling...) +ExtractActionHelper.done.notifyMsg.fileExtr.text=File(s) extracted. diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/actionhelpers/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/directorytree/actionhelpers/Bundle.properties-MERGED new file mode 100644 index 0000000000..f86fa5612d --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/directorytree/actionhelpers/Bundle.properties-MERGED @@ -0,0 +1,9 @@ +ExtractActionHelper.extractFiles.cantCreateFolderErr.msg=Could not create selected folder. +ExtractActionHelper.confDlg.destFileExist.msg=Destination file {0} already exists, overwrite? +ExtractActionHelper.confDlg.destFileExist.title=File Exists +ExtractActionHelper.msgDlg.cantOverwriteFile.msg=Could not overwrite existing file {0} +ExtractActionHelper.noOpenCase.errMsg=No open case available. +ExtractActionHelper.notifyDlg.noFileToExtr.msg=No file(s) to extract. +ExtractActionHelper.progress.extracting=Extracting +ExtractActionHelper.progress.cancellingExtraction={0} (Cancelling...) +ExtractActionHelper.done.notifyMsg.fileExtr.text=File(s) extracted. diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/actionhelpers/ExtractActionHelper.java b/Core/src/org/sleuthkit/autopsy/directorytree/actionhelpers/ExtractActionHelper.java new file mode 100644 index 0000000000..fa10b046a4 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/directorytree/actionhelpers/ExtractActionHelper.java @@ -0,0 +1,357 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013-2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this content 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.directorytree.actionhelpers; + +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import javax.swing.JFileChooser; +import javax.swing.JOptionPane; +import javax.swing.SwingWorker; +import org.netbeans.api.progress.ProgressHandle; +import org.openide.util.Cancellable; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.FileUtil; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.autopsy.datamodel.ContentUtils; +import org.sleuthkit.datamodel.AbstractFile; + +/** + * Helper class for methods needed by actions which extract files. + */ +public class ExtractActionHelper { + + private final Logger logger = Logger.getLogger(ExtractActionHelper.class.getName()); + private String userDefinedExportPath; + + /** + * Extract the specified collection of files with an event specified for + * context. + * + * @param event The event that caused the extract method to be + * called. + * @param selectedFiles The files to be extracted from the current case. + */ + public void extract(ActionEvent event, Collection selectedFiles) { + if (selectedFiles.size() > 1) { + extractFiles(event, selectedFiles); + } else if (selectedFiles.size() == 1) { + AbstractFile source = selectedFiles.iterator().next(); + if (source.isDir()) { + extractFiles(event, selectedFiles); + } else { + extractFile(event, selectedFiles.iterator().next()); + } + } + } + + /** + * Called when user has selected a single file to extract + * + * @param event + * @param selectedFile Selected file + */ + @NbBundle.Messages({"ExtractActionHelper.noOpenCase.errMsg=No open case available."}) + private void extractFile(ActionEvent event, AbstractFile selectedFile) { + Case openCase; + try { + openCase = Case.getCurrentCaseThrows(); + } catch (NoCurrentCaseException ex) { + JOptionPane.showMessageDialog((Component) event.getSource(), Bundle.ExtractActionHelper_noOpenCase_errMsg()); + logger.log(Level.INFO, "Exception while getting open case.", ex); //NON-NLS + return; + } + JFileChooser fileChooser = new JFileChooser(); + fileChooser.setCurrentDirectory(new File(getExportDirectory(openCase))); + // If there is an attribute name, change the ":". Otherwise the extracted file will be hidden + fileChooser.setSelectedFile(new File(FileUtil.escapeFileName(selectedFile.getName()))); + if (fileChooser.showSaveDialog((Component) event.getSource()) == JFileChooser.APPROVE_OPTION) { + updateExportDirectory(fileChooser.getSelectedFile().getParent(), openCase); + + ArrayList fileExtractionTasks = new ArrayList<>(); + fileExtractionTasks.add(new FileExtractionTask(selectedFile, fileChooser.getSelectedFile())); + runExtractionTasks(event, fileExtractionTasks); + } + } + + /** + * Called when a user has selected multiple files to extract + * + * @param event + * @param selectedFiles Selected files + */ + private void extractFiles(ActionEvent event, Collection selectedFiles) { + Case openCase; + try { + openCase = Case.getCurrentCaseThrows(); + } catch (NoCurrentCaseException ex) { + JOptionPane.showMessageDialog((Component) event.getSource(), Bundle.ExtractActionHelper_noOpenCase_errMsg()); + logger.log(Level.INFO, "Exception while getting open case.", ex); //NON-NLS + return; + } + JFileChooser folderChooser = new JFileChooser(); + folderChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + folderChooser.setCurrentDirectory(new File(getExportDirectory(openCase))); + if (folderChooser.showSaveDialog((Component) event.getSource()) == JFileChooser.APPROVE_OPTION) { + File destinationFolder = folderChooser.getSelectedFile(); + if (!destinationFolder.exists()) { + try { + destinationFolder.mkdirs(); + } catch (Exception ex) { + JOptionPane.showMessageDialog((Component) event.getSource(), NbBundle.getMessage(this.getClass(), + "ExtractAction.extractFiles.cantCreateFolderErr.msg")); + logger.log(Level.INFO, "Unable to create folder(s) for user " + destinationFolder.getAbsolutePath(), ex); //NON-NLS + return; + } + } + updateExportDirectory(destinationFolder.getPath(), openCase); + + /* + * get the unique set of files from the list. A user once reported + * extraction taking days because it was extracting the same PST + * file 20k times. They selected 20k email messages in the tree and + * chose to extract them. + */ + Set uniqueFiles = new HashSet<>(selectedFiles); + + // make a task for each file + ArrayList fileExtractionTasks = new ArrayList<>(); + for (AbstractFile source : uniqueFiles) { + // If there is an attribute name, change the ":". Otherwise the extracted file will be hidden + fileExtractionTasks.add(new FileExtractionTask(source, new File(destinationFolder, source.getId() + "-" + FileUtil.escapeFileName(source.getName())))); + } + runExtractionTasks(event, fileExtractionTasks); + } + } + + /** + * Get the export directory path. + * + * @param openCase The current case. + * + * @return The export directory path. + */ + private String getExportDirectory(Case openCase) { + String caseExportPath = openCase.getExportDirectory(); + + if (userDefinedExportPath == null) { + return caseExportPath; + } + + File file = new File(userDefinedExportPath); + if (file.exists() == false || file.isDirectory() == false) { + return caseExportPath; + } + + return userDefinedExportPath; + } + + /** + * Update the default export directory. If the directory path matches the + * case export directory, then the directory used will always match the + * export directory of any given case. Otherwise, the path last used will be + * saved. + * + * @param exportPath The export path. + * @param openCase The current case. + */ + private void updateExportDirectory(String exportPath, Case openCase) { + if (exportPath.equalsIgnoreCase(openCase.getExportDirectory())) { + userDefinedExportPath = null; + } else { + userDefinedExportPath = exportPath; + } + } + + /** + * Execute a series of file extraction tasks. + * + * @param event ActionEvent whose source will be used for + * centering popup dialogs. + * @param fileExtractionTasks List of file extraction tasks. + */ + private void runExtractionTasks(ActionEvent event, List fileExtractionTasks) { + + // verify all of the sources and destinations are OK + for (Iterator it = fileExtractionTasks.iterator(); it.hasNext();) { + FileExtractionTask task = it.next(); + + if (ContentUtils.isDotDirectory(task.source)) { + //JOptionPane.showMessageDialog((Component) e.getSource(), "Cannot extract virtual " + task.source.getName() + " directory.", "File is Virtual Directory", JOptionPane.WARNING_MESSAGE); + it.remove(); + continue; + } + + /* + * This code assumes that each destination is unique. We previously + * satisfied that by adding the unique ID. + */ + if (task.destination.exists()) { + if (JOptionPane.showConfirmDialog((Component) event.getSource(), + NbBundle.getMessage(this.getClass(), "ExtractActionHelper.confDlg.destFileExist.msg", task.destination.getAbsolutePath()), + NbBundle.getMessage(this.getClass(), "ExtractActionHelper.confDlg.destFileExist.title"), + JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) { + if (!FileUtil.deleteFileDir(task.destination)) { + JOptionPane.showMessageDialog((Component) event.getSource(), + NbBundle.getMessage(this.getClass(), "ExtractActionHelper.msgDlg.cantOverwriteFile.msg", task.destination.getAbsolutePath())); + it.remove(); + } + } else { + it.remove(); + } + } + } + + // launch a thread to do the work + if (!fileExtractionTasks.isEmpty()) { + try { + FileExtracter extracter = new FileExtracter(fileExtractionTasks); + extracter.execute(); + } catch (Exception ex) { + logger.log(Level.WARNING, "Unable to start background file extraction thread", ex); //NON-NLS + } + } else { + MessageNotifyUtil.Message.info( + NbBundle.getMessage(this.getClass(), "ExtractActionHelper.notifyDlg.noFileToExtr.msg")); + } + } + + /** + * Stores source and destination for file extraction. + */ + private class FileExtractionTask { + + AbstractFile source; + File destination; + + /** + * Create an instance of the FileExtractionTask. + * + * @param source The file to be extracted. + * @param destination The destination for the extraction. + */ + FileExtractionTask(AbstractFile source, File destination) { + this.source = source; + this.destination = destination; + } + } + + /** + * Thread that does the actual extraction work + */ + private class FileExtracter extends SwingWorker { + + private final Logger logger = Logger.getLogger(FileExtracter.class.getName()); + private ProgressHandle progress; + private final List extractionTasks; + + /** + * Create an instance of the FileExtracter. + * + * @param extractionTasks List of file extraction tasks. + */ + FileExtracter(List extractionTasks) { + this.extractionTasks = extractionTasks; + } + + @Override + protected Object doInBackground() throws Exception { + if (extractionTasks.isEmpty()) { + return null; + } + + // Setup progress bar. + final String displayName = NbBundle.getMessage(this.getClass(), "ExtractActionHelper.progress.extracting"); + progress = ProgressHandle.createHandle(displayName, new Cancellable() { + @Override + public boolean cancel() { + if (progress != null) { + progress.setDisplayName( + NbBundle.getMessage(this.getClass(), "ExtractActionHelper.progress.cancellingExtraction", displayName)); + } + return ExtractActionHelper.FileExtracter.this.cancel(true); + } + }); + progress.start(); + progress.switchToIndeterminate(); + + /* + * @@@ Add back in -> Causes exceptions int workUnits = 0; for + * (FileExtractionTask task : extractionTasks) { workUnits += + * calculateProgressBarWorkUnits(task.source); } + * progress.switchToDeterminate(workUnits); + */ + // Do the extraction tasks. + for (FileExtractionTask task : this.extractionTasks) { + // @@@ Note, we are no longer passing in progress + ContentUtils.ExtractFscContentVisitor.extract(task.source, task.destination, null, this); + } + + return null; + } + + @Override + protected void done() { + boolean msgDisplayed = false; + try { + super.get(); + } catch (InterruptedException | ExecutionException ex) { + logger.log(Level.SEVERE, "Fatal error during file extraction", ex); //NON-NLS + MessageNotifyUtil.Message.info( + NbBundle.getMessage(this.getClass(), "ExtractActionHelper.done.notifyMsg.extractErr", ex.getMessage())); + msgDisplayed = true; + } finally { + progress.finish(); + if (!this.isCancelled() && !msgDisplayed) { + MessageNotifyUtil.Message.info( + NbBundle.getMessage(this.getClass(), "ExtractActionHelper.done.notifyMsg.fileExtr.text")); + } + } + } + + /** + * Calculate the number of work units for the progress bar. + * + * @param file File whose children will be reviewed to get the number of + * work units. + * + * @return The number of work units. + */ + /* + * private int calculateProgressBarWorkUnits(AbstractFile file) { int + * workUnits = 0; if (file.isFile()) { workUnits += file.getSize(); } + * else { try { for (Content child : file.getChildren()) { if (child + * instanceof AbstractFile) { workUnits += + * calculateProgressBarWorkUnits((AbstractFile) child); } } } catch + * (TskCoreException ex) { logger.log(Level.SEVERE, "Could not get + * children of content", ex); //NON-NLS } } return workUnits; } + */ + } +} From c007b27f602d05d86d03d897417c2a3c4c828d15 Mon Sep 17 00:00:00 2001 From: esaunders Date: Mon, 4 Nov 2019 15:03:24 -0500 Subject: [PATCH 109/134] Switch to Amazon Corretto JDK which bundles Java FX. --- .travis.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index b907afbd81..575fa3f9dc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -61,11 +61,9 @@ before_script: - if [ $TRAVIS_OS_NAME = osx ]; then brew uninstall java --force; brew cask uninstall java --force; - brew tap adoptopenjdk/openjdk; - brew cask install adoptopenjdk8; - wget https://cdn.azul.com/zulu/bin/zulu8.40.0.25-ca-fx-jdk8.0.222-macosx_x64.tar.gz; - sudo tar xf zulu8.40.0.25-ca-fx-jdk8.0.222-macosx_x64.tar.gz --strip-components=1 -C /Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home; - export JAVA_HOME=/Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home; + brew tap homebrew/cask-versions; + brew cask install corretto8; + export JAVA_HOME=/Library/Java/JavaVirtualMachines/amazon-corretto-8.jdk/Contents/Home; fi - java -version From 8c6cab1bce68a33fa14ef5e1916605f36b4edbca Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Mon, 4 Nov 2019 16:49:35 -0500 Subject: [PATCH 110/134] Implemented XRY Report folder logic --- .../autopsy/xryparser/XRYReportFile.java | 30 +++++++ .../autopsy/xryparser/XRYReportFolder.java | 80 +++++++++++++++++++ .../xryparser/XRYReportFolderTest.java | 64 +++++++++++++++ 3 files changed, 174 insertions(+) create mode 100755 Core/src/org/sleuthkit/autopsy/xryparser/XRYReportFile.java create mode 100755 Core/src/org/sleuthkit/autopsy/xryparser/XRYReportFolder.java create mode 100755 Core/test/unit/src/org/sleuthkit/autopsy/xryparser/XRYReportFolderTest.java diff --git a/Core/src/org/sleuthkit/autopsy/xryparser/XRYReportFile.java b/Core/src/org/sleuthkit/autopsy/xryparser/XRYReportFile.java new file mode 100755 index 0000000000..bd1fa14ad0 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/xryparser/XRYReportFile.java @@ -0,0 +1,30 @@ +/* + * 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.xryparser; + +import java.nio.file.Path; +import org.apache.commons.io.FilenameUtils; + +/** + * + * @author dsmyda + */ +public class XRYReportFile { + + //It is assumed XRY files have a txt extension. + private static final String EXTENSION = "txt"; + + public static boolean isXRYReportFile(Path file) { + String parsedExtension = FilenameUtils.getExtension(file.toString()); + if(!EXTENSION.equals(parsedExtension)) { + return false; + } + + + + return true; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/xryparser/XRYReportFolder.java b/Core/src/org/sleuthkit/autopsy/xryparser/XRYReportFolder.java new file mode 100755 index 0000000000..004396a406 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/xryparser/XRYReportFolder.java @@ -0,0 +1,80 @@ +/* + * 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.xryparser; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +/** + * + */ +public class XRYReportFolder { + + //Depth to the first potential XRY file. Root children are at 1. We expect + //XRY files to be contained in a subfolder of the root, so 2. + private static final int XRY_FILES_DEPTH = 2; + + public XRYReportFolder(Path reportFolder) { + + } + + public List getXRYReportFiles() { + throw new UnsupportedOperationException(); + } + + public List getOtherFiles() { + throw new UnsupportedOperationException(); + } + + /** + * Searches all immediate subdirectories looking for XRY files. If any file + * matches, the folder is assumed to be an XRY Report folder. + * + * @param folder Folder to test. Assumes that caller has read access to the + * folder and all of its immediate sub folders. + * @return Flag indicating a valid XRY report folder. + * + * @throws IOException Error occurred during File I/O. + * @throws SecurityException If the security manager denies access to the + * starting folder. + */ + public static boolean isXRYReportFolder(Path folder) throws IOException { + BasicFileAttributes folderAttributes = Files.readAttributes(folder, BasicFileAttributes.class); + if (!folderAttributes.isDirectory()) { + return false; + } + + try (Stream folderChildren = Files.walk(folder, XRY_FILES_DEPTH)) { + Optional xryFile = folderChildren + //Filter out all directories. + .filter(path -> { + try { + BasicFileAttributes fileAttributes = + Files.readAttributes(path, BasicFileAttributes.class); + return !fileAttributes.isDirectory(); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + }) + //Filter out all files that are not in the right depth. + .filter(path -> folder.relativize(path).getNameCount() == XRY_FILES_DEPTH) + //Filter out all files that don't pass the report test. + .filter(filePath -> XRYReportFile.isXRYReportFile(filePath)) + //For debugging. + .peek(path -> System.out.println(path)) + .findAny(); + return xryFile.isPresent(); + } catch (UncheckedIOException ex) { + throw ex.getCause(); + } + } +} diff --git a/Core/test/unit/src/org/sleuthkit/autopsy/xryparser/XRYReportFolderTest.java b/Core/test/unit/src/org/sleuthkit/autopsy/xryparser/XRYReportFolderTest.java new file mode 100755 index 0000000000..2e980fd15e --- /dev/null +++ b/Core/test/unit/src/org/sleuthkit/autopsy/xryparser/XRYReportFolderTest.java @@ -0,0 +1,64 @@ +/* + * 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.xryparser; + +import java.nio.file.Path; +import java.nio.file.Paths; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + * @author dsmyda + */ +public class XRYReportFolderTest { + + private final Path reportDirectory = Paths.get("C:", "Users", "dsmyda", "Downloads", "2019-10-23-XRYSamples"); + private final Path notAReportDirectory = Paths.get("C:", "Users", "dsmyda", "Downloads", "Not-2019-10-23-XRYSamples"); + private final Path biggerReportDirectory = Paths.get("C:", "Users", "dsmyda", "Documents", "personal"); + + public XRYReportFolderTest() { + } + + /** + * Test of getXRYReportFiles method, of class XRYReportFolder. + */ +// @Test +// public void testGetXRYReportFiles() { +// System.out.println("getXRYReportFiles"); +// XRYReportFolder instance = null; +// List expResult = null; +// List result = instance.getXRYReportFiles(); +// assertEquals(expResult, result); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } + + /** + * Test of getOtherFiles method, of class XRYReportFolder. + */ +// @Test +// public void testGetOtherFiles() { +// System.out.println("getOtherFiles"); +// XRYReportFolder instance = null; +// List expResult = null; +// List result = instance.getOtherFiles(); +// assertEquals(expResult, result); +// // TODO review the generated test code and remove the default call to fail. +// fail("The test case is a prototype."); +// } + + /** + * Test of isXRYReportFolder method, of class XRYReportFolder. + */ + @Test + public void testIsXRYReportFolder() throws Exception { + assertTrue("Not flagged as an xry folder, but should be", XRYReportFolder.isXRYReportFolder(reportDirectory)); + assertFalse("Flagged as an xry folder, but shouldn't be", XRYReportFolder.isXRYReportFolder(notAReportDirectory)); + assertTrue("Not flagged as an xry folder, but should be", XRYReportFolder.isXRYReportFolder(biggerReportDirectory)); + } + +} From 429f5228f80105c972a04ca89020e17eb6e7f472 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Mon, 4 Nov 2019 16:52:04 -0500 Subject: [PATCH 111/134] Cleaned up unit test code --- .../xryparser/XRYReportExtractorTest.java | 16 -------------- .../autopsy/xryparser/XRYReportTest.java | 22 +------------------ 2 files changed, 1 insertion(+), 37 deletions(-) diff --git a/Core/test/unit/src/org/sleuthkit/autopsy/xryparser/XRYReportExtractorTest.java b/Core/test/unit/src/org/sleuthkit/autopsy/xryparser/XRYReportExtractorTest.java index 19a4ee747a..2f39468ebe 100755 --- a/Core/test/unit/src/org/sleuthkit/autopsy/xryparser/XRYReportExtractorTest.java +++ b/Core/test/unit/src/org/sleuthkit/autopsy/xryparser/XRYReportExtractorTest.java @@ -41,22 +41,6 @@ public class XRYReportExtractorTest { public XRYReportExtractorTest() { } - @BeforeClass - public static void setUpClass() { - } - - @AfterClass - public static void tearDownClass() { - } - - @Before - public void setUp() { - } - - @After - public void tearDown() { - } - @Test public void testCallLogsSample() throws IOException { Path reportPath = reportDirectory.resolve("Calls.txt"); diff --git a/Core/test/unit/src/org/sleuthkit/autopsy/xryparser/XRYReportTest.java b/Core/test/unit/src/org/sleuthkit/autopsy/xryparser/XRYReportTest.java index 85c791b7db..64391e7564 100755 --- a/Core/test/unit/src/org/sleuthkit/autopsy/xryparser/XRYReportTest.java +++ b/Core/test/unit/src/org/sleuthkit/autopsy/xryparser/XRYReportTest.java @@ -25,10 +25,6 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; import org.junit.Test; import static org.junit.Assert.*; @@ -41,23 +37,7 @@ public class XRYReportTest { public XRYReportTest() { } - - @BeforeClass - public static void setUpClass() { - } - - @AfterClass - public static void tearDownClass() { - } - - @Before - public void setUp() { - } - - @After - public void tearDown() { - } - + @Test public void testParseType() { List reportTestFiles = new ArrayList() { From 3a54c9a03bad3452f984b52d0c822d88200829ab Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Tue, 5 Nov 2019 13:46:26 -0500 Subject: [PATCH 112/134] Simplfied the model --- .../datamodel/ArtifactWaypoint.java | 197 ------------ .../geolocation/datamodel/EXIFWaypoint.java | 4 +- .../datamodel/LastKnownWaypoint.java | 4 +- .../autopsy/geolocation/datamodel/Route.java | 81 ++--- .../geolocation/datamodel/RoutePoint.java | 85 ----- .../geolocation/datamodel/SearchWaypoint.java | 6 +- .../datamodel/TrackpointWaypoint.java | 4 +- .../geolocation/datamodel/Waypoint.java | 303 +++++++++++++----- 8 files changed, 279 insertions(+), 405 deletions(-) delete mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactWaypoint.java delete mode 100755 Core/src/org/sleuthkit/autopsy/geolocation/datamodel/RoutePoint.java diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactWaypoint.java deleted file mode 100755 index a2a0f3315f..0000000000 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/ArtifactWaypoint.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * 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.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * Representation of a Waypoint created from a BlackboardArtifact. - * - */ -class ArtifactWaypoint implements Waypoint { - - final private Long timestamp; - final private Double longitude; - final private Double latitude; - final private Double altitude; - final private String label; - final private AbstractFile image; - final private BlackboardArtifact artifact; - - // This list is not expected to change after construction so the - // constructor will take care of creating an unmodifiable List - final private List immutablePropertiesList; - - /** - * Construct a waypoint with the given artifact. - * - * @param artifact BlackboardArtifact for this waypoint - * - * @throws GeoLocationDataException Exception will be thrown if artifact did - * not have a valid longitude and latitude. - */ - ArtifactWaypoint(BlackboardArtifact artifact) throws GeoLocationDataException { - this(artifact, - getAttributesFromArtifactAsMap(artifact)); - } - - /** - * Constructor that sets all of the member variables. - * - * @param artifact BlackboardArtifact for this waypoint - * @param label String waypoint label - * @param timestamp Long timestamp, unix/java epoch seconds - * @param latitude Double waypoint latitude - * @param longitude Double waypoint longitude - * @param altitude Double waypoint altitude - * @param image AbstractFile image for waypoint, this maybe null - * @param attributeMap A Map of attributes for the given artifact - * - * @throws GeoLocationDataException Exception will be thrown if artifact did - * not have a valid longitude and latitude. - */ - ArtifactWaypoint(BlackboardArtifact artifact, String label, Long timestamp, Double latitude, Double longitude, Double altitude, AbstractFile image, Map attributeMap) throws GeoLocationDataException { - if (longitude == null || latitude == null) { - throw new GeoLocationDataException("Invalid waypoint, null value passed for longitude or latitude"); - } - - this.artifact = artifact; - this.label = label; - this.image = image; - this.timestamp = timestamp; - this.longitude = longitude; - this.latitude = latitude; - this.altitude = altitude; - - immutablePropertiesList = Collections.unmodifiableList(Waypoint.createGeolocationProperties(attributeMap)); - } - - /** - * Constructs a new ArtifactWaypoint. - * - * @param artifact BlackboardArtifact for this waypoint - * @param attributeMap A Map of the BlackboardAttributes for the given - * artifact. - * - * @throws GeoLocationDataException - */ - private ArtifactWaypoint(BlackboardArtifact artifact, Map attributeMap) throws GeoLocationDataException { - this(artifact, - getLabelFromArtifact(attributeMap), - attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME).getValueLong() : null, - attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE).getValueDouble() : null, - attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE).getValueDouble() : null, - attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE).getValueDouble() : null, - null, attributeMap); - } - - /** - * Get the BlackboardArtifact that this waypoint represents. - * - * @return BlackboardArtifact for this waypoint. - */ - BlackboardArtifact getArtifact() { - return artifact; - } - - @Override - public Long getTimestamp() { - return timestamp; - } - - @Override - public String getLabel() { - return label; - } - - @Override - public Double getLatitude() { - return latitude; - } - - @Override - public Double getLongitude() { - return longitude; - } - - @Override - public Double getAltitude() { - return altitude; - } - - @Override - public AbstractFile getImage() { - return image; - } - - @Override - public List getOtherProperties() { - return immutablePropertiesList; - } - - /** - * Gets the label for this waypoint. - * - * @param artifact BlackboardArtifact for waypoint - * - * @return Returns a label for the waypoint, or empty string if no label was - * found. - */ - private static String getLabelFromArtifact(Map attributeMap) { - BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); - if (attribute != null) { - return attribute.getDisplayString(); - } - - return ""; - } - - /** - * Gets the list of attributes from the artifact and puts them into a map - * with the ATRIBUTE_TYPE as the key. - * - * @param artifact BlackboardArtifact current artifact - * - * @return A Map of BlackboardAttributes for the given artifact with - * ATTRIBUTE_TYPE as the key. - * - * @throws GeoLocationDataException - */ - static Map getAttributesFromArtifactAsMap(BlackboardArtifact artifact) throws GeoLocationDataException { - Map attributeMap = new HashMap<>(); - try { - List attributeList = artifact.getAttributes(); - for (BlackboardAttribute attribute : attributeList) { - BlackboardAttribute.ATTRIBUTE_TYPE type = BlackboardAttribute.ATTRIBUTE_TYPE.fromID(attribute.getAttributeType().getTypeID()); - attributeMap.put(type, attribute); - } - } catch (TskCoreException ex) { - throw new GeoLocationDataException("Unable to get attributes from artifact", ex); - } - - return attributeMap; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/EXIFWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/EXIFWaypoint.java index 927b91b1d4..3a1ec3e66e 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/EXIFWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/EXIFWaypoint.java @@ -27,7 +27,7 @@ import org.sleuthkit.datamodel.TskCoreException; /** * Waypoint wrapper class for TSK_METADATA_EXIF artifacts. */ -final class EXIFWaypoint extends ArtifactWaypoint { +final class EXIFWaypoint extends Waypoint { /** * Construct a way point with the given artifact. @@ -56,7 +56,7 @@ final class EXIFWaypoint extends ArtifactWaypoint { attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE).getValueDouble() : null, attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE).getValueDouble() : null, attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE).getValueDouble() : null, - image, attributeMap); + image, attributeMap, null); } /** diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/LastKnownWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/LastKnownWaypoint.java index 3c27e81f5d..7bf85874ff 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/LastKnownWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/LastKnownWaypoint.java @@ -26,7 +26,7 @@ import org.sleuthkit.datamodel.BlackboardAttribute; /** * A Last Known Location Waypoint object. */ -final class LastKnownWaypoint extends ArtifactWaypoint { +final class LastKnownWaypoint extends Waypoint { /** * Constructs a new waypoint. @@ -55,7 +55,7 @@ final class LastKnownWaypoint extends ArtifactWaypoint { attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE).getValueDouble() : null, attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE).getValueDouble() : null, attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE).getValueDouble() : null, - null, attributeMap); + null, attributeMap, null); } /** diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java index 8b6d3a968b..29cfd5afe4 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Route.java @@ -31,14 +31,13 @@ import org.sleuthkit.datamodel.TskCoreException; /** * A Route represents a TSK_GPS_ROUTE artifact which has a start and end point - * however the class was written with the assumption that some routes may have + * however the class was written with the assumption that routes may have * more that two points. * */ public final class Route { private final List points; private final Long timestamp; - private final Double altitude; // This list is not expected to change after construction so the // constructor will take care of creating an unmodifiable List @@ -78,14 +77,11 @@ public final class Route { Route(BlackboardArtifact artifact) throws GeoLocationDataException { points = new ArrayList<>(); - Map attributeMap = ArtifactWaypoint.getAttributesFromArtifactAsMap(artifact); - points.add(getRouteStartPoint(attributeMap)); - points.add(getRouteEndPoint(attributeMap)); - - BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE); - altitude = attribute != null ? attribute.getValueDouble() : null; - - attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME); + Map attributeMap = Waypoint.getAttributesFromArtifactAsMap(artifact); + points.add(getRouteStartPoint(artifact, attributeMap)); + points.add(getRouteEndPoint(artifact, attributeMap)); + + BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME); timestamp = attribute != null ? attribute.getValueLong() : null; immutablePropertiesList = Collections.unmodifiableList(Waypoint.createGeolocationProperties(attributeMap)); @@ -100,24 +96,6 @@ public final class Route { return Collections.unmodifiableList(points); } - /** - * Get the timestamp for this Route - * - * @return The timestamp (java/unix epoch seconds) or null if none was set. - */ - public Long getTimestamp() { - return timestamp; - } - - /** - * Get the altitude for this route. - * - * @return The Double altitude value or null if none was set. - */ - public Double getAltitude() { - return altitude; - } - /** * Get the "Other attributes" for this route. The map will contain display * name, formatted value pairs. This list is unmodifiable. @@ -139,25 +117,39 @@ public final class Route { public String getLabel() { return Bundle.Route_Label(); } - + + public Long getTimestamp() { + return timestamp; + } + /** * Get the route start point. * - * @param attributeMap Map of artifact attributes for this waypoint + * @param attributeMap Map of artifact attributes for this waypoint. + * + * An exception will be thrown if longitude or latitude is null. * - * @return Start RoutePoint + * @return Start waypoint * - * @throws GeoLocationDataException when longitude or latitude is null + * @throws GeoLocationDataException. */ @Messages({ "Route_Start_Label=Start" }) - private Waypoint getRouteStartPoint(Map attributeMap) throws GeoLocationDataException { + private Waypoint getRouteStartPoint(BlackboardArtifact artifact, Map attributeMap) throws GeoLocationDataException { BlackboardAttribute latitude = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START); BlackboardAttribute longitude = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START); + BlackboardAttribute altitude = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE); + BlackboardAttribute pointTimestamp = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME); if (latitude != null && longitude != null) { - return new RoutePoint(this, latitude.getValueDouble(), longitude.getValueDouble(), Bundle.Route_Start_Label()); + return new Waypoint(artifact, + Bundle.Route_Start_Label(), + pointTimestamp != null ? pointTimestamp.getValueLong() : null, + latitude.getValueDouble(), + longitude.getValueDouble(), + altitude != null ? altitude.getValueDouble() : null, + null, attributeMap, this); } else { throw new GeoLocationDataException("Unable to create route start point, invalid longitude and/or latitude"); } @@ -165,24 +157,33 @@ public final class Route { /** * Get the route End point. + * + * An exception will be thrown if longitude or latitude is null. * * @param attributeMap Map of artifact attributes for this waypoint * - * @return End RoutePoint or null if valid longitude and latitude are not - * found + * @return The end waypoint * - * @throws GeoLocationDataException when longitude or latitude is null + * @throws GeoLocationDataException */ @Messages({ "Route_End_Label=End" }) - private Waypoint getRouteEndPoint(Map attributeMap) throws GeoLocationDataException { + private Waypoint getRouteEndPoint(BlackboardArtifact artifact, Map attributeMap) throws GeoLocationDataException { BlackboardAttribute latitude = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END); BlackboardAttribute longitude = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END); + BlackboardAttribute altitude = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE); + BlackboardAttribute pointTimestamp = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME); if (latitude != null && longitude != null) { - return new RoutePoint(this, latitude.getValueDouble(), longitude.getValueDouble(), Bundle.Route_End_Label()); - }else { + return new Waypoint(artifact, + Bundle.Route_End_Label(), + pointTimestamp != null ? pointTimestamp.getValueLong() : null, + latitude.getValueDouble(), + longitude.getValueDouble(), + altitude != null ? altitude.getValueDouble() : null, + null, attributeMap, this); + } else { throw new GeoLocationDataException("Unable to create route end point, invalid longitude and/or latitude"); } } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/RoutePoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/RoutePoint.java deleted file mode 100755 index 50087de873..0000000000 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/RoutePoint.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * - * 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.util.List; -import org.sleuthkit.datamodel.AbstractFile; - -/** - * A point in a Route. For future use this point will have a pointer to its - * parent route. - */ -final class RoutePoint implements Waypoint { - - private final Route parent; - private final Double longitude; - private final Double latitude; - private final String label; - - /** - * Construct a route for a route. - * - * @param parent The parent route object. - * @param latitude Latitude for point - * @param longitude Longitude for point - * @param label Way point label. - */ - RoutePoint(Route parent, double latitude, double longitude, String label) { - this.longitude = longitude; - this.latitude = latitude; - this.label = label; - this.parent = parent; - } - - @Override - public Long getTimestamp() { - return parent.getTimestamp(); - } - - @Override - public String getLabel() { - return label; - } - - @Override - public Double getLatitude() { - return latitude; - } - - @Override - public Double getLongitude() { - return longitude; - } - - @Override - public Double getAltitude() { - return parent.getAltitude(); - } - - @Override - public List getOtherProperties() { - return parent.getOtherProperties(); - } - - @Override - public AbstractFile getImage() { - return null; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/SearchWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/SearchWaypoint.java index bc70cfe570..7f0746d6ca 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/SearchWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/SearchWaypoint.java @@ -24,9 +24,9 @@ import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; /** - * A SearchWaypoint is a subclass of ArtifactWaypoint. + * A SearchWaypoint is a subclass of Waypoint. */ -final class SearchWaypoint extends ArtifactWaypoint { +final class SearchWaypoint extends Waypoint { /** * Construct a waypoint for TSK_GPS_SEARCH artifact. @@ -44,7 +44,7 @@ final class SearchWaypoint extends ArtifactWaypoint { attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE).getValueDouble() : null, attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE).getValueDouble() : null, attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE).getValueDouble() : null, - null, attributeMap); + null, attributeMap, null); } /** diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/TrackpointWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/TrackpointWaypoint.java index ea724f2821..7009aa63a1 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/TrackpointWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/TrackpointWaypoint.java @@ -26,7 +26,7 @@ import org.sleuthkit.datamodel.BlackboardAttribute; /** * A wrapper class for TSK_GPS_TRACKPOINT artifacts. */ -final class TrackpointWaypoint extends ArtifactWaypoint { +final class TrackpointWaypoint extends Waypoint { /** * Construct a waypoint for trackpoints. * @@ -43,7 +43,7 @@ final class TrackpointWaypoint extends ArtifactWaypoint { attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE).getValueDouble() : null, attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE).getValueDouble() : null, attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE).getValueDouble() : null, - null, attributeMap); + null, attributeMap, null); } /** diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java index 45c8816574..3c84c92c26 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java @@ -19,6 +19,9 @@ package org.sleuthkit.autopsy.geolocation.datamodel; import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -31,17 +34,29 @@ import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; /** - * The basic details of a waypoint. + * Representation of a Waypoint created from a BlackboardArtifact. * */ -public interface Waypoint { - static final Logger logger = Logger.getLogger(Waypoint.class.getName()); - +public class Waypoint { + + final private Long timestamp; + final private Double longitude; + final private Double latitude; + final private Double altitude; + final private String label; + final private AbstractFile image; + final private BlackboardArtifact artifact; + final private Route route; + + // This list is not expected to change after construction. The + // constructor will take care of making an unmodifiable List + final private List immutablePropertiesList; + /** * This is a list of attributes that are already being handled by the - * waypoint classes and will have get functions. + * by getter functions. */ - BlackboardAttribute.ATTRIBUTE_TYPE[] ALREADY_HANDLED_ATTRIBUTES = { + static BlackboardAttribute.ATTRIBUTE_TYPE[] ALREADY_HANDLED_ATTRIBUTES = { BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE, @@ -53,61 +68,202 @@ public interface 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()); + /** - * Interface to describe a waypoint. A waypoint is made up of - * a longitude, latitude, label, timestamp, type, image and altitude. - * + * Construct a waypoint with the given artifact. + * + * @param artifact BlackboardArtifact for this waypoint + * + * @throws GeoLocationDataException Exception will be thrown if artifact did + * not have a valid longitude and latitude. + */ + Waypoint(BlackboardArtifact artifact) throws GeoLocationDataException { + this(artifact, + getAttributesFromArtifactAsMap(artifact)); + } + + /** + * Constructor that initializes all of the member variables. + * + * @param artifact BlackboardArtifact for this waypoint + * @param label String waypoint label + * @param timestamp Long timestamp, unix/java epoch seconds + * @param latitude Double waypoint latitude + * @param longitude Double waypoint longitude + * @param altitude Double waypoint altitude + * @param image AbstractFile image for waypoint, this maybe null + * @param attributeMap A Map of attributes for the given artifact + * + * @throws GeoLocationDataException Exception will be thrown if artifact did + * not have a valid longitude and latitude. + */ + Waypoint(BlackboardArtifact artifact, String label, Long timestamp, Double latitude, Double longitude, Double altitude, AbstractFile image, Map attributeMap, Route route) throws GeoLocationDataException { + if (longitude == null || latitude == null) { + throw new GeoLocationDataException("Invalid waypoint, null value passed for longitude or latitude"); + } + + this.artifact = artifact; + this.label = label; + this.image = image; + this.timestamp = timestamp; + this.longitude = longitude; + this.latitude = latitude; + this.altitude = altitude; + this.route = null; + + immutablePropertiesList = Collections.unmodifiableList(createGeolocationProperties(attributeMap)); + } + + /** + * Constructs a new ArtifactWaypoint. + * + * @param artifact BlackboardArtifact for this waypoint + * @param attributeMap A Map of the BlackboardAttributes for the given + * artifact. + * + * @throws GeoLocationDataException + */ + private Waypoint(BlackboardArtifact artifact, Map attributeMap) throws GeoLocationDataException { + this(artifact, + getLabelFromArtifact(attributeMap), + attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME).getValueLong() : null, + attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE).getValueDouble() : null, + attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE).getValueDouble() : null, + attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE) != null ? attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE).getValueDouble() : null, + null, attributeMap, null); + } + + /** + * Get the BlackboardArtifact that this waypoint represents. + * + * @return BlackboardArtifact for this waypoint. + */ + public BlackboardArtifact getArtifact() { + return artifact; + } + + /** + * Interface to describe a waypoint. A waypoint is made up of a longitude, + * latitude, label, timestamp, type, image and altitude. + * * A good way point should have at minimum a longitude and latutude. * * @return Timestamp in java/unix epoch seconds or null if none was set. */ - Long getTimestamp(); + public Long getTimestamp() { + return timestamp; + } /** * Get the label for this point object. * * @return String label for the point or null if none was set */ - String getLabel(); + public String getLabel() { + return label; + } /** * Get the latitude for this point. * - * @return Returns the latitude for the point or null if none was set + * @return Returns the latitude for the point */ - Double getLatitude(); + public Double getLatitude() { + return latitude; + } /** * Get the longitude for this point. * - * @return Returns the longitude for the point or null if none was set + * @return Returns the longitude for the point */ - Double getLongitude(); + public Double getLongitude() { + return longitude; + } /** * Get the altitude for this point. * * @return Returns the altitude for the point or null if none was set */ - Double getAltitude(); - - /** - * Gets an unmodifiable List of other properties that may be interesting to this way point. - * The List will not include properties for which getter functions - * exist. - * - * @return A List of waypoint properties - */ - List getOtherProperties(); + public Double getAltitude() { + return altitude; + } /** * Get the image for this waypoint. * * @return AbstractFile image or null if one was not set */ - AbstractFile getImage(); + public AbstractFile getImage() { + return image; + } /** + * Gets an unmodifiable List of other properties that may be interesting to + * this way point. The List will not include properties for which getter + * functions exist. + * + * @return A List of waypoint properties + */ + public List getOtherProperties() { + return immutablePropertiesList; + } + + /** + * Returns the route that this waypoint is apart of . + * + * @return The waypoint route or null if the waypoint is not apart of a route. + */ + public Route getRoute() { + return route; + } + + /** + * Gets the label for this waypoint. + * + * @param artifact BlackboardArtifact for waypoint + * + * @return Returns a label for the waypoint, or empty string if no label was + * found. + */ + private static String getLabelFromArtifact(Map attributeMap) { + BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); + if (attribute != null) { + return attribute.getDisplayString(); + } + + return ""; + } + + /** + * Gets the list of attributes from the artifact and puts them into a map + * with the ATRIBUTE_TYPE as the key. + * + * @param artifact BlackboardArtifact current artifact + * + * @return A Map of BlackboardAttributes for the given artifact with + * ATTRIBUTE_TYPE as the key. + * + * @throws GeoLocationDataException + */ + static Map getAttributesFromArtifactAsMap(BlackboardArtifact artifact) throws GeoLocationDataException { + Map attributeMap = new HashMap<>(); + try { + List attributeList = artifact.getAttributes(); + for (BlackboardAttribute attribute : attributeList) { + BlackboardAttribute.ATTRIBUTE_TYPE type = BlackboardAttribute.ATTRIBUTE_TYPE.fromID(attribute.getAttributeType().getTypeID()); + attributeMap.put(type, attribute); + } + } catch (TskCoreException ex) { + throw new GeoLocationDataException("Unable to get attributes from artifact", ex); + } + + return attributeMap; + } + + /** * Returns a list of Waypoints for the artifacts with geolocation * information. * @@ -120,7 +276,7 @@ public interface Waypoint { * * @throws GeoLocationDataException */ - static List getAllWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { + public static List getAllWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { List points = new ArrayList<>(); points.addAll(getTrackpointWaypoints(skCase)); @@ -141,22 +297,22 @@ public interface Waypoint { * * @throws GeoLocationDataException */ - static List getTrackpointWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { + public static List getTrackpointWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { List artifacts = null; - try{ + try { artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT); - } catch(TskCoreException ex) { + } 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{ - ArtifactWaypoint point = new TrackpointWaypoint(artifact); + try { + Waypoint point = new TrackpointWaypoint(artifact); points.add(point); - } catch(GeoLocationDataException ex) { + } catch (GeoLocationDataException ex) { logger.log(Level.WARNING, String.format("No longitude or latitude available for TSK_GPS_TRACKPOINT artifactID: %d", artifact.getArtifactID())); - } + } } return points; } @@ -170,25 +326,25 @@ public interface Waypoint { * * @throws GeoLocationDataException */ - static List getEXIFWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { + static public List getEXIFWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { List artifacts = null; - try{ + try { artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF); - } catch(TskCoreException ex) { + } 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{ - ArtifactWaypoint point = new EXIFWaypoint(artifact); + try { + Waypoint point = new EXIFWaypoint(artifact); points.add(point); - } catch(GeoLocationDataException ex) { + } 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; @@ -203,23 +359,23 @@ public interface Waypoint { * * @throws GeoLocationDataException */ - static List getSearchWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { + public static List getSearchWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { List artifacts = null; - try{ + try { artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH); - } catch(TskCoreException ex) { + } 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{ - ArtifactWaypoint point = new SearchWaypoint(artifact); + try { + Waypoint point = new SearchWaypoint(artifact); points.add(point); - } catch(GeoLocationDataException ex) { + } catch (GeoLocationDataException ex) { logger.log(Level.WARNING, String.format("No longitude or latitude available for TSK_GPS_SEARCH artifactID: %d", artifact.getArtifactID())); - } + } } } return points; @@ -234,23 +390,23 @@ public interface Waypoint { * * @throws GeoLocationDataException */ - static List getLastKnownWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { + public static List getLastKnownWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { List artifacts = null; - try{ + try { artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION); - } catch(TskCoreException ex) { + } 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{ - ArtifactWaypoint point = new LastKnownWaypoint(artifact); + for (BlackboardArtifact artifact : artifacts) { + try { + Waypoint point = new LastKnownWaypoint(artifact); points.add(point); - } catch(GeoLocationDataException ex) { + } 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; @@ -265,29 +421,28 @@ public interface Waypoint { * * @throws GeoLocationDataException */ - static List getBookmarkWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { + public static List getBookmarkWaypoints(SleuthkitCase skCase) throws GeoLocationDataException { List artifacts = null; - try{ + try { artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK); - } catch(TskCoreException ex) { + } 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{ - ArtifactWaypoint point = new ArtifactWaypoint(artifact); + try { + Waypoint point = new Waypoint(artifact); points.add(point); - } catch(GeoLocationDataException ex) { + } 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 * will not include attributes that the Waypoint interfact has get functions @@ -299,10 +454,10 @@ public interface Waypoint { * * @throws GeoLocationDataException */ - static List createGeolocationProperties(Map attributeMap) throws GeoLocationDataException { + static public List createGeolocationProperties(Map attributeMap) throws GeoLocationDataException { List list = new ArrayList<>(); - Set keys = attributeMap.keySet(); + Set keys = new HashSet<>(attributeMap.keySet()); for (BlackboardAttribute.ATTRIBUTE_TYPE type : ALREADY_HANDLED_ATTRIBUTES) { keys.remove(type); @@ -321,7 +476,7 @@ public interface Waypoint { * Simple property class for waypoint properties that a purely * informational. */ - class Property { + public static final class Property { private final String displayName; private final String value; @@ -333,7 +488,7 @@ public interface Waypoint { * or empty string. * @param value String value for property. Can be null. */ - Property(String displayName, String value) { + private Property(String displayName, String value) { this.displayName = displayName; this.value = value; } From 388140482db4efcf4f644b935c7da0a4319298dd Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 5 Nov 2019 14:17:46 -0500 Subject: [PATCH 113/134] 5665 remove commented out line of code --- .../autopsy/directorytree/actionhelpers/ExtractActionHelper.java | 1 - 1 file changed, 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/actionhelpers/ExtractActionHelper.java b/Core/src/org/sleuthkit/autopsy/directorytree/actionhelpers/ExtractActionHelper.java index fa10b046a4..339a58142d 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/actionhelpers/ExtractActionHelper.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/actionhelpers/ExtractActionHelper.java @@ -204,7 +204,6 @@ public class ExtractActionHelper { FileExtractionTask task = it.next(); if (ContentUtils.isDotDirectory(task.source)) { - //JOptionPane.showMessageDialog((Component) e.getSource(), "Cannot extract virtual " + task.source.getName() + " directory.", "File is Virtual Directory", JOptionPane.WARNING_MESSAGE); it.remove(); continue; } From 8905b0d1679cacc24be8cebfea4063599e452a04 Mon Sep 17 00:00:00 2001 From: esaunders Date: Tue, 5 Nov 2019 14:32:30 -0500 Subject: [PATCH 114/134] Update Linux/Mac installation instructions and remove reference to Oracle. --- .../sleuthkit/autopsy/core/Bundle.properties | 2 +- .../autopsy/core/Bundle.properties-MERGED | 2 +- .../autopsy/core/Bundle_ja.properties | 57 +++++++++---------- Running_Linux_OSX.txt | 14 ++--- 4 files changed, 37 insertions(+), 38 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/core/Bundle.properties b/Core/src/org/sleuthkit/autopsy/core/Bundle.properties index db8e9e9808..4fccbcb6fa 100644 --- a/Core/src/org/sleuthkit/autopsy/core/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/core/Bundle.properties @@ -11,7 +11,7 @@ OpenIDE-Module-Short-Description=Autopsy Core Module org_sleuthkit_autopsy_core_update_center=http://sleuthkit.org/autopsy/updates.xml Services/AutoupdateType/org_sleuthkit_autopsy_core_update_center.settings=Autopsy Update Center Installer.errorInitJavafx.msg=Error initializing JavaFX. -Installer.errorInitJavafx.details=\ Some features will not be available. Check that you have the right JRE installed (Oracle JRE > 1.7.10). +Installer.errorInitJavafx.details=\ Some features will not be available. Check that you have JavaFX installed (OpenJFX 8). ServicesMonitor.failedService.notify.title=Service Is Down ServicesMonitor.failedService.notify.msg=Connection to {0} is down ServicesMonitor.restoredService.notify.title=Service Is Up diff --git a/Core/src/org/sleuthkit/autopsy/core/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/core/Bundle.properties-MERGED index 087eaec314..0b16a9701f 100755 --- a/Core/src/org/sleuthkit/autopsy/core/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/core/Bundle.properties-MERGED @@ -15,7 +15,7 @@ OpenIDE-Module-Short-Description=Autopsy Core Module org_sleuthkit_autopsy_core_update_center=http://sleuthkit.org/autopsy/updates.xml Services/AutoupdateType/org_sleuthkit_autopsy_core_update_center.settings=Autopsy Update Center Installer.errorInitJavafx.msg=Error initializing JavaFX. -Installer.errorInitJavafx.details=\ Some features will not be available. Check that you have the right JRE installed (Oracle JRE > 1.7.10). +Installer.errorInitJavafx.details=\ Some features will not be available. Check that you have JavaFX installed (OpenJFX 8). ServicesMonitor.failedService.notify.title=Service Is Down ServicesMonitor.failedService.notify.msg=Connection to {0} is down ServicesMonitor.restoredService.notify.title=Service Is Up diff --git a/Core/src/org/sleuthkit/autopsy/core/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/core/Bundle_ja.properties index 171ced6172..7bd07e089b 100644 --- a/Core/src/org/sleuthkit/autopsy/core/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/core/Bundle_ja.properties @@ -1,31 +1,30 @@ -OpenIDE-Module-Display-Category=\u30A4\u30F3\u30D5\u30E9\u30B9\u30C8\u30E9\u30AF\u30C1\u30E3\u30FC +OpenIDE-Module-Display-Category=\u30a4\u30f3\u30d5\u30e9\u30b9\u30c8\u30e9\u30af\u30c1\u30e3\u30fc OpenIDE-Module-Long-Description=\ - \u3053\u308C\u304CAutopsy\u306E\u30B3\u30A2\u30E2\u30B8\u30E5\u30FC\u30EB\u3067\u3059\u3002\n\n\ - \u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3\u306E\u307F\u3067\u5B9F\u884C\u3059\u308B\u306E\u306B\u5FC5\u8981\u306A\u4E3B\u8981\u30B3\u30F3\u30DD\u30FC\u30CD\u30F3\u30C8\u304C\u542B\u307E\u308C\u3066\u3044\u307E\u3059\uFF1ARCP\u30D7\u30E9\u30C3\u30C8\u30D5\u30A9\u30FC\u30E0\u3001\u30A6\u30A3\u30F3\u30C9\u30A6\u30A4\u30F3\u30B0GUI\u3001Sleuth Kit\u30D0\u30A4\u30F3\u30C7\u30A3\u30F3\u30B0\u3001\u30C7\u30FC\u30BF\u30E2\u30C7\u30EB\uFF0F\u30B9\u30C8\u30EC\u30FC\u30B8\u3001\u30A8\u30AF\u30B9\u30D7\u30ED\u30FC\u30E9\u3001\u7D50\u679C\u30D3\u30E5\u30FC\u30A2\u3001\u30B3\u30F3\u30C6\u30F3\u30C4\u30D3\u30E5\u30FC\u30A2\u3001\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u7528\u30D5\u30EC\u30FC\u30E0\u30EF\u30FC\u30AF\u3001\u30EC\u30DD\u30FC\u30C8\u751F\u6210\u3001\u30D5\u30A1\u30A4\u30EB\u691C\u7D22\u7B49\u306E\u4E3B\u8981\u30C4\u30FC\u30EB\u3002\n\n\ - \u30E2\u30B8\u30E5\u30FC\u30EB\u5185\u306E\u30D5\u30EC\u30FC\u30E0\u30EF\u30FC\u30AF\u306B\u306F\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u3001\u30D3\u30E5\u30FC\u30A2\u3001\u30EC\u30DD\u30FC\u30C8\u751F\u6210\u306E\u30E2\u30B8\u30E5\u30FC\u30EB\u958B\u767A\u7528\u306EAPI\u304C\u542B\u307E\u308C\u307E\u3059\u3002\ - \u30E2\u30B8\u30E5\u30FC\u30EB\u306FAutopsy\u30D7\u30E9\u30B0\u30A4\u30F3\u30A4\u30F3\u30B9\u30C8\u30FC\u30E9\u30FC\u3092\u4F7F\u7528\u3057\u3001\u30D7\u30E9\u30B0\u30A4\u30F3\u3068\u3057\u3066\u5B9F\u88C5\u3067\u304D\u307E\u3059\u3002\n\ - \u3053\u306E\u30E2\u30B8\u30E5\u30FC\u30EB\u306F\u30A2\u30F3\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u3057\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u3055\u308C\u3066\u3044\u306A\u3051\u308C\u3070\u3001Autopsy\u306F\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\u3002\n\n\ - \u8A73\u7D30\u306F\u3053\u3061\u3089\u3067\u3054\u78BA\u8A8D\u304F\u3060\u3055\u3044\u3002http\://www.sleuthkit.org/autopsy/ -OpenIDE-Module-Name=Autopsy-\u30B3\u30A2 -OpenIDE-Module-Short-Description=Autopsy\u30B3\u30A2\u30E2\u30B8\u30E5\u30FC\u30EB + \u3053\u308c\u304cAutopsy\u306e\u30b3\u30a2\u30e2\u30b8\u30e5\u30fc\u30eb\u3067\u3059\u3002\n\n\ + \u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306e\u307f\u3067\u5b9f\u884c\u3059\u308b\u306e\u306b\u5fc5\u8981\u306a\u4e3b\u8981\u30b3\u30f3\u30dd\u30fc\u30cd\u30f3\u30c8\u304c\u542b\u307e\u308c\u3066\u3044\u307e\u3059\uff1aRCP\u30d7\u30e9\u30c3\u30c8\u30d5\u30a9\u30fc\u30e0\u3001\u30a6\u30a3\u30f3\u30c9\u30a6\u30a4\u30f3\u30b0GUI\u3001Sleuth Kit\u30d0\u30a4\u30f3\u30c7\u30a3\u30f3\u30b0\u3001\u30c7\u30fc\u30bf\u30e2\u30c7\u30eb\uff0f\u30b9\u30c8\u30ec\u30fc\u30b8\u3001\u30a8\u30af\u30b9\u30d7\u30ed\u30fc\u30e9\u3001\u7d50\u679c\u30d3\u30e5\u30fc\u30a2\u3001\u30b3\u30f3\u30c6\u30f3\u30c4\u30d3\u30e5\u30fc\u30a2\u3001\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u7528\u30d5\u30ec\u30fc\u30e0\u30ef\u30fc\u30af\u3001\u30ec\u30dd\u30fc\u30c8\u751f\u6210\u3001\u30d5\u30a1\u30a4\u30eb\u691c\u7d22\u7b49\u306e\u4e3b\u8981\u30c4\u30fc\u30eb\u3002\n\n\ + \u30e2\u30b8\u30e5\u30fc\u30eb\u5185\u306e\u30d5\u30ec\u30fc\u30e0\u30ef\u30fc\u30af\u306b\u306f\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u3001\u30d3\u30e5\u30fc\u30a2\u3001\u30ec\u30dd\u30fc\u30c8\u751f\u6210\u306e\u30e2\u30b8\u30e5\u30fc\u30eb\u958b\u767a\u7528\u306eAPI\u304c\u542b\u307e\u308c\u307e\u3059\u3002\ + \u30e2\u30b8\u30e5\u30fc\u30eb\u306fAutopsy\u30d7\u30e9\u30b0\u30a4\u30f3\u30a4\u30f3\u30b9\u30c8\u30fc\u30e9\u30fc\u3092\u4f7f\u7528\u3057\u3001\u30d7\u30e9\u30b0\u30a4\u30f3\u3068\u3057\u3066\u5b9f\u88c5\u3067\u304d\u307e\u3059\u3002\n\ + \u3053\u306e\u30e2\u30b8\u30e5\u30fc\u30eb\u306f\u30a2\u30f3\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3057\u306a\u3044\u3067\u304f\u3060\u3055\u3044\u3002\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3055\u308c\u3066\u3044\u306a\u3051\u308c\u3070\u3001Autopsy\u306f\u4f7f\u7528\u3067\u304d\u307e\u305b\u3093\u3002\n\n\ + \u8a73\u7d30\u306f\u3053\u3061\u3089\u3067\u3054\u78ba\u8a8d\u304f\u3060\u3055\u3044\u3002http\://www.sleuthkit.org/autopsy/ +OpenIDE-Module-Name=Autopsy-\u30b3\u30a2 +OpenIDE-Module-Short-Description=Autopsy\u30b3\u30a2\u30e2\u30b8\u30e5\u30fc\u30eb org_sleuthkit_autopsy_core_update_center=http\://sleuthkit.org/autopsy/updates_ja.xml -Services/AutoupdateType/org_sleuthkit_autopsy_core_update_center.settings=Autopsy\u30A2\u30C3\u30D7\u30C7\u30FC\u30C8\u30BB\u30F3\u30BF\u30FC -Installer.errorInitJavafx.msg=JavaFX\u521D\u671F\u5316\u30A8\u30E9\u30FC -Installer.errorInitJavafx.details=\u4E00\u90E8\u306E\u6A5F\u80FD\u304C\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093\u3002\u6B63\u3057\u3044JRE\u304C\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u3055\u308C\u3066\u3044\u308B\u304B\u3092\u78BA\u8A8D\u3057\u3066\u4E0B\u3055\u3044\u3002\uFF08Oracle JRE > 1.7.10\uFF09 -ServicesMonitor.failedService.notify.title=\u30B5\u30FC\u30D3\u30B9\u304C\u505C\u6B62\u3057\u3066\u3044\u307E\u3059 -ServicesMonitor.failedService.notify.msg={0}\u3078\u306E\u63A5\u7D9A\u304C\u30C0\u30A6\u30F3\u3057\u3066\u3044\u307E\u3059 -ServicesMonitor.restoredService.notify.title=\u30B5\u30FC\u30D3\u30B9\u304C\u7A3C\u50CD\u4E2D\u3067\u3059 -ServicesMonitor.restoredService.notify.msg={0}\u3078\u306E\u63A5\u7D9A\u304C\u5229\u7528\u3067\u304D\u307E\u3059 -ServicesMonitor.statusChange.notify.title=\u30B5\u30FC\u30D3\u30B9\u30B9\u30C6\u30FC\u30BF\u30B9\u30A2\u30C3\u30D7\u30C7\u30FC\u30C8 -ServicesMonitor.statusChange.notify.msg={0}\u306E\u30B9\u30C6\u30FC\u30BF\u30B9\u306F{1} -ServicesMonitor.nullServiceName.excepton.txt=\u30EA\u30AF\u30A8\u30B9\u30C8\u3055\u308C\u305F\u30B5\u30FC\u30D3\u30B9\u306F\u30CC\u30EB\u3067\u3059 -ServicesMonitor.unknownServiceName.excepton.txt=\u30EA\u30AF\u30A8\u30B9\u30C8\u3055\u308C\u305F\u30B5\u30FC\u30D3\u30B9{0}\u306F\u4E0D\u660E\u3067\u3059 -TextConverter.convert.exception.txt=\u30C6\u30AD\u30B9\u30C8{0}\u3092hex\u30C6\u30AD\u30B9\u30C8\u306B\u5909\u63DB\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F -TextConverter.convertFromHex.exception.txt=hex\u30C6\u30AD\u30B9\u30C8\u3092\u30C6\u30AD\u30B9\u30C8\u306B\u5909\u63DB\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F -ServicesMonitor.KeywordSearchNull=\u30AD\u30FC\u30EF\u30FC\u30C9\u691C\u7D22\u30B5\u30FC\u30D3\u30B9\u3092\u898B\u3064\u3051\u308C\u307E\u305B\u3093\u3067\u3057\u305F -ServicesMonitor.InvalidPortNumber=\u7121\u52B9\u306A\u30DD\u30FC\u30C8\u756A\u53F7 -ServicesMonitor.remoteCaseDatabase.displayName.text=\u8907\u6570\u306E\u30E6\u30FC\u30B6\u30FC\u306E\u30B1\u30FC\u30B9\u306E\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u30B5\u30FC\u30D3\u30B9 -ServicesMonitor.remoteKeywordSearch.displayName.text=\u8907\u6570\u306E\u30E6\u30FC\u30B6\u30FC\u306E\u30AD\u30FC\u30EF\u30FC\u30C9\u691C\u7D22\u30B5\u30FC\u30D3\u30B9 -ServicesMonitor.messaging.displayName.text=\u30E1\u30C3\u30BB\u30FC\u30B8\u30B5\u30FC\u30D3\u30B9 -ServicesMonitor.databaseConnectionInfo.error.msg=\u30B1\u30FC\u30B9\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u306E\u63A5\u7D9A\u60C5\u5831\u3092\u5165\u624B\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F -ServicesMonitor.messagingService.connErr.text=\u30E1\u30C3\u30BB\u30FC\u30B8\u30B5\u30FC\u30D3\u30B9\u306E\u63A5\u7D9A\u60C5\u5831\u3092\u5165\u624B\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F \ No newline at end of file +Services/AutoupdateType/org_sleuthkit_autopsy_core_update_center.settings=Autopsy\u30a2\u30c3\u30d7\u30c7\u30fc\u30c8\u30bb\u30f3\u30bf\u30fc +Installer.errorInitJavafx.msg=JavaFX\u521d\u671f\u5316\u30a8\u30e9\u30fc +ServicesMonitor.failedService.notify.title=\u30b5\u30fc\u30d3\u30b9\u304c\u505c\u6b62\u3057\u3066\u3044\u307e\u3059 +ServicesMonitor.failedService.notify.msg={0}\u3078\u306e\u63a5\u7d9a\u304c\u30c0\u30a6\u30f3\u3057\u3066\u3044\u307e\u3059 +ServicesMonitor.restoredService.notify.title=\u30b5\u30fc\u30d3\u30b9\u304c\u7a3c\u50cd\u4e2d\u3067\u3059 +ServicesMonitor.restoredService.notify.msg={0}\u3078\u306e\u63a5\u7d9a\u304c\u5229\u7528\u3067\u304d\u307e\u3059 +ServicesMonitor.statusChange.notify.title=\u30b5\u30fc\u30d3\u30b9\u30b9\u30c6\u30fc\u30bf\u30b9\u30a2\u30c3\u30d7\u30c7\u30fc\u30c8 +ServicesMonitor.statusChange.notify.msg={0}\u306e\u30b9\u30c6\u30fc\u30bf\u30b9\u306f{1} +ServicesMonitor.nullServiceName.excepton.txt=\u30ea\u30af\u30a8\u30b9\u30c8\u3055\u308c\u305f\u30b5\u30fc\u30d3\u30b9\u306f\u30cc\u30eb\u3067\u3059 +ServicesMonitor.unknownServiceName.excepton.txt=\u30ea\u30af\u30a8\u30b9\u30c8\u3055\u308c\u305f\u30b5\u30fc\u30d3\u30b9{0}\u306f\u4e0d\u660e\u3067\u3059 +TextConverter.convert.exception.txt=\u30c6\u30ad\u30b9\u30c8{0}\u3092hex\u30c6\u30ad\u30b9\u30c8\u306b\u5909\u63db\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f +TextConverter.convertFromHex.exception.txt=hex\u30c6\u30ad\u30b9\u30c8\u3092\u30c6\u30ad\u30b9\u30c8\u306b\u5909\u63db\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f +ServicesMonitor.KeywordSearchNull=\u30ad\u30fc\u30ef\u30fc\u30c9\u691c\u7d22\u30b5\u30fc\u30d3\u30b9\u3092\u898b\u3064\u3051\u308c\u307e\u305b\u3093\u3067\u3057\u305f +ServicesMonitor.InvalidPortNumber=\u7121\u52b9\u306a\u30dd\u30fc\u30c8\u756a\u53f7 +ServicesMonitor.remoteCaseDatabase.displayName.text=\u8907\u6570\u306e\u30e6\u30fc\u30b6\u30fc\u306e\u30b1\u30fc\u30b9\u306e\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u30b5\u30fc\u30d3\u30b9 +ServicesMonitor.remoteKeywordSearch.displayName.text=\u8907\u6570\u306e\u30e6\u30fc\u30b6\u30fc\u306e\u30ad\u30fc\u30ef\u30fc\u30c9\u691c\u7d22\u30b5\u30fc\u30d3\u30b9 +ServicesMonitor.messaging.displayName.text=\u30e1\u30c3\u30bb\u30fc\u30b8\u30b5\u30fc\u30d3\u30b9 +ServicesMonitor.databaseConnectionInfo.error.msg=\u30b1\u30fc\u30b9\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306e\u63a5\u7d9a\u60c5\u5831\u3092\u5165\u624b\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f +ServicesMonitor.messagingService.connErr.text=\u30e1\u30c3\u30bb\u30fc\u30b8\u30b5\u30fc\u30d3\u30b9\u306e\u63a5\u7d9a\u60c5\u5831\u3092\u5165\u624b\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f \ No newline at end of file diff --git a/Running_Linux_OSX.txt b/Running_Linux_OSX.txt index 56ea6a5888..bb60569a56 100644 --- a/Running_Linux_OSX.txt +++ b/Running_Linux_OSX.txt @@ -14,21 +14,21 @@ The following need to be done at least once. They do not need to be repeated for 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 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. +-- 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 tap adoptopenjdk/openjdk % brew cask install adoptopenjdk8 - 2. Download a 64 bit Java 8 JavaFX for macOS from https://www.azul.com/downloads/zulu-community - 3. Extract the contents of the JavaFX archive into the folder where the JRE was installed. - e.g. % sudo tar xf ~/Downloads/zulu8.40.0.25-ca-fx-jre8.0.222-macosx_x64.tar.gz -C /Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home--strip-components=1 - 4. Confirm Java 8 is being found by running 'java -version' - 5. Set JAVA_HOME environment variable to location of JRE installation. + 2. Set JAVA_HOME environment variable to location of JRE installation. + e.g. add the following to ~/.bashrc + export JAVA_HOME=$(/usr/libexec/java_home -v 1.8) + 3. Confirm your version of Java by running + % java -version * Install The Sleuth Kit Java Bindings * From 20469bf71979461836391bb4229729b34c265358 Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Tue, 5 Nov 2019 14:55:51 -0500 Subject: [PATCH 115/134] changed field to private --- .../org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java index 3c84c92c26..bd5b39b6b7 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/Waypoint.java @@ -56,7 +56,7 @@ public class Waypoint { * This is a list of attributes that are already being handled by the * by getter functions. */ - static BlackboardAttribute.ATTRIBUTE_TYPE[] ALREADY_HANDLED_ATTRIBUTES = { + static private BlackboardAttribute.ATTRIBUTE_TYPE[] ALREADY_HANDLED_ATTRIBUTES = { BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE, From 4b5447cc4ab8a3bfb44652163be1dfcfe70dc0cd Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Tue, 5 Nov 2019 14:58:13 -0500 Subject: [PATCH 116/134] Fixed bug in KMLReport --- .../autopsy/report/modules/kml/KMLReport.java | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) 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 002215bf04..79fb65ec0c 100644 --- a/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java +++ b/Core/src/org/sleuthkit/autopsy/report/modules/kml/KMLReport.java @@ -730,9 +730,9 @@ class KMLReport implements GeneralReportModule { * * @param route * - * @return + * @return A HTML formatted list of the Route attributes */ - private String getFormattedDetails(Route route) { + private String getFormattedDetails(Route route) { List points = route.getRoute(); StringBuilder result = new StringBuilder(); //NON-NLS @@ -749,13 +749,20 @@ class KMLReport implements GeneralReportModule { Waypoint end = points.get(1); result.append(formatAttribute("Start Latitude", start.getLatitude().toString())) - .append(formatAttribute("Start Longitude", start.getLongitude().toString())) - .append(formatAttribute("End Latitude", end.getLatitude().toString())) + .append(formatAttribute("Start Longitude", start.getLongitude().toString())); + + Double altitude = start.getAltitude(); + if(altitude != null) { + result.append(formatAttribute("Start Altitude", altitude.toString())); + } + + result.append(formatAttribute("End Latitude", end.getLatitude().toString())) .append(formatAttribute("End Longitude", end.getLongitude().toString())); - } - - if (route.getAltitude() != null) { - result.append(formatAttribute("Altitude", route.getAltitude().toString())); + + altitude = end.getAltitude(); + if(altitude != null) { + result.append(formatAttribute("End Altitude", altitude.toString())); + } } List list = route.getOtherProperties(); From 5293d319a031c4f61a3b1c3350ff68e2b7863b4e Mon Sep 17 00:00:00 2001 From: Eammon Date: Tue, 5 Nov 2019 16:21:58 -0500 Subject: [PATCH 117/134] Remove extra space which caused travis to ignore commits. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 575fa3f9dc..dd6b50f34a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ sudo: required jobs: include: - os: linux - dist: bionic + dist: bionic - os: osx env: From 6e1bc93225a0826fef56b0922b6bf14e7babfd5a Mon Sep 17 00:00:00 2001 From: Eammon Date: Tue, 5 Nov 2019 16:45:03 -0500 Subject: [PATCH 118/134] Set execute permission on travis_build.sh --- travis_build.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 travis_build.sh diff --git a/travis_build.sh b/travis_build.sh old mode 100644 new mode 100755 From 80ae1aa934be0b02dab10fd53de6dad4bfdf66eb Mon Sep 17 00:00:00 2001 From: Eammon Date: Tue, 5 Nov 2019 17:41:45 -0500 Subject: [PATCH 119/134] configure not found. --- travis_build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/travis_build.sh b/travis_build.sh index d25d52d6ae..f7b7cfb72d 100755 --- a/travis_build.sh +++ b/travis_build.sh @@ -3,7 +3,7 @@ set -e echo "Building TSK..." cd sleuthkit/sleuthkit -./bootstrap && configure --prefix=/usr && make +./bootstrap && ./configure --prefix=/usr && make pushd bindings/java && ant -q dist-PostgreSQL && popd echo "Building Autopsy..." && echo -en 'travis_fold:start:script.build\\r' From ff3fcde45206cdc088a2ab924c3faeda79e03dc3 Mon Sep 17 00:00:00 2001 From: Mark McKinnon Date: Thu, 7 Nov 2019 13:37:40 -0500 Subject: [PATCH 120/134] Add summary panel components and country code Added summary panel components and country code. --- Core/ivy.xml | 2 +- Core/nbproject/project.properties | 3 +- Core/nbproject/project.xml | 4 + .../relationships/Bundle.properties | 1 + .../relationships/Bundle.properties-MERGED | 1 + .../relationships/SummaryViewer.form | 355 +++++++++--------- .../relationships/SummaryViewer.java | 84 +++-- .../autopsy/coreutils/PhoneNumUtil.java | 106 ++++++ 8 files changed, 357 insertions(+), 199 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/coreutils/PhoneNumUtil.java diff --git a/Core/ivy.xml b/Core/ivy.xml index 699c3e8237..2958230052 100644 --- a/Core/ivy.xml +++ b/Core/ivy.xml @@ -44,7 +44,7 @@ - + diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties index 736428abc2..aa5e50279c 100644 --- a/Core/nbproject/project.properties +++ b/Core/nbproject/project.properties @@ -50,8 +50,9 @@ file.reference.jsoup-1.11.3.jar=release\\modules\\ext\\jsoup-1.11.3.jar file.reference.jul-to-slf4j-1.7.25.jar=release\\modules\\ext\\jul-to-slf4j-1.7.25.jar file.reference.juniversalchardet-1.0.3.jar=release\\modules\\ext\\juniversalchardet-1.0.3.jar file.reference.junrar-2.0.0.jar=release\\modules\\ext\\junrar-2.0.0.jar -file.reference.jxmapviewer2-2.4.jar=release\\modules\\ext\\jxmapviewer2-2.4.jar +file.reference.jxmapviewer2-2.4.jar=release/modules/ext/jxmapviewer2-2.4.jar file.reference.jython-standalone-2.7.0.jar=release/modules/ext/jython-standalone-2.7.0.jar +file.reference.libphonenumber-3.5.jar=release/modules/ext/libphonenumber-3.5.jar file.reference.mchange-commons-java-0.2.9.jar=release/modules/ext/mchange-commons-java-0.2.9.jar file.reference.metadata-extractor-2.11.0.jar=release\\modules\\ext\\metadata-extractor-2.11.0.jar file.reference.netcdf4-4.5.5.jar=release\\modules\\ext\\netcdf4-4.5.5.jar diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index ce74b2e5a3..48063c8566 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -405,6 +405,10 @@ ext/okhttp-2.7.5.jar release/modules/ext/okhttp-2.7.5.jar
    + + ext/libphonenumber-3.5.jar + release/modules/ext/libphonenumber-3.5.jar + ext/tika-core-1.20.jar release\modules\ext\tika-core-1.20.jar diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties b/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties index 01d5316454..b14d8a2688 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties @@ -26,3 +26,4 @@ SummaryViewer.attachmentsLabel.text=Total Attachments: SummaryViewer.referencesLabel.text=Communication References: SummaryViewer.referencesDataLabel.text= SummaryViewer.contactsLabel.text=Book Entries: +SummaryViewer.accountCountry.text= diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED index 3d6fbef0cb..869c95699b 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED @@ -71,3 +71,4 @@ SummaryViewer.attachmentsLabel.text=Total Attachments: SummaryViewer.referencesLabel.text=Communication References: SummaryViewer.referencesDataLabel.text= SummaryViewer.contactsLabel.text=Book Entries: +SummaryViewer.accountCountry.text= diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.form b/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.form index 1887560589..aaea0ec45a 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.form +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.form @@ -1,154 +1,6 @@
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -164,38 +16,169 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -208,7 +191,7 @@ - + @@ -270,5 +253,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.java index 043b229859..6fdc1b2084 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.java @@ -29,6 +29,7 @@ import org.openide.util.Lookup; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb; import org.sleuthkit.datamodel.Account; +import org.sleuthkit.autopsy.coreutils.PhoneNumUtil; /** * Account Summary View Panel. This panel shows a list of various counts related @@ -110,7 +111,16 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi Account[] accountArray = info.getAccounts().toArray(new Account[1]); Account account = accountArray[0]; - accountLabel.setText(account.getTypeSpecificID()); + if (account.getAccountType().getTypeName().contains("PHONE")) { + String countryCode = PhoneNumUtil.getCountryCode(account.getTypeSpecificID()); + accountLabel.setText(PhoneNumUtil.convertToInternational(account.getTypeSpecificID())); + accountCountry.setText("Country: " + countryCode); //NON-NLS + accountCountry.setEnabled(true); + } else { + accountLabel.setText(account.getTypeSpecificID()); + accountCountry.setText(""); + accountCountry.setEnabled(false); + } if (account.getAccountType().equals(Account.Type.DEVICE)) { accoutDescriptionLabel.setText(Bundle.SummaryViewer_Account_Description()); @@ -173,6 +183,7 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi accountLabel.setText(""); accoutDescriptionLabel.setText(""); referencesDataLabel.setText(""); + accountCountry.setText(""); fileReferencesPanel.setNode(new AbstractNode(Children.LEAF)); caseReferencesPanel.setNode(new AbstractNode(Children.LEAF)); @@ -210,6 +221,7 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi summaryPanel = new javax.swing.JPanel(); accountLabel = new javax.swing.JLabel(); + accountCountry = new javax.swing.JLabel(); accoutDescriptionLabel = new javax.swing.JLabel(); countsPanel = new javax.swing.JPanel(); messagesLabel = new javax.swing.JLabel(); @@ -220,13 +232,15 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi callLogsDataLabel = new javax.swing.JLabel(); attachmentsLabel = new javax.swing.JLabel(); attachmentDataLabel = new javax.swing.JLabel(); - fileReferencesPanel = new org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel(); - caseReferencesPanel = new org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel(); contanctsPanel = new javax.swing.JPanel(); contactsLabel = new javax.swing.JLabel(); contactsDataLabel = new javax.swing.JLabel(); referencesLabel = new javax.swing.JLabel(); referencesDataLabel = new javax.swing.JLabel(); + fileReferencesPanel = new org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel(); + caseReferencesPanel = new org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel(); + + setLayout(new java.awt.GridBagLayout()); summaryPanel.setLayout(new java.awt.GridBagLayout()); @@ -238,15 +252,27 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi gridBagConstraints.insets = new java.awt.Insets(15, 9, 0, 9); summaryPanel.add(accountLabel, gridBagConstraints); - org.openide.awt.Mnemonics.setLocalizedText(accoutDescriptionLabel, org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.accoutDescriptionLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(accountCountry, org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.accountCountry.text")); // NOI18N gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; gridBagConstraints.gridy = 1; gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(0, 9, 0, 9); + summaryPanel.add(accountCountry, gridBagConstraints); + + org.openide.awt.Mnemonics.setLocalizedText(accoutDescriptionLabel, org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.accoutDescriptionLabel.text")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 2; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; gridBagConstraints.weightx = 1.0; gridBagConstraints.insets = new java.awt.Insets(15, 9, 15, 9); summaryPanel.add(accoutDescriptionLabel, gridBagConstraints); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + add(summaryPanel, gridBagConstraints); + countsPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.countsPanel.border.title"))); // NOI18N countsPanel.setLayout(new java.awt.GridBagLayout()); @@ -315,31 +341,12 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi gridBagConstraints.insets = new java.awt.Insets(0, 0, 9, 15); countsPanel.add(attachmentDataLabel, gridBagConstraints); - summaryPanel.add(countsPanel, new java.awt.GridBagConstraints()); - - setLayout(new java.awt.GridBagLayout()); - - fileReferencesPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.fileReferencesPanel.border.title"))); // NOI18N gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 3; + gridBagConstraints.gridy = 1; gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; - gridBagConstraints.weightx = 1.0; - gridBagConstraints.weighty = 1.0; - gridBagConstraints.insets = new java.awt.Insets(9, 0, 0, 0); - add(fileReferencesPanel, gridBagConstraints); - - caseReferencesPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.caseReferencesPanel.border.title"))); // NOI18N - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 4; - gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; - gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; - gridBagConstraints.weightx = 1.0; - gridBagConstraints.weighty = 1.0; - gridBagConstraints.insets = new java.awt.Insets(9, 0, 0, 0); - add(caseReferencesPanel, gridBagConstraints); + add(countsPanel, gridBagConstraints); contanctsPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.contanctsPanel.border.title"))); // NOI18N contanctsPanel.setLayout(new java.awt.GridBagLayout()); @@ -381,14 +388,37 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 1; - gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.gridy = 2; + gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; add(contanctsPanel, gridBagConstraints); + + fileReferencesPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.fileReferencesPanel.border.title"))); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 3; + gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.weighty = 1.0; + gridBagConstraints.insets = new java.awt.Insets(9, 0, 0, 0); + add(fileReferencesPanel, gridBagConstraints); + + caseReferencesPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.caseReferencesPanel.border.title"))); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 4; + gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.weighty = 1.0; + gridBagConstraints.insets = new java.awt.Insets(9, 0, 0, 0); + add(caseReferencesPanel, gridBagConstraints); }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JLabel accountCountry; private javax.swing.JLabel accountLabel; private javax.swing.JLabel accoutDescriptionLabel; private javax.swing.JLabel attachmentDataLabel; diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/PhoneNumUtil.java b/Core/src/org/sleuthkit/autopsy/coreutils/PhoneNumUtil.java new file mode 100644 index 0000000000..a94a31e8ca --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/coreutils/PhoneNumUtil.java @@ -0,0 +1,106 @@ +/* + * 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.coreutils; + +import com.google.i18n.phonenumbers.NumberParseException; +import com.google.i18n.phonenumbers.PhoneNumberUtil; +import com.google.i18n.phonenumbers.Phonenumber; +import java.util.logging.Level; + +/** + * + * Class to format and get information from a phone number + */ +public class PhoneNumUtil { + + private static final Logger logger = Logger.getLogger(PhoneNumUtil.class.getName()); + + /** + * Get the country code from a phone number + * + * @param phoneNumber + * @return country code if can determine otherwise it will return "" + */ + public static String getCountryCode(String phoneNumber) { + String regionCode = null; + try { + PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance(); + Phonenumber.PhoneNumber phoneNum = phoneNumberUtil.parse(phoneNumber, ""); + regionCode = phoneNumberUtil.getRegionCodeForNumber(phoneNum); + + if (regionCode == null) { + return ""; + } else { + return regionCode; + } + } catch (NumberParseException ex) { + logger.log(Level.WARNING, "Error getting country code, for phone number: {0}", phoneNumber); + return ""; + } + } + + /** + * Convert a phone number to the E164 format + * + * @param phoneNumber + * + * @return formated phone number if successful or original phone number if + * unsuccessful + */ + public static String convertToE164(String phoneNumber) { + PhoneNumberUtil phone_util = PhoneNumberUtil.getInstance(); + try { + Phonenumber.PhoneNumber phoneProto = phone_util.parse(phoneNumber, getCountryCode(phoneNumber)); + if (phone_util.isValidNumber(phoneProto)) { + return phone_util.format(phoneProto, PhoneNumberUtil.PhoneNumberFormat.E164); + } else { + logger.log(Level.WARNING, "Invalid phone number: {0}", phoneNumber); + return phoneNumber; + } + } catch (NumberParseException e) { + logger.log(Level.WARNING, "Error parsing phone number: {0}", phoneNumber); + return phoneNumber; + } + } + + /** + * Convert a phone number to the International format + * + * @param phoneNumber + * + * @return formated phone number if successful or original phone number if + * unsuccessful + */ + public static String convertToInternational(String phoneNumber) { + PhoneNumberUtil phone_util = PhoneNumberUtil.getInstance(); + try { + Phonenumber.PhoneNumber phoneProto = phone_util.parse(phoneNumber, getCountryCode(phoneNumber)); + if (phone_util.isValidNumber(phoneProto)) { + return phone_util.format(phoneProto, PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL); + } else { + logger.log(Level.WARNING, "Invalid phone number: {0}", phoneNumber); + return phoneNumber; + } + } catch (NumberParseException e) { + logger.log(Level.WARNING, "Error parsing phone number: {0}", phoneNumber); + return phoneNumber; + } + } + +} From 79f048414cd86b6dad7cbd1af586e2132242ee2c Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Thu, 7 Nov 2019 13:46:06 -0500 Subject: [PATCH 121/134] Implemented and tested XRY folder and XRY file checking --- .../xry/XRYFileReader.java | 163 ++++++++++++++++++ .../datasourceprocessors/xry/XRYFolder.java | 77 +++++++++ .../autopsy/xryparser/XRYReportFile.java | 30 ---- .../autopsy/xryparser/XRYReportFolder.java | 80 --------- .../xryparser/XRYReportFolderTest.java | 64 ------- 5 files changed, 240 insertions(+), 174 deletions(-) create mode 100755 Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java create mode 100755 Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFolder.java delete mode 100755 Core/src/org/sleuthkit/autopsy/xryparser/XRYReportFile.java delete mode 100755 Core/src/org/sleuthkit/autopsy/xryparser/XRYReportFolder.java delete mode 100755 Core/test/unit/src/org/sleuthkit/autopsy/xryparser/XRYReportFolderTest.java diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java new file mode 100755 index 0000000000..67c266688f --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java @@ -0,0 +1,163 @@ +/* + * 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.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.charset.Charset; +import java.nio.charset.MalformedInputException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Optional; +import java.util.logging.Level; +import java.util.stream.Stream; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.apache.commons.io.FilenameUtils; + +/** + * Extracts XRY entities from an XRY file and determines the report type. An + * example of an XRY entity would be: + * + * Calls # 1 + * Call Type: Missed + * Time: 1/2/2019 1:23:45 PM (Device) + * From + * Tel: 12345678 + * + */ +public class XRYFileReader { + + 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 all headers are 5 lines in length. + private static final int HEADER_LENGTH_IN_LINES = 5; + + //Assume all XRY reports have the type on the 3rd line. + private static final int LINE_WITH_REPORT_TYPE = 3; + + //Assume 0xFFFE is the BOM + private static final int[] BOM = {0xFF, 0xFE}; + + /** + * Checks if the Path is an XRY file. In order to be an XRY file, it must + * have a txt extension, a 0xFFFE BOM (for UTF-16LE), and a non-empty report + * type. The encoding is not verified any further than checking the BOM. To + * get the report type, the file is read with a UTF-16LE decoder. If a + * failure directly related to the decoding is encountered, it is logged and + * the file is assumed not to be an XRY file. A direct consequence is that + * there may be false positives. + * + * All other I/O exceptions are propagated up. If the Path represents a + * symbolic link, this function will not follow it. + * + * @param file Path to test. It is assumed that the caller has read access + * to the file. + * @return Indicates whether the Path is a XRY file. + * + * @throws IOException if an I/O error occurs + */ + public static boolean isXRYFile(Path file) throws IOException { + String parsedExtension = FilenameUtils.getExtension(file.toString()); + + //A XRY file should have a txt extension. + if (!EXTENSION.equals(parsedExtension)) { + return false; + } + + BasicFileAttributes attr = Files.readAttributes(file, + BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS); + + //Do not follow symbolic links. XRY files most definitely cannot be a + //directory. + if (attr.isSymbolicLink() || attr.isDirectory()) { + return false; + } + + //Check 0xFFFE BOM + if (!isXRYBOM(file)) { + return false; + } + + try { + Optional reportType = getType(file); + //All valid XRY reports should have a type. + return reportType.isPresent() && !reportType.get().isEmpty(); + } catch (MalformedInputException ex) { + logger.log(Level.WARNING, String.format("File at path [%s] had " + + "0xFFFE BOM but was not encoded in UTF-16LE.", file.toString()), ex); + return false; + } + } + + /** + * Checks the leading bytes of the Path to verify they match the expected + * 0xFFFE BOM. + * + * @param file Path to check. It is assumed that the caller has read access + * to the file. + * + * @return Indication if the leading bytes match. + * @throws IOException if an I/O error occurs. + */ + static boolean isXRYBOM(Path file) throws IOException { + try (InputStream in = Files.newInputStream(file, StandardOpenOption.READ)) { + for (int bomByte : BOM) { + if (in.read() != bomByte) { + return false; + } + } + } + + return true; + } + + /** + * Reads the report type from the Path. It is assumed that the Path will + * have a UTF-16LE encoding. A MalformedInputException will be thrown if + * there is a decoding error. + * + * @param file + * @return + * @throws IOException + */ + static Optional getType(Path file) throws IOException { + try (BufferedReader reader = Files.newBufferedReader(file, CHARSET); + //Limit this stream to only the length of the header + //and skip to the line just before the type information. + Stream xryReportHeader = reader.lines() + .limit(HEADER_LENGTH_IN_LINES) + .skip(LINE_WITH_REPORT_TYPE - 1)) { + return xryReportHeader.findFirst(); + } catch (UncheckedIOException ex) { + throw ex.getCause(); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFolder.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFolder.java new file mode 100755 index 0000000000..7abccfad51 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFolder.java @@ -0,0 +1,77 @@ +/* + * 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.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.stream.Stream; + +/** + * Extracts XRY files and (optionally) non-XRY files from a XRY Report folder. + */ +public class XRYFolder { + + //Depth that will contain XRY files. All XRY files will be immediate + //children of their parent folder. + private static final int XRY_FILES_DEPTH = 1; + + /** + * 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. + * + * This function will not follow any symbolic links, the directory is tested + * as is. + * + * @param folder Path to test. Assumes that caller has read access to the + * folder and all of the top level files. + * @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 + * the files. + */ + public static boolean isXRYFolder(Path folder) throws IOException { + BasicFileAttributes attr = Files.readAttributes(folder, + BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS); + + if (!attr.isDirectory()) { + return false; + } + + //Files.walk by default will not follow symbolic links. + try (Stream allFiles = Files.walk(folder, XRY_FILES_DEPTH)) { + Iterator allFilesIterator = allFiles.iterator(); + while (allFilesIterator.hasNext()) { + Path currentFile = allFilesIterator.next(); + if (XRYFileReader.isXRYFile(currentFile)) { + return true; + } + } + + return false; + } catch (UncheckedIOException ex) { + throw ex.getCause(); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/xryparser/XRYReportFile.java b/Core/src/org/sleuthkit/autopsy/xryparser/XRYReportFile.java deleted file mode 100755 index bd1fa14ad0..0000000000 --- a/Core/src/org/sleuthkit/autopsy/xryparser/XRYReportFile.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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.xryparser; - -import java.nio.file.Path; -import org.apache.commons.io.FilenameUtils; - -/** - * - * @author dsmyda - */ -public class XRYReportFile { - - //It is assumed XRY files have a txt extension. - private static final String EXTENSION = "txt"; - - public static boolean isXRYReportFile(Path file) { - String parsedExtension = FilenameUtils.getExtension(file.toString()); - if(!EXTENSION.equals(parsedExtension)) { - return false; - } - - - - return true; - } -} diff --git a/Core/src/org/sleuthkit/autopsy/xryparser/XRYReportFolder.java b/Core/src/org/sleuthkit/autopsy/xryparser/XRYReportFolder.java deleted file mode 100755 index 004396a406..0000000000 --- a/Core/src/org/sleuthkit/autopsy/xryparser/XRYReportFolder.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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.xryparser; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.List; -import java.util.Optional; -import java.util.stream.Stream; - -/** - * - */ -public class XRYReportFolder { - - //Depth to the first potential XRY file. Root children are at 1. We expect - //XRY files to be contained in a subfolder of the root, so 2. - private static final int XRY_FILES_DEPTH = 2; - - public XRYReportFolder(Path reportFolder) { - - } - - public List getXRYReportFiles() { - throw new UnsupportedOperationException(); - } - - public List getOtherFiles() { - throw new UnsupportedOperationException(); - } - - /** - * Searches all immediate subdirectories looking for XRY files. If any file - * matches, the folder is assumed to be an XRY Report folder. - * - * @param folder Folder to test. Assumes that caller has read access to the - * folder and all of its immediate sub folders. - * @return Flag indicating a valid XRY report folder. - * - * @throws IOException Error occurred during File I/O. - * @throws SecurityException If the security manager denies access to the - * starting folder. - */ - public static boolean isXRYReportFolder(Path folder) throws IOException { - BasicFileAttributes folderAttributes = Files.readAttributes(folder, BasicFileAttributes.class); - if (!folderAttributes.isDirectory()) { - return false; - } - - try (Stream folderChildren = Files.walk(folder, XRY_FILES_DEPTH)) { - Optional xryFile = folderChildren - //Filter out all directories. - .filter(path -> { - try { - BasicFileAttributes fileAttributes = - Files.readAttributes(path, BasicFileAttributes.class); - return !fileAttributes.isDirectory(); - } catch (IOException ex) { - throw new UncheckedIOException(ex); - } - }) - //Filter out all files that are not in the right depth. - .filter(path -> folder.relativize(path).getNameCount() == XRY_FILES_DEPTH) - //Filter out all files that don't pass the report test. - .filter(filePath -> XRYReportFile.isXRYReportFile(filePath)) - //For debugging. - .peek(path -> System.out.println(path)) - .findAny(); - return xryFile.isPresent(); - } catch (UncheckedIOException ex) { - throw ex.getCause(); - } - } -} diff --git a/Core/test/unit/src/org/sleuthkit/autopsy/xryparser/XRYReportFolderTest.java b/Core/test/unit/src/org/sleuthkit/autopsy/xryparser/XRYReportFolderTest.java deleted file mode 100755 index 2e980fd15e..0000000000 --- a/Core/test/unit/src/org/sleuthkit/autopsy/xryparser/XRYReportFolderTest.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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.xryparser; - -import java.nio.file.Path; -import java.nio.file.Paths; -import org.junit.Test; -import static org.junit.Assert.*; - -/** - * - * @author dsmyda - */ -public class XRYReportFolderTest { - - private final Path reportDirectory = Paths.get("C:", "Users", "dsmyda", "Downloads", "2019-10-23-XRYSamples"); - private final Path notAReportDirectory = Paths.get("C:", "Users", "dsmyda", "Downloads", "Not-2019-10-23-XRYSamples"); - private final Path biggerReportDirectory = Paths.get("C:", "Users", "dsmyda", "Documents", "personal"); - - public XRYReportFolderTest() { - } - - /** - * Test of getXRYReportFiles method, of class XRYReportFolder. - */ -// @Test -// public void testGetXRYReportFiles() { -// System.out.println("getXRYReportFiles"); -// XRYReportFolder instance = null; -// List expResult = null; -// List result = instance.getXRYReportFiles(); -// assertEquals(expResult, result); -// // TODO review the generated test code and remove the default call to fail. -// fail("The test case is a prototype."); -// } - - /** - * Test of getOtherFiles method, of class XRYReportFolder. - */ -// @Test -// public void testGetOtherFiles() { -// System.out.println("getOtherFiles"); -// XRYReportFolder instance = null; -// List expResult = null; -// List result = instance.getOtherFiles(); -// assertEquals(expResult, result); -// // TODO review the generated test code and remove the default call to fail. -// fail("The test case is a prototype."); -// } - - /** - * Test of isXRYReportFolder method, of class XRYReportFolder. - */ - @Test - public void testIsXRYReportFolder() throws Exception { - assertTrue("Not flagged as an xry folder, but should be", XRYReportFolder.isXRYReportFolder(reportDirectory)); - assertFalse("Flagged as an xry folder, but shouldn't be", XRYReportFolder.isXRYReportFolder(notAReportDirectory)); - assertTrue("Not flagged as an xry folder, but should be", XRYReportFolder.isXRYReportFolder(biggerReportDirectory)); - } - -} From eb34a74b04d844caa4fe529fa2f1faa675cc46bd Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Thu, 7 Nov 2019 13:49:18 -0500 Subject: [PATCH 122/134] Fixed comment --- .../autopsy/datasourceprocessors/xry/XRYFileReader.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java index 67c266688f..4d56c66512 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java @@ -95,8 +95,7 @@ public class XRYFileReader { BasicFileAttributes attr = Files.readAttributes(file, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS); - //Do not follow symbolic links. XRY files most definitely cannot be a - //directory. + //Do not follow symbolic links. XRY files cannot be a directory. if (attr.isSymbolicLink() || attr.isDirectory()) { return false; } From 5c5514f0c1073e09955a6f7d079628acb6d61175 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Thu, 7 Nov 2019 14:42:16 -0500 Subject: [PATCH 123/134] Fixed up comments and changed scope of some methods to private --- .../autopsy/datasourceprocessors/xry/XRYFileReader.java | 6 +++--- .../autopsy/datasourceprocessors/xry/XRYFolder.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java index 4d56c66512..142df64440 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java @@ -37,7 +37,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.apache.commons.io.FilenameUtils; /** - * Extracts XRY entities from an XRY file and determines the report type. An + * Extracts XRY entities and determines the report type. An * example of an XRY entity would be: * * Calls # 1 @@ -126,7 +126,7 @@ public class XRYFileReader { * @return Indication if the leading bytes match. * @throws IOException if an I/O error occurs. */ - static boolean isXRYBOM(Path file) throws IOException { + private static boolean isXRYBOM(Path file) throws IOException { try (InputStream in = Files.newInputStream(file, StandardOpenOption.READ)) { for (int bomByte : BOM) { if (in.read() != bomByte) { @@ -147,7 +147,7 @@ public class XRYFileReader { * @return * @throws IOException */ - static Optional getType(Path file) throws IOException { + private static Optional getType(Path file) throws IOException { try (BufferedReader reader = Files.newBufferedReader(file, CHARSET); //Limit this stream to only the length of the header //and skip to the line just before the type information. diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFolder.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFolder.java index 7abccfad51..d72c631e69 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFolder.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFolder.java @@ -28,7 +28,7 @@ import java.util.Iterator; import java.util.stream.Stream; /** - * Extracts XRY files and (optionally) non-XRY files from a XRY Report folder. + * Extracts XRY files and (optionally) non-XRY files from a XRY (Report) folder. */ public class XRYFolder { From ee3c0c01d2fe89f4f4b19a0ec24d72ba015769a1 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Thu, 7 Nov 2019 16:07:35 -0500 Subject: [PATCH 124/134] Implemented report reading, now testing --- .../xry/XRYFileReader.java | 133 ++++++++++++++++++ .../autopsy/xryparser/XRYRecordParser.java | 34 ----- .../autopsy/xryparser/XRYReport.java | 131 ----------------- .../autopsy/xryparser/XRYReportExtractor.java | 105 -------------- .../xry/XRYFileReaderTest.java} | 0 .../autopsy/xryparser/XRYReportTest.java | 92 ------------ 6 files changed, 133 insertions(+), 362 deletions(-) create mode 100755 Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java delete mode 100755 Core/src/org/sleuthkit/autopsy/xryparser/XRYRecordParser.java delete mode 100755 Core/src/org/sleuthkit/autopsy/xryparser/XRYReport.java delete mode 100755 Core/src/org/sleuthkit/autopsy/xryparser/XRYReportExtractor.java rename Core/test/unit/src/org/sleuthkit/autopsy/{xryparser/XRYReportExtractorTest.java => datasourceprocessors/xry/XRYFileReaderTest.java} (100%) delete mode 100755 Core/test/unit/src/org/sleuthkit/autopsy/xryparser/XRYReportTest.java diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java new file mode 100755 index 0000000000..c77483a62b --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java @@ -0,0 +1,133 @@ +/* + * 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.BufferedReader; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.NoSuchElementException; + +/** + * Extracts XRY entities and determines the report type. An + * example of an XRY entity would be: + * + * Calls # 1 + * Call Type: Missed + * Time: 1/2/2019 1:23:45 PM (Device) + * From + * Tel: 12345678 + * + */ +public final class XRYFileReader { + + ///Assume UTF_16LE + private static final Charset CHARSET = StandardCharsets.UTF_16LE; + + //Assume all headers are 5 lines in length. + private static final int HEADER_LENGTH_IN_LINES = 5; + + private final BufferedReader reader; + + private final StringBuilder xryEntity; + + /** + * Creates an XRYFileReader. As part of construction, the file handles are + * opened and reader is advanced passed all header lines of the XRY file. + * + * The file is assumed to be encoded in UTF-16LE. + * + * @param xryFile XRY file to read. It is assumed that the caller has read + * access to the path. + * @throws IOException if an I/O error occurs. + */ + public XRYFileReader(Path xryFile) throws IOException { + reader = Files.newBufferedReader(xryFile, CHARSET); + + //Advance reader to start of the first XRY entity. + for(int i = 0; i < HEADER_LENGTH_IN_LINES; i++) { + reader.readLine(); + } + + xryEntity = new StringBuilder(); + } + + /** + * + * @return + * @throws IOException + */ + public boolean hasNextEntity() throws IOException { + //Entity has yet to be consumed. + if (xryEntity.length() > 0) { + return true; + } + + String line; + while ((line = reader.readLine()) != null) { + if (marksEndOfEntity(line)) { + if (xryEntity.length() > 0) { + //Found a non empty XRY entity. + return true; + } + } else { + xryEntity.append(line).append("\n"); + } + } + + //Check if EOF was hit before an entity delimiter was found. + return xryEntity.length() > 0; + } + + /** + * + * @return + * @throws IOException + */ + public String nextEntity() throws IOException { + if (hasNextEntity()) { + String returnVal = xryEntity.toString(); + xryEntity.setLength(0); + return returnVal; + } else { + throw new NoSuchElementException(); + } + } + + /** + * + * @throws IOException + */ + public void close() throws IOException { + reader.close(); + } + + /** + * Determines if the line encountered during file reading signifies the end + * of an XRY entity. + * + * @param line + * @return + */ + private boolean marksEndOfEntity(String line) { + return line.isEmpty(); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/xryparser/XRYRecordParser.java b/Core/src/org/sleuthkit/autopsy/xryparser/XRYRecordParser.java deleted file mode 100755 index da1d79ce66..0000000000 --- a/Core/src/org/sleuthkit/autopsy/xryparser/XRYRecordParser.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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.xryparser; - -import org.sleuthkit.datamodel.BlackboardArtifact; - -/** - * - */ -public interface XRYRecordParser { - - /** - * - * @param xryRecord - * @return - */ - public BlackboardArtifact makeArtifact(String xryRecord); -} diff --git a/Core/src/org/sleuthkit/autopsy/xryparser/XRYReport.java b/Core/src/org/sleuthkit/autopsy/xryparser/XRYReport.java deleted file mode 100755 index e38586e15a..0000000000 --- a/Core/src/org/sleuthkit/autopsy/xryparser/XRYReport.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * 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.xryparser; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Optional; -import java.util.stream.Stream; - -/** - * - */ -public class XRYReport { - - //Number of lines that make up the header of the report file. - private final static int LINES_IN_HEADER = 5; - - //Header line number that corresponds to the report type. - private final static int LINE_WITH_REPORT_TYPE = 3; - - //Encoding of the XRY Report file. - private final static Charset REPORT_ENCODING = StandardCharsets.UTF_16LE; - - //Path to the physical report file - private final Path reportPath; - - //XRY Report type (Calls, Messages, etc) - private final String reportType; - - /** - * - * @param reportPath - * @throws IOException - */ - public XRYReport(Path reportPath) throws IOException { - this.reportPath = reportPath; - this.reportType = parseType(reportPath); - } - - /** - * - * @return - */ - public String getType() { - return reportType; - } - - /** - * - * @return - */ - public Path getPath() { - return reportPath; - } - - /** - * - * @return - */ - public Charset getEncoding() { - return REPORT_ENCODING; - } - - /** - * - * @return - */ - public int getTotalLinesInHeader() { - return LINES_IN_HEADER; - } - - /** - * - * @return - */ - public int getReportTypeLineNumber() { - return LINE_WITH_REPORT_TYPE; - } - - /** - * - * @param report - * @return - * @throws IOException - */ - private String parseType(Path report) throws IOException { - try { - BufferedReader reader = Files.newBufferedReader(report, this.getEncoding()); - - //Limit this stream to only the length of the header - //and skip to the line just before the type information. - Stream xryReportHeader = reader.lines() - .limit(this.getTotalLinesInHeader()) - .skip(this.getReportTypeLineNumber() - 1); - - Optional type = xryReportHeader.findFirst(); - if(!type.isPresent()) { - throw new IllegalArgumentException("Report did not have a type."); - } - - if(type.get().isEmpty()) { - throw new IllegalArgumentException("Report did not have a type."); - } - - return type.get(); - } catch (UncheckedIOException ex) { - throw ex.getCause(); - } - } -} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/xryparser/XRYReportExtractor.java b/Core/src/org/sleuthkit/autopsy/xryparser/XRYReportExtractor.java deleted file mode 100755 index 9dd8c90de4..0000000000 --- a/Core/src/org/sleuthkit/autopsy/xryparser/XRYReportExtractor.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * 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.xryparser; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.Files; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Stream; -import org.sleuthkit.datamodel.BlackboardArtifact; - -/** - * This class is responsible for extracting XRY records from a specified - * XRYReport. XRY records are the numbered, blank line separated 'groups' in an - * XRY report. - * - * Example: - * - * Calls # 1 - * Call Type: Missed - * Time: 1/2/2019 1:23:45 PM (Device) - * From - * Tel: 12345678 - * - */ -public final class XRYReportExtractor { - - private final XRYReport xryReport; - - /** - * Creates an XRYReportExtractor. - * - * @param report Report to be extracted. - */ - public XRYReportExtractor(XRYReport report) { - this.xryReport = report; - } - - /** - * - * @param parser - * @throws IOException - */ - public List extract(XRYRecordParser parser) throws IOException { - try { - BufferedReader reader = Files.newBufferedReader(xryReport.getPath(), xryReport.getEncoding()); - - //Get a stream of all lines in the file. Skip the first n header lines. - Stream xryReportStream = reader.lines().skip(xryReport.getTotalLinesInHeader()); - - StringBuilder xryRecord = new StringBuilder(); - List artifacts = new ArrayList<>(); - xryReportStream.forEach((line) -> { - if (this.isEndOfXRYRecord(line)) { - //Pass only non empty XRY records to the parser. - if (xryRecord.length() > 0) { - artifacts.add(parser.makeArtifact(xryRecord.toString())); - xryRecord.setLength(0); - } - } else { - xryRecord.append(line).append("\n"); - } - }); - - //The file may have ended without a blank line (which is used to delimit - //records). The last XRY record would not have been processed. - if (xryRecord.length() > 0) { - artifacts.add(parser.makeArtifact(xryRecord.toString())); - } - - return artifacts; - } catch (UncheckedIOException ex) { - throw ex.getCause(); - } - } - - /** - * Determines if the line encountered during file reading signifies the end - * of an XRYRecord. - * - * @param line - * @return - */ - private boolean isEndOfXRYRecord(String line) { - return line.isEmpty(); - } -} diff --git a/Core/test/unit/src/org/sleuthkit/autopsy/xryparser/XRYReportExtractorTest.java b/Core/test/unit/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReaderTest.java similarity index 100% rename from Core/test/unit/src/org/sleuthkit/autopsy/xryparser/XRYReportExtractorTest.java rename to Core/test/unit/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReaderTest.java diff --git a/Core/test/unit/src/org/sleuthkit/autopsy/xryparser/XRYReportTest.java b/Core/test/unit/src/org/sleuthkit/autopsy/xryparser/XRYReportTest.java deleted file mode 100755 index 64391e7564..0000000000 --- a/Core/test/unit/src/org/sleuthkit/autopsy/xryparser/XRYReportTest.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * 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.xryparser; - -import java.util.ArrayList; -import java.io.IOException; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; -import org.junit.Test; -import static org.junit.Assert.*; - -/** - * - */ -public class XRYReportTest { - - private final Path reportDirectory = Paths.get("C:", "Users", "dsmyda", "Downloads", "2019-10-23-XRYSamples", "files"); - - public XRYReportTest() { - } - - @Test - public void testParseType() { - List reportTestFiles = new ArrayList() { - { - add(reportDirectory.resolve("Calls.txt")); - add(reportDirectory.resolve("Messages-SMS.txt")); - add(reportDirectory.resolve("Contacts-Contacts.txt")); - add(reportDirectory.resolve("Web-Bookmarks.txt")); - add(reportDirectory.resolve("Device-General Information.txt")); - } - }; - - List expectedTypes = new ArrayList() { - { - add("Calls"); - add("Messages/SMS"); - add("Contacts/Contacts"); - add("Web/Bookmarks"); - add("Device/General Information"); - } - }; - - List actualTypes = new ArrayList<>(); - - reportTestFiles.forEach((Path reportFile) -> { - try { - XRYReport reportObj = new MockXRYReport(reportFile); - actualTypes.add(reportObj.getType()); - } catch (IOException ex) { - fail(ex.getMessage()); - } - }); - - assertArrayEquals("Types did not match.", expectedTypes.toArray(), actualTypes.toArray()); - } - - /** - * Mock the valid XRY Report encoding to UTF-8 so that the test files can be - * run unmodified (they are currently UTF-8). - */ - private class MockXRYReport extends XRYReport { - - public MockXRYReport(Path reportPath) throws IOException { - super(reportPath); - } - - @Override - public Charset getEncoding() { - return StandardCharsets.UTF_8; - } - } -} From f254b9ddb065578d70a6133d191989514f0e71e1 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Thu, 7 Nov 2019 16:36:53 -0500 Subject: [PATCH 125/134] Reduce complexity of XRYFileReader --- .../xry/XRYFileReader.java | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java index 142df64440..b1db23dad6 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java @@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.datasourceprocessors.xry; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; -import java.io.UncheckedIOException; import java.nio.charset.Charset; import java.nio.charset.MalformedInputException; import java.nio.charset.StandardCharsets; @@ -32,7 +31,6 @@ import java.nio.file.StandardOpenOption; import java.nio.file.attribute.BasicFileAttributes; import java.util.Optional; import java.util.logging.Level; -import java.util.stream.Stream; import org.sleuthkit.autopsy.coreutils.Logger; import org.apache.commons.io.FilenameUtils; @@ -57,9 +55,6 @@ public class XRYFileReader { //Assume TXT extension private static final String EXTENSION = "txt"; - //Assume all headers are 5 lines in length. - private static final int HEADER_LENGTH_IN_LINES = 5; - //Assume all XRY reports have the type on the 3rd line. private static final int LINE_WITH_REPORT_TYPE = 3; @@ -108,7 +103,7 @@ public class XRYFileReader { try { Optional reportType = getType(file); //All valid XRY reports should have a type. - return reportType.isPresent() && !reportType.get().isEmpty(); + return reportType.isPresent(); } catch (MalformedInputException ex) { logger.log(Level.WARNING, String.format("File at path [%s] had " + "0xFFFE BOM but was not encoded in UTF-16LE.", file.toString()), ex); @@ -126,7 +121,7 @@ public class XRYFileReader { * @return Indication if the leading bytes match. * @throws IOException if an I/O error occurs. */ - private static boolean isXRYBOM(Path file) throws IOException { + static boolean isXRYBOM(Path file) throws IOException { try (InputStream in = Files.newInputStream(file, StandardOpenOption.READ)) { for (int bomByte : BOM) { if (in.read() != bomByte) { @@ -147,16 +142,18 @@ public class XRYFileReader { * @return * @throws IOException */ - private static Optional getType(Path file) throws IOException { - try (BufferedReader reader = Files.newBufferedReader(file, CHARSET); - //Limit this stream to only the length of the header - //and skip to the line just before the type information. - Stream xryReportHeader = reader.lines() - .limit(HEADER_LENGTH_IN_LINES) - .skip(LINE_WITH_REPORT_TYPE - 1)) { - return xryReportHeader.findFirst(); - } catch (UncheckedIOException ex) { - throw ex.getCause(); + static Optional getType(Path file) throws IOException { + try (BufferedReader reader = Files.newBufferedReader(file, CHARSET)) { + //Advance the reader to the line before the report type. + for(int i = 0; i < LINE_WITH_REPORT_TYPE - 1; i++) { + reader.readLine(); + } + + String reportTypeLine = reader.readLine(); + if(reportTypeLine != null && !reportTypeLine.isEmpty()) { + return Optional.of(reportTypeLine); + } + return Optional.empty(); } } } From a41b28cbf5d81b573c1d638256c03df54ae5dbf9 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Thu, 7 Nov 2019 16:38:08 -0500 Subject: [PATCH 126/134] Revert scope of methods in XRYFileReader back to private --- .../autopsy/datasourceprocessors/xry/XRYFileReader.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java index b1db23dad6..0c441e7d7e 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java @@ -121,7 +121,7 @@ public class XRYFileReader { * @return Indication if the leading bytes match. * @throws IOException if an I/O error occurs. */ - static boolean isXRYBOM(Path file) throws IOException { + private static boolean isXRYBOM(Path file) throws IOException { try (InputStream in = Files.newInputStream(file, StandardOpenOption.READ)) { for (int bomByte : BOM) { if (in.read() != bomByte) { @@ -142,7 +142,7 @@ public class XRYFileReader { * @return * @throws IOException */ - static Optional getType(Path file) throws IOException { + private static Optional getType(Path file) throws IOException { try (BufferedReader reader = Files.newBufferedReader(file, CHARSET)) { //Advance the reader to the line before the report type. for(int i = 0; i < LINE_WITH_REPORT_TYPE - 1; i++) { From 859f1faba90947048cb10c7bdfab8cad1e801e56 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Thu, 7 Nov 2019 17:01:18 -0500 Subject: [PATCH 127/134] Added comments and am in the process of testing --- .../xry/XRYFileReader.java | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java index c77483a62b..d64fe79629 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java @@ -27,17 +27,16 @@ import java.nio.file.Path; import java.util.NoSuchElementException; /** - * Extracts XRY entities and determines the report type. An - * example of an XRY entity would be: + * Extracts XRY entities and determines the report type. An example of an XRY + * entity would be: * * Calls # 1 * Call Type: Missed * Time: 1/2/2019 1:23:45 PM (Device) * From * Tel: 12345678 - * */ -public final class XRYFileReader { +public final class XRYFileReader implements AutoCloseable { ///Assume UTF_16LE private static final Charset CHARSET = StandardCharsets.UTF_16LE; @@ -45,25 +44,26 @@ public final class XRYFileReader { //Assume all headers are 5 lines in length. private static final int HEADER_LENGTH_IN_LINES = 5; + //Underlying reader for the xry file. private final BufferedReader reader; private final StringBuilder xryEntity; /** - * Creates an XRYFileReader. As part of construction, the file handles are + * Creates an XRYFileReader. As part of construction, the file handles are * opened and reader is advanced passed all header lines of the XRY file. - * + * * The file is assumed to be encoded in UTF-16LE. - * + * * @param xryFile XRY file to read. It is assumed that the caller has read * access to the path. * @throws IOException if an I/O error occurs. */ public XRYFileReader(Path xryFile) throws IOException { reader = Files.newBufferedReader(xryFile, CHARSET); - - //Advance reader to start of the first XRY entity. - for(int i = 0; i < HEADER_LENGTH_IN_LINES; i++) { + + //Advance the reader to the start of the first XRY entity. + for (int i = 0; i < HEADER_LENGTH_IN_LINES; i++) { reader.readLine(); } @@ -71,16 +71,18 @@ public final class XRYFileReader { } /** + * Advances the reader until the next valid XRY entity is detected. * - * @return - * @throws IOException + * @return Indication that there is another XRY entity to consume or that + * the file has been exhausted. + * @throws IOException if an I/O error occurs. */ public boolean hasNextEntity() throws IOException { //Entity has yet to be consumed. if (xryEntity.length() > 0) { return true; } - + String line; while ((line = reader.readLine()) != null) { if (marksEndOfEntity(line)) { @@ -98,9 +100,12 @@ public final class XRYFileReader { } /** + * Returns an XRY entity if there is one, otherwise an exception is thrown. * - * @return - * @throws IOException + * @return A non-empty XRY entity. + * @throws IOException if an I/O error occurs. + * @throws NoSuchElementException if there are no more XRY entities to + * consume. */ public String nextEntity() throws IOException { if (hasNextEntity()) { @@ -113,9 +118,11 @@ public final class XRYFileReader { } /** + * Closes any file handles this reader may have open. * * @throws IOException */ + @Override public void close() throws IOException { reader.close(); } From 6a281f274f1f18e20ee2a0a5e1b153e6b851e653 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Thu, 7 Nov 2019 17:02:25 -0500 Subject: [PATCH 128/134] Made both classes final --- .../autopsy/datasourceprocessors/xry/XRYFileReader.java | 2 +- .../sleuthkit/autopsy/datasourceprocessors/xry/XRYFolder.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java index 0c441e7d7e..d552f96481 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java @@ -45,7 +45,7 @@ import org.apache.commons.io.FilenameUtils; * Tel: 12345678 * */ -public class XRYFileReader { +public final class XRYFileReader { private static final Logger logger = Logger.getLogger(XRYFileReader.class.getName()); diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFolder.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFolder.java index d72c631e69..b9b999f270 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 class XRYFolder { +public final class XRYFolder { //Depth that will contain XRY files. All XRY files will be immediate //children of their parent folder. From 56d57f63d17d0b75cd4d4617b9f3096d531c9b41 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Thu, 7 Nov 2019 17:29:24 -0500 Subject: [PATCH 129/134] Fully implemented and tested the file reading capability --- .../autopsy/datasourceprocessors/xry/XRYFileReader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java index d64fe79629..429f77f24e 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java @@ -137,4 +137,4 @@ public final class XRYFileReader implements AutoCloseable { private boolean marksEndOfEntity(String line) { return line.isEmpty(); } -} +} \ No newline at end of file From bbe0c8dcd0a0b88c29d3e25cdaa666fdd0716806 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Thu, 7 Nov 2019 17:36:36 -0500 Subject: [PATCH 130/134] Updated comments and removed old test files --- .../xry/XRYFileReader.java | 10 +- .../xry/XRYFileReaderTest.java | 212 ------------------ 2 files changed, 6 insertions(+), 216 deletions(-) delete mode 100755 Core/test/unit/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReaderTest.java diff --git a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java index 429f77f24e..55e70b331d 100755 --- a/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java +++ b/Core/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReader.java @@ -50,8 +50,9 @@ public final class XRYFileReader implements AutoCloseable { private final StringBuilder xryEntity; /** - * Creates an XRYFileReader. As part of construction, the file handles are - * opened and reader is advanced passed all header lines of the XRY file. + * 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. * @@ -71,7 +72,8 @@ public final class XRYFileReader implements AutoCloseable { } /** - * Advances the reader until the next valid XRY entity is detected. + * Advances the reader until a valid XRY entity is detected or EOF is + * reached. * * @return Indication that there is another XRY entity to consume or that * the file has been exhausted. @@ -137,4 +139,4 @@ public final class XRYFileReader implements AutoCloseable { private boolean marksEndOfEntity(String line) { return line.isEmpty(); } -} \ No newline at end of file +} diff --git a/Core/test/unit/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReaderTest.java b/Core/test/unit/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReaderTest.java deleted file mode 100755 index 2f39468ebe..0000000000 --- a/Core/test/unit/src/org/sleuthkit/autopsy/datasourceprocessors/xry/XRYFileReaderTest.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * 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.xryparser; - -import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.HashSet; -import java.util.Set; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import org.sleuthkit.datamodel.BlackboardArtifact; -import static org.junit.Assert.*; - -/** - * - */ -public class XRYReportExtractorTest { - - private final Path reportDirectory = Paths.get("C:", "Users", "dsmyda", "Downloads", "2019-10-23-XRYSamples", "files"); - - public XRYReportExtractorTest() { - } - - @Test - public void testCallLogsSample() throws IOException { - Path reportPath = reportDirectory.resolve("Calls.txt"); - XRYReportExtractor extractor = new XRYReportExtractor(new XRYReport(reportPath)); - Set expectation = new HashSet() { - { - add("Calls # 1\n" - + "Call Type: Missed\n" - + "Time: 1/2/2019 1:23:45 PM (Device)\n" - + "From\n" - + "Tel: 12345678\n"); - add("Calls # 2\n" - + "Call Type: Dialed\n" - + "Time: 1/2/2019 2:34:56 PM (Device)\n" - + "Duration: 00:00:05\n" - + "To\n" - + "Tel: 23456789\n"); - add("Calls # 3\n" - + "Call Type: Last Dialed\n" - + "Number: 1234\n" - + "Storage: SIM\n" - + "To\n"); - add("Calls # 4\n" - + "Call Type: Received\n" - + "Time: 1/2/2019 2:34:56 AM (Device)\n" - + "Duration: 00:00:20\n" - + "From\n" - + "Tel: 34567890\n"); - } - }; - MockXRYRecordParser mockParser = new MockXRYRecordParser(expectation); - extractor.extract(mockParser); - assertEquals(expectation.size(), mockParser.getCount()); - } - - @Test - public void testMessagesSample() throws IOException { - Path reportPath = reportDirectory.resolve("Messages-SMS.txt"); - XRYReportExtractor extractor = new XRYReportExtractor(new XRYReport(reportPath)); - Set expectation = new HashSet() { - { - add("Messages-SMS # 1\n" - + "Text: Hello, this is my message. \n" - + "It has multiple lines.\n" - + "Time: 1/23/2019 1:23:45 PM UTC (Network)\n" - + "Type: Deliver\n" - + "Reference Number: 22\n" - + "Segment Number: 1\n" - + "Segments: 1\n" - + "From\n" - + "Tel: 12345678\n"); - add("Messages-SMS # 2\n" - + "Text: Hello, this is another message. one line.\n" - + "Time: 1/2/2019 1:33:44 PM (Device)\n" - + "Type: Submit\n" - + "To\n" - + "Tel: 1234\n"); - add("Messages-SMS # 3\n" - + "Text: Text goes here\n" - + "Time: 1/3/2019 2:33:22 PM (Device)\n" - + "Type: Status Report\n" - + "Participant\n" - + "Tel: 12345\n"); - } - }; - MockXRYRecordParser mockParser = new MockXRYRecordParser(expectation); - extractor.extract(mockParser); - assertEquals(expectation.size(), mockParser.getCount()); - } - - @Test - public void testContactsSample() throws IOException { - Path reportPath = reportDirectory.resolve("Contacts-Contacts.txt"); - XRYReportExtractor extractor = new XRYReportExtractor(new XRYReport(reportPath)); - Set expectation = new HashSet() { - { - add("Contacts-Contacts # 1\n" - + "Name: Abc\n" - + "Tel: +123456\n" - + "Storage: Device\n"); - add("Contacts-Contacts # 2\n" - + "Name: Xyz\n" - + "Tel: +34567\n" - + "Storage: SIM\n"); - } - }; - MockXRYRecordParser mockParser = new MockXRYRecordParser(expectation); - extractor.extract(mockParser); - assertEquals(expectation.size(), mockParser.getCount()); - } - - @Test - public void testWebBookmarksSample() throws IOException { - Path reportPath = reportDirectory.resolve("Web-Bookmarks.txt"); - XRYReportExtractor extractor = new XRYReportExtractor(new XRYReport(reportPath)); - Set expectation = new HashSet() { - { - add("Web-Bookmarks # 1\n" - + "Web Address: http://www.google.com\n" - + "Domain: Google Search\n"); - } - }; - MockXRYRecordParser mockParser = new MockXRYRecordParser(expectation); - extractor.extract(mockParser); - assertEquals(expectation.size(), mockParser.getCount()); - } - - @Test - public void testDeviceSample() throws IOException { - Path reportPath = reportDirectory.resolve("Device-General Information.txt"); - XRYReportExtractor extractor = new XRYReportExtractor(new XRYReport(reportPath)); - Set expectation = new HashSet() { - { - add("Device-General Information # 1\n" - + "Data: c:\\Path To Something I forget what though\\Maybe the Storage folder\n"); - add("Device-General Information # 2\n" - + "Data: Nokia XYZ\n" - + "Attribute: Device Name\n"); - add("Device-General Information # 3\n" - + "Data: Phone\n" - + "Attribute: Device Family\n"); - add("Device-General Information # 4\n" - + "Data: XYZ\n" - + "Attribute: Device Type\n"); - add("Device-General Information # 5\n" - + "Data: 123456\n" - + "Attribute: Mobile Id (IMEI)\n"); - add("Device-General Information # 6\n" - + "Data: 12345\n" - + "Attribute: Security Code\n"); - add("Device-General Information # 7\n" - + "Data: SIM Card\n" - + "Attribute: Device Name\n"); - } - }; - MockXRYRecordParser mockParser = new MockXRYRecordParser(expectation); - extractor.extract(mockParser); - assertEquals(expectation.size(), mockParser.getCount()); - } - - /** - * Mock XRYRecordParser. Rather than creating BlackboardArtifacts, we are - * instead verifying that the XRY Records are being parsed out correctly. - */ - private class MockXRYRecordParser implements XRYRecordParser { - - private final Set allRecords; - private int recordCount; - - public MockXRYRecordParser(Set allRecords) { - this.allRecords = allRecords; - recordCount = 0; - } - - @Override - public BlackboardArtifact makeArtifact(String xryRecord) { - assertNotNull(allRecords); - assertNotNull(xryRecord); - recordCount++; - assertTrue("More records than expected: " + recordCount + ". Expected at most: "+allRecords.size(), recordCount <= allRecords.size()); - assertTrue("Did not find the following record: " + xryRecord, allRecords.contains(xryRecord)); - return null; - } - - public int getCount() { - return recordCount; - } - } -} \ No newline at end of file From e6218ab6c9a0ef2cd98d6263938f7b89e0bb2e1a Mon Sep 17 00:00:00 2001 From: Mark McKinnon Date: Thu, 7 Nov 2019 17:42:36 -0500 Subject: [PATCH 131/134] Add bundle message Add bundle message --- .../communications/relationships/Bundle.properties-MERGED | 1 + .../autopsy/communications/relationships/SummaryViewer.java | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED index 869c95699b..f1530d9f89 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED @@ -46,6 +46,7 @@ SummaryViewer_Account_Description=This account represents a device in the case. SummaryViewer_Account_Description_MuliSelect=Summary information is not available when multiple accounts are selected. SummaryViewer_CaseRefNameColumn_Title=Case Name SummaryViewer_CentralRepository_Message= +SummaryViewer_Country_Code=Country: SummaryViewer_Creation_Date_Title=Creation Date SummaryViewer_Device_Account_Description=This account was referenced by a device in the case. SummaryViewer_FileRef_Message=", "SummaryViewer_Device_Account_Description=This account was referenced by a device in the case.", "SummaryViewer_Account_Description=This account represents a device in the case.", - "SummaryViewer_Account_Description_MuliSelect=Summary information is not available when multiple accounts are selected." + "SummaryViewer_Account_Description_MuliSelect=Summary information is not available when multiple accounts are selected.", + "SummaryViewer_Country_Code=Country: " }) /** @@ -114,7 +115,7 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi if (account.getAccountType().getTypeName().contains("PHONE")) { String countryCode = PhoneNumUtil.getCountryCode(account.getTypeSpecificID()); accountLabel.setText(PhoneNumUtil.convertToInternational(account.getTypeSpecificID())); - accountCountry.setText("Country: " + countryCode); //NON-NLS + accountCountry.setText(Bundle.SummaryViewer_Country_Code() + countryCode); //NON-NLS accountCountry.setEnabled(true); } else { accountLabel.setText(account.getTypeSpecificID()); From b751b8d23ee0ae110223d6d00fd4db2b329c7dd8 Mon Sep 17 00:00:00 2001 From: Mark McKinnon Date: Thu, 7 Nov 2019 17:43:23 -0500 Subject: [PATCH 132/134] Update SummaryViewer.java Remove //NON-NLS --- .../autopsy/communications/relationships/SummaryViewer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.java index 432b38b326..a8694c2619 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.java @@ -115,7 +115,7 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi if (account.getAccountType().getTypeName().contains("PHONE")) { String countryCode = PhoneNumUtil.getCountryCode(account.getTypeSpecificID()); accountLabel.setText(PhoneNumUtil.convertToInternational(account.getTypeSpecificID())); - accountCountry.setText(Bundle.SummaryViewer_Country_Code() + countryCode); //NON-NLS + accountCountry.setText(Bundle.SummaryViewer_Country_Code() + countryCode); accountCountry.setEnabled(true); } else { accountLabel.setText(account.getTypeSpecificID()); From ef1e1f26554ca6fcd2e88130824e1c4eb9c7c25a Mon Sep 17 00:00:00 2001 From: Mark McKinnon Date: Fri, 8 Nov 2019 10:59:10 -0500 Subject: [PATCH 133/134] Update PhoneNumUtil.java Make codacy happy --- Core/src/org/sleuthkit/autopsy/coreutils/PhoneNumUtil.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/PhoneNumUtil.java b/Core/src/org/sleuthkit/autopsy/coreutils/PhoneNumUtil.java index a94a31e8ca..b85df14ad8 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/PhoneNumUtil.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/PhoneNumUtil.java @@ -31,6 +31,9 @@ public class PhoneNumUtil { private static final Logger logger = Logger.getLogger(PhoneNumUtil.class.getName()); + private PhoneNumUtil() { + } + /** * Get the country code from a phone number * From b3ffc4cac0ec2c6dc93273b558f64539c95ffefc Mon Sep 17 00:00:00 2001 From: Mark McKinnon Date: Fri, 8 Nov 2019 11:14:54 -0500 Subject: [PATCH 134/134] Update PhoneNumUtil.java One more time for codacy --- Core/src/org/sleuthkit/autopsy/coreutils/PhoneNumUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/PhoneNumUtil.java b/Core/src/org/sleuthkit/autopsy/coreutils/PhoneNumUtil.java index b85df14ad8..1fa56d1bfd 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/PhoneNumUtil.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/PhoneNumUtil.java @@ -27,7 +27,7 @@ import java.util.logging.Level; * * Class to format and get information from a phone number */ -public class PhoneNumUtil { +public final class PhoneNumUtil { private static final Logger logger = Logger.getLogger(PhoneNumUtil.class.getName());