Merge remote-tracking branch 'upstream/develop' into 5618-show-country-code-for-acct-number-in-the-cvt-acct-table

This commit is contained in:
Mark McKinnon 2019-11-11 09:27:13 -05:00
commit 0809c92c23
117 changed files with 5966 additions and 2059 deletions

View File

@ -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,9 +49,7 @@ before_install:
- python setupSleuthkitBranch.py
install:
- sudo apt-get install testdisk
- cd sleuthkit/sleuthkit
- ./travis_install_libs.sh
- pushd sleuthkit/sleuthkit && ./travis_install_libs.sh && popd
before_script:
- if [ $TRAVIS_OS_NAME = linux ]; then
@ -54,13 +58,13 @@ 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 homebrew/cask-versions;
brew cask install corretto8;
export JAVA_HOME=/Library/Java/JavaVirtualMachines/amazon-corretto-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
- 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

View File

@ -137,7 +137,7 @@
</target>
<target name="get-deps" depends="init-ivy,getTSKJars,get-thirdparty-dependencies,get-InternalPythonModules, download-binlist,getTestDataFiles">
<target name="get-deps" depends="init-ivy,getTSKJars,get-thirdparty-dependencies,get-InternalPythonModules, download-binlist">
<mkdir dir="${ext.dir}"/>
<copy file="${thirdparty.dir}/LICENSE-2.0.txt" todir="${ext.dir}" />
<!-- fetch all the dependencies from Ivy and stick them in the right places -->

View File

@ -1,56 +1,59 @@
<ivy-module version="2.0">
<info organisation="org.sleuthkit.autopsy" module="emailparser"/>
<configurations >
<!-- module dependencies -->
<conf name="core"/>
</configurations>
<dependencies >
<dependency conf="core->default" org="com.github.jgraph" name="jgraphx" rev="v3.8.0"/>
<dependency conf="core->default" org="org.apache.activemq" name="activemq-all" rev="5.11.1"/>
<dependency conf="core->default" org="org.apache.curator" name="curator-client" rev="2.8.0"/>
<dependency conf="core->default" org="org.apache.curator" name="curator-framework" rev="2.8.0"/>
<dependency conf="core->default" org="org.apache.curator" name="curator-recipes" rev="2.8.0"/>
<dependency conf="core->default" org="org.python" name="jython-standalone" rev="2.7.0" />
<dependency conf="core->default" org="org.apache.tika" name="tika-core" rev="1.20"/>
<!-- Exclude the version of cxf-rt-rs-client from Tika 1.20, one of its depedencies breaks Ivy -->
<dependency conf="core->default" org="org.apache.tika" name="tika-parsers" rev="1.20">
<exclude module="cxf-rt-rs-client"/>
<exclude module="cleartk-ml"/>
</dependency>
<!-- Pull down the latest cxf-rt-rs-client which has the Ivy fix -->
<dependency conf="core->default" org="org.apache.cxf" name="cxf-rt-rs-client" rev="3.3.0"/>
<dependency conf="core->default" org="org.cleartk" name="cleartk-ml" rev="2.0.0"/>
<dependency conf="core->default" org="com.adobe.xmp" name="xmpcore" rev="5.1.2"/>
<dependency conf="core->default" org="org.apache.zookeeper" name="zookeeper" rev="3.4.6"/>
<dependency conf="core->default" org="com.healthmarketscience.jackcess" name="jackcess" rev="2.2.0"/>
<dependency conf="core->default" org="com.healthmarketscience.jackcess" name="jackcess-encrypt" rev="2.1.4"/>
<dependency conf="core->default" org="org.apache.commons" name="commons-dbcp2" rev="2.1.1"/>
<dependency conf="core->default" org="org.apache.commons" name="commons-pool2" rev="2.4.2"/>
<dependency conf="core->default" org="commons-codec" name="commons-codec" rev="1.11"/>
<dependency conf="core->default" org="org.jsoup" name="jsoup" rev="1.10.3"/>
<dependency conf="core->default" org="com.fasterxml.jackson.core" name="jackson-core" rev="2.9.7"/>
<dependency conf="core->default" org="com.google.cloud" name="google-cloud-translate" rev="1.70.0"/>
<dependency conf="core->default" org="org.apache.opennlp" name="opennlp-tools" rev="1.9.1"/>
<dependency org="org.sejda.webp-imageio" name="webp-imageio-sejda" rev="0.1.0"/>
<dependency org="com.googlecode.libphonenumber" name="libphonenumber" rev="3.5" />
<dependency conf="core->default" org="commons-validator" name="commons-validator" rev="1.6"/>
<dependency conf="core->default" org="net.htmlparser.jericho" name="jericho-html" rev="3.3"/>
<dependency org="com.squareup.okhttp" name="okhttp" rev="2.7.5"/>
<!-- https://mvnrepository.com/artifact/javax.ws.rs/javax.ws.rs-api -->
<dependency org="javax.ws.rs" name="javax.ws.rs-api" rev="2.0"/>
<override org="jakarta.ws.rs" module="jakarta.ws.rs-api" rev="2.1.5"/>
</dependencies>
</ivy-module>
<ivy-module version="2.0">
<info organisation="org.sleuthkit.autopsy" module="emailparser"/>
<configurations >
<!-- module dependencies -->
<conf name="core"/>
</configurations>
<dependencies >
<dependency conf="core->default" org="com.github.jgraph" name="jgraphx" rev="v3.8.0"/>
<dependency conf="core->default" org="org.apache.activemq" name="activemq-all" rev="5.11.1"/>
<dependency conf="core->default" org="org.apache.curator" name="curator-client" rev="2.8.0"/>
<dependency conf="core->default" org="org.apache.curator" name="curator-framework" rev="2.8.0"/>
<dependency conf="core->default" org="org.apache.curator" name="curator-recipes" rev="2.8.0"/>
<dependency conf="core->default" org="org.python" name="jython-standalone" rev="2.7.0" />
<dependency conf="core->default" org="org.apache.tika" name="tika-core" rev="1.20"/>
<!-- Exclude the version of cxf-rt-rs-client from Tika 1.20, one of its depedencies breaks Ivy -->
<dependency conf="core->default" org="org.apache.tika" name="tika-parsers" rev="1.20">
<exclude module="cxf-rt-rs-client"/>
<exclude module="cleartk-ml"/>
</dependency>
<!-- Pull down the latest cxf-rt-rs-client which has the Ivy fix -->
<dependency conf="core->default" org="org.apache.cxf" name="cxf-rt-rs-client" rev="3.3.0"/>
<dependency conf="core->default" org="org.cleartk" name="cleartk-ml" rev="2.0.0"/>
<dependency conf="core->default" org="com.adobe.xmp" name="xmpcore" rev="5.1.2"/>
<dependency conf="core->default" org="org.apache.zookeeper" name="zookeeper" rev="3.4.6"/>
<dependency conf="core->default" org="com.healthmarketscience.jackcess" name="jackcess" rev="2.2.0"/>
<dependency conf="core->default" org="com.healthmarketscience.jackcess" name="jackcess-encrypt" rev="2.1.4"/>
<dependency conf="core->default" org="org.apache.commons" name="commons-dbcp2" rev="2.1.1"/>
<dependency conf="core->default" org="org.apache.commons" name="commons-pool2" rev="2.4.2"/>
<dependency conf="core->default" org="commons-codec" name="commons-codec" rev="1.11"/>
<dependency conf="core->default" org="org.jsoup" name="jsoup" rev="1.10.3"/>
<dependency conf="core->default" org="com.fasterxml.jackson.core" name="jackson-core" rev="2.9.7"/>
<dependency conf="core->default" org="com.google.cloud" name="google-cloud-translate" rev="1.70.0"/>
<dependency conf="core->default" org="org.apache.opennlp" name="opennlp-tools" rev="1.9.1"/>
<dependency org="org.sejda.webp-imageio" name="webp-imageio-sejda" rev="0.1.0"/>
<dependency org="com.googlecode.libphonenumber" name="libphonenumber" rev="3.5" />
<dependency conf="core->default" org="commons-validator" name="commons-validator" rev="1.6"/>
<dependency conf="core->default" org="net.htmlparser.jericho" name="jericho-html" rev="3.3"/>
<dependency org="com.squareup.okhttp" name="okhttp" rev="2.7.5"/>
<!-- map support for geolocation -->
<dependency conf="core->default" org="org.jxmapviewer" name="jxmapviewer2" rev="2.4"/>
<!-- https://mvnrepository.com/artifact/javax.ws.rs/javax.ws.rs-api -->
<dependency org="javax.ws.rs" name="javax.ws.rs-api" rev="2.0"/>
<override org="jakarta.ws.rs" module="jakarta.ws.rs-api" rev="2.1.5"/>
</dependencies>
</ivy-module>

View File

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

View File

@ -327,6 +327,7 @@
<package>org.sleuthkit.autopsy.datasourceprocessors</package>
<package>org.sleuthkit.autopsy.directorytree</package>
<package>org.sleuthkit.autopsy.events</package>
<package>org.sleuthkit.autopsy.exceptions</package>
<package>org.sleuthkit.autopsy.filesearch</package>
<package>org.sleuthkit.autopsy.guiutils</package>
<package>org.sleuthkit.autopsy.healthmonitor</package>
@ -453,6 +454,10 @@
<runtime-relative-path>ext/commons-pool2-2.4.2.jar</runtime-relative-path>
<binary-origin>release/modules/ext/commons-pool2-2.4.2.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/jxmapviewer2-2.4.jar</runtime-relative-path>
<binary-origin>release/modules/ext/jxmapviewer2-2.4.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/jdom-2.0.5-contrib.jar</runtime-relative-path>
<binary-origin>release/modules/ext/jdom-2.0.5-contrib.jar</binary-origin>

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2018 Basis Technology Corp.
* Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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<BlackboardArtifact> selectedArtifacts = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class));
final Collection<BlackboardArtifact> 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
* 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 {

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2018 Basis Technology Corp.
* Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -76,16 +76,26 @@ public class AddContentTagAction 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 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.
*/
final Collection<AbstractFile> selectedFiles = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class));
final Collection<AbstractFile> 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.
*/
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 {

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2018 Basis Technology Corp.
* Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -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,7 @@ 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.Content;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
@ -45,6 +49,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> content = new HashSet<>();
AddTagAction(String menuText) {
super(menuText);
@ -52,6 +57,32 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup {
@Override
public JMenuItem getPopupPresenter() {
content.clear();
return new TagMenu();
}
/**
* 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<Content> getContentToTag() {
return Collections.unmodifiableCollection(content);
}
/**
* 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<? extends Content> contentToTag) {
content.clear();
content.addAll(contentToTag);
return new TagMenu();
}
@ -118,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.
@ -150,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.
@ -162,7 +193,7 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup {
}
});
add(newTagMenuItem);
}
/**
@ -194,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);
}

View File

@ -1,15 +1,15 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2017-2018 Basis Technology Corp.
*
* Copyright 2017-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -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,19 @@ 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<AbstractFile> selectedFiles) {
return new TagMenu(selectedFiles);
}
@Override
public void actionPerformed(ActionEvent e) {
}
@ -102,19 +115,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;
@ -141,15 +154,24 @@ 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<AbstractFile> selectedFiles) {
super(getActionDisplayName());
final Collection<AbstractFile> 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<String, TagName> tagNamesMap = null;
List<String> standardTagNames = TagsManager.getStandardTagNames();
List<JMenuItem> standardTagMenuitems = new ArrayList<>();
@ -167,22 +189,22 @@ public class DeleteFileContentTagAction extends AbstractAction implements Presen
// a tag with the associated tag name.
if (null != tagNamesMap && !tagNamesMap.isEmpty()) {
try {
List<ContentTag> existingTagsList =
Case.getCurrentCaseThrows().getServices().getTagsManager()
List<ContentTag> existingTagsList
= Case.getCurrentCaseThrows().getServices().getTagsManager()
.getContentTagsByContent(file);
for (Map.Entry<String, TagName> 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 +220,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);
}
}

View File

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

View File

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

View File

@ -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;
@ -84,10 +85,17 @@ 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 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)) {
@ -95,7 +103,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());
/**
* The parent of the fileChooser will either be the multi user
* case dialog or the startup window.
*/
int retval = fileChooser.showOpenDialog(multiUserCaseDialog.isVisible()
? multiUserCaseDialog : (Component) StartupWindowProvider.getInstance().getStartupWindow());
if (retval == JFileChooser.APPROVE_OPTION) {
/*
* Close the startup window, if it is open.
@ -105,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
@ -159,6 +172,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);

View File

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

View File

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

View File

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

View File

@ -3298,10 +3298,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;
@ -3312,10 +3312,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());
}
/*
@ -3393,7 +3393,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";
@ -3553,7 +3553,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);

View File

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

View File

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

View File

@ -1,7 +1,7 @@
/*
* Central Repository
*
* Copyright 2015-2017 Basis Technology Corp.
* Copyright 2015-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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.
*

View File

@ -181,32 +181,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 +223,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 +237,7 @@ public class EamDbUtil {
}
EamDbPlatformEnum.setSelectedPlatform(EamDbPlatformEnum.DISABLED.name());
EamDbPlatformEnum.saveSelectedPlatform();
throw new EamDbException(messageForDialog);
throw ex;
}
}

View File

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

View File

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

View File

@ -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",

View File

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

View File

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

View File

@ -77,7 +77,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)) {
@ -91,7 +91,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);

View File

@ -26,3 +26,4 @@ SummaryViewer.attachmentsLabel.text=Total Attachments:
SummaryViewer.referencesLabel.text=Communication References:
SummaryViewer.referencesDataLabel.text=<reference count>
SummaryViewer.contactsLabel.text=Book Entries:
SummaryViewer.accountCountry.text=<account country>

View File

@ -47,6 +47,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=<Enable Central Respository to see Other Occurrences>
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=<Select a single account to see File References>
@ -72,3 +73,4 @@ SummaryViewer.attachmentsLabel.text=Total Attachments:
SummaryViewer.referencesLabel.text=Communication References:
SummaryViewer.referencesDataLabel.text=<reference count>
SummaryViewer.contactsLabel.text=Book Entries:
SummaryViewer.accountCountry.text=<account country>

View File

@ -1,154 +1,6 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<NonVisualComponents>
<Container class="javax.swing.JPanel" name="summaryPanel">
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Component class="javax.swing.JLabel" name="accountLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.accountLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="15" insetsLeft="9" insetsBottom="0" insetsRight="9" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="accoutDescriptionLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.accoutDescriptionLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="15" insetsLeft="9" insetsBottom="15" insetsRight="9" anchor="18" weightX="1.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Container class="javax.swing.JPanel" name="countsPanel">
<Properties>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
<TitledBorder title="Communications">
<ResourceString PropertyName="titleX" bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.countsPanel.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</TitledBorder>
</Border>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="-1" gridY="-1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Component class="javax.swing.JLabel" name="messagesLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.messagesLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="9" insetsLeft="15" insetsBottom="9" insetsRight="15" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="callLogsLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.callLogsLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="15" insetsBottom="9" insetsRight="15" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="thumbnailCntLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.thumbnailCntLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="2" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="15" insetsBottom="9" insetsRight="15" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="thumbnailsDataLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.thumbnailsDataLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="1" gridY="2" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="9" insetsRight="15" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="messagesDataLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.messagesDataLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="1" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="9" insetsLeft="0" insetsBottom="9" insetsRight="15" anchor="18" weightX="1.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="callLogsDataLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.callLogsDataLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="1" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="9" insetsRight="15" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="attachmentsLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.attachmentsLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="3" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="15" insetsBottom="9" insetsRight="15" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="attachmentDataLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.attachmentDataLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="1" gridY="3" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="9" insetsRight="15" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Container>
</NonVisualComponents>
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
@ -164,38 +16,169 @@
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Component class="org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel" name="fileReferencesPanel">
<Container class="javax.swing.JPanel" name="summaryPanel">
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="-1" gridY="-1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Component class="javax.swing.JLabel" name="accountLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.accountLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="15" insetsLeft="9" insetsBottom="0" insetsRight="9" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="accountCountry">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.accountCountry.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="9" insetsBottom="0" insetsRight="9" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="accoutDescriptionLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.accoutDescriptionLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="2" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="15" insetsLeft="9" insetsBottom="15" insetsRight="9" anchor="18" weightX="1.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
</SubComponents>
</Container>
<Container class="javax.swing.JPanel" name="countsPanel">
<Properties>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
<TitledBorder title="File References in Current Case">
<ResourceString PropertyName="titleX" bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.fileReferencesPanel.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
<TitledBorder title="Communications">
<ResourceString PropertyName="titleX" bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.countsPanel.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</TitledBorder>
</Border>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="3" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="9" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="1.0" weightY="1.0"/>
<GridBagConstraints gridX="0" gridY="1" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel" name="caseReferencesPanel">
<Properties>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
<TitledBorder title="Other Occurrences">
<ResourceString PropertyName="titleX" bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.caseReferencesPanel.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</TitledBorder>
</Border>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="4" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="9" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="1.0" weightY="1.0"/>
</Constraint>
</Constraints>
</Component>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Component class="javax.swing.JLabel" name="messagesLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.messagesLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="9" insetsLeft="15" insetsBottom="9" insetsRight="15" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="callLogsLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.callLogsLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="15" insetsBottom="9" insetsRight="15" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="thumbnailCntLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.thumbnailCntLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="2" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="15" insetsBottom="9" insetsRight="15" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="thumbnailsDataLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.thumbnailsDataLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="1" gridY="2" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="9" insetsRight="15" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="messagesDataLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.messagesDataLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="1" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="9" insetsLeft="0" insetsBottom="9" insetsRight="15" anchor="18" weightX="1.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="callLogsDataLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.callLogsDataLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="1" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="9" insetsRight="15" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="attachmentsLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.attachmentsLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="3" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="15" insetsBottom="9" insetsRight="15" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="attachmentDataLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.attachmentDataLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="1" gridY="3" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="9" insetsRight="15" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
</SubComponents>
</Container>
<Container class="javax.swing.JPanel" name="contanctsPanel">
<Properties>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
@ -208,7 +191,7 @@
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="1" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="0.0" weightY="0.0"/>
<GridBagConstraints gridX="0" gridY="2" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
@ -270,5 +253,37 @@
</Component>
</SubComponents>
</Container>
<Component class="org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel" name="fileReferencesPanel">
<Properties>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
<TitledBorder title="File References in Current Case">
<ResourceString PropertyName="titleX" bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.fileReferencesPanel.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</TitledBorder>
</Border>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="3" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="9" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="1.0" weightY="1.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel" name="caseReferencesPanel">
<Properties>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
<TitledBorder title="Other Occurrences">
<ResourceString PropertyName="titleX" bundle="org/sleuthkit/autopsy/communications/relationships/Bundle.properties" key="SummaryViewer.caseReferencesPanel.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</TitledBorder>
</Border>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="4" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="9" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="1.0" weightY="1.0"/>
</Constraint>
</Constraints>
</Component>
</SubComponents>
</Form>

View File

@ -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
@ -49,7 +50,8 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi
"SummaryViewer_FileRef_Message=<Select a single account to see File References>",
"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: "
})
/**
@ -110,7 +112,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(Bundle.SummaryViewer_Country_Code() + countryCode);
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 +184,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 +222,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 +233,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 +253,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 +342,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 +389,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);
}// </editor-fold>//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;

View File

@ -84,9 +84,12 @@ 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=\u2bc8\u2bc8
MediaPlayerPanel.playButton.text=\u25ba
MediaPlayerPanel.infoLabel.text=No Errors
MediaViewImagePanel.tagsMenu.text_1=Tags Menu
MediaPlayerPanel.VolumeIcon.text=Volume
MediaPlayerPanel.playBackSpeedLabel.text=Speed:

View File

@ -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.
@ -154,12 +155,15 @@ 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=\u2bc8\u2bc8
MediaPlayerPanel.playButton.text=\u25ba
MediaPlayerPanel.infoLabel.text=No Errors
MediaViewImagePanel.tagsMenu.text_1=Tags Menu
MediaPlayerPanel.VolumeIcon.text=Volume
MediaPlayerPanel.playBackSpeedLabel.text=Speed:
# {0} - tableName
SQLiteViewer.readTable.errorText=Error getting rows for table: {0}
# {0} - tableName

View File

@ -20,9 +20,12 @@ 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.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;
@ -59,17 +62,27 @@ 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);
return new String(buffer);
CharsetMatch match = new CharsetDetector().setText(buffer).detect();
if (match != null) {
return new String(buffer, match.getName());
} else {
return new String(buffer);
}
} 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("<p>%s</p>", 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("<p>%s</p>", Bundle.HtmlViewer_encoding_error());
}
}

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<Form version="1.8" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
@ -16,8 +16,8 @@
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="videoPanel" alignment="0" max="32767" attributes="0"/>
<Component id="controlPanel" alignment="0" max="32767" attributes="0"/>
<Component id="videoPanel" alignment="0" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
@ -41,7 +41,7 @@
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<EmptySpace min="0" pref="259" max="32767" attributes="0"/>
<EmptySpace min="0" pref="131" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
</Layout>
@ -51,47 +51,42 @@
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="1" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<Component id="playButton" min="-2" pref="64" max="-2" attributes="0"/>
<EmptySpace type="unrelated" min="-2" max="-2" attributes="0"/>
<Component id="progressSlider" pref="680" max="32767" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="progressLabel" min="-2" max="-2" attributes="0"/>
</Group>
<Group type="102" attributes="0">
<Component id="infoLabel" max="32767" attributes="0"/>
<EmptySpace type="separate" max="-2" attributes="0"/>
<Component id="VolumeIcon" min="-2" pref="64" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="2" max="-2" attributes="0"/>
<Component id="audioSlider" min="-2" pref="229" max="-2" attributes="0"/>
<Component id="infoLabel" max="32767" attributes="0"/>
<Group type="102" alignment="1" attributes="0">
<Group type="103" groupAlignment="1" attributes="0">
<Component id="buttonPanel" alignment="0" max="32767" attributes="0"/>
<Component id="progressSlider" pref="623" max="32767" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" max="-2" attributes="0">
<Component id="progressLabel" max="32767" attributes="0"/>
<Component id="playBackPanel" pref="0" max="32767" attributes="0"/>
</Group>
<EmptySpace min="-2" pref="10" max="-2" attributes="0"/>
</Group>
</Group>
<EmptySpace min="-2" max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="103" groupAlignment="0" max="-2" attributes="0">
<Component id="progressLabel" max="32767" attributes="0"/>
<Component id="progressSlider" max="32767" attributes="0"/>
</Group>
<Component id="playButton" min="-2" max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" max="-2" attributes="0">
<Component id="progressLabel" max="32767" attributes="0"/>
<Component id="progressSlider" max="32767" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="1" attributes="0">
<Component id="audioSlider" min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="VolumeIcon" alignment="3" min="-2" pref="23" max="-2" attributes="0"/>
<Component id="infoLabel" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace min="-2" pref="5" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" max="-2" attributes="0">
<Component id="buttonPanel" max="32767" attributes="0"/>
<Component id="playBackPanel" pref="0" max="32767" attributes="0"/>
</Group>
<EmptySpace min="-2" pref="13" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="14" max="-2" attributes="0"/>
<Component id="infoLabel" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
@ -111,26 +106,9 @@
<Dimension value="[200, 21]"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="infoLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MediaPlayerPanel.infoLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="cursor" type="java.awt.Cursor" editor="org.netbeans.modules.form.editors2.CursorEditor">
<Color id="Default Cursor"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JButton" name="playButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MediaPlayerPanel.playButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="playButtonActionPerformed"/>
</Events>
<AuxValues>
<AuxValue name="JavaCodeGenerator_InitCodePost" type="java.lang.String" value="progressSlider.setUI(new CircularJSliderUI(progressSlider, new CircularJSliderConfiguration(new Dimension(18,18))));"/>
</AuxValues>
</Component>
<Component class="javax.swing.JLabel" name="progressLabel">
<Properties>
@ -139,31 +117,174 @@
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="VolumeIcon">
<Container class="javax.swing.JPanel" name="buttonPanel">
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Component class="javax.swing.JButton" name="playButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MediaPlayerPanel.playButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="playButtonActionPerformed"/>
</Events>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="1" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="21" ipadY="0" insetsTop="5" insetsLeft="6" insetsBottom="0" insetsRight="0" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JButton" name="fastForwardButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MediaPlayerPanel.fastForwardButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="fastForwardButtonActionPerformed"/>
</Events>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="2" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="5" insetsLeft="6" insetsBottom="0" insetsRight="0" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JButton" name="rewindButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MediaPlayerPanel.rewindButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="rewindButtonActionPerformed"/>
</Events>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="5" insetsLeft="0" insetsBottom="1" insetsRight="0" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JLabel" name="VolumeIcon">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MediaPlayerPanel.VolumeIcon.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="horizontalTextPosition" type="int" value="2"/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="3" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="8" ipadY="7" insetsTop="6" insetsLeft="14" insetsBottom="0" insetsRight="0" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JSlider" name="audioSlider">
<Properties>
<Property name="majorTickSpacing" type="int" value="10"/>
<Property name="maximum" type="int" value="50"/>
<Property name="minorTickSpacing" type="int" value="5"/>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MediaPlayerPanel.audioSlider.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="value" type="int" value="25"/>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[200, 21]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[200, 21]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_InitCodePost" type="java.lang.String" value="audioSlider.setUI(new CircularJSliderUI(audioSlider, new CircularJSliderConfiguration(new Dimension(15,15))));"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="4" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="-116" ipadY="7" insetsTop="3" insetsLeft="1" insetsBottom="0" insetsRight="10" anchor="18" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
</SubComponents>
</Container>
<Component class="javax.swing.JLabel" name="infoLabel">
<Properties>
<Property name="horizontalAlignment" type="int" value="2"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MediaPlayerPanel.VolumeIcon.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JSlider" name="audioSlider">
<Properties>
<Property name="majorTickSpacing" type="int" value="10"/>
<Property name="maximum" type="int" value="50"/>
<Property name="minorTickSpacing" type="int" value="5"/>
<Property name="paintTicks" type="boolean" value="true"/>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MediaPlayerPanel.audioSlider.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="value" type="int" value="25"/>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[200, 21]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[200, 21]"/>
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MediaPlayerPanel.infoLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="cursor" type="java.awt.Cursor" editor="org.netbeans.modules.form.editors2.CursorEditor">
<Color id="Default Cursor"/>
</Property>
</Properties>
</Component>
<Container class="javax.swing.JPanel" name="playBackPanel">
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<Component id="playBackSpeedLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="32767" attributes="0"/>
<Component id="playBackSpeedComboBox" min="-2" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="13" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="-2" pref="6" max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="playBackSpeedComboBox" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="playBackSpeedLabel" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Component class="javax.swing.JComboBox" name="playBackSpeedComboBox">
<Properties>
<Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor">
<StringArray count="8">
<StringItem index="0" value="0.25x"/>
<StringItem index="1" value="0.50x"/>
<StringItem index="2" value="0.75x"/>
<StringItem index="3" value="1x"/>
<StringItem index="4" value="1.25x"/>
<StringItem index="5" value="1.50x"/>
<StringItem index="6" value="1.75x"/>
<StringItem index="7" value="2x"/>
</StringArray>
</Property>
<Property name="selectedIndex" type="int" value="3"/>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[53, 23]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[53, 23]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[53, 23]"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="playBackSpeedComboBoxActionPerformed"/>
</Events>
<AuxValues>
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;String&gt;"/>
</AuxValues>
</Component>
<Component class="javax.swing.JLabel" name="playBackSpeedLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MediaPlayerPanel.playBackSpeedLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Container>
</SubComponents>

View File

@ -19,16 +19,26 @@
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;
import java.util.EnumSet;
import java.util.List;
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;
@ -37,7 +47,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;
@ -52,8 +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 javax.swing.plaf.basic.BasicSliderUI.TrackListener;
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
@ -177,17 +195,27 @@ 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;
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
*/
public MediaPlayerPanel() throws GstException, UnsatisfiedLinkError {
initComponents();
customizeComponents();
//True for fairness. In other words,
//acquire() calls are processed in order of invocation.
sliderLock = new Semaphore(1, true);
}
private void customizeComponents() {
@ -202,11 +230,20 @@ 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);
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, 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);
}
}
});
@ -220,10 +257,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(
"<html><font color='red'>%s</font></html>",
MEDIA_PLAYER_ERROR_STRING));
SwingUtilities.invokeLater(() -> {
enableComponents(false);
infoLabel.setText(String.format(
"<html><font color='red'>%s</font></html>",
MEDIA_PLAYER_ERROR_STRING));
});
timer.stop();
}
};
@ -231,9 +270,13 @@ 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("");
});
}
}
};
@ -241,7 +284,6 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
@Override
public void endOfStream(GstObject go) {
gstPlayBin.seek(ClockTime.ZERO);
progressSlider.setValue(0);
/**
* Keep the video from automatically playing
*/
@ -268,7 +310,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("<html><font color='red'>%s</font></html>", Bundle.GstVideoPanel_noOpenCase_errMsg()));
@ -301,8 +343,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;
@ -317,6 +359,9 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
progressSlider.setEnabled(isEnabled);
videoPanel.setEnabled(isEnabled);
audioSlider.setEnabled(isEnabled);
rewindButton.setEnabled(isEnabled);
fastForwardButton.setEnabled(isEnabled);
playBackSpeedComboBox.setEnabled(isEnabled);
}
@Override
@ -373,7 +418,18 @@ 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));
}
/**
* 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));
}
/**
@ -383,24 +439,18 @@ 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();
}
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);
}
/**
@ -422,7 +472,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);
@ -443,8 +497,8 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
protected void done() {
try {
super.get();
if(this.isCancelled()) {
if (this.isCancelled()) {
return;
}
@ -460,8 +514,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;
}
@ -471,14 +525,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) {
@ -499,6 +553,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
@Override
public void actionPerformed(ActionEvent e) {
if (!progressSlider.getValueIsAdjusting()) {
sliderLock.acquireUninterruptibly();
long position = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS);
long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS);
/**
@ -506,12 +561,218 @@ 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));
}
updateTimeLabel(position, duration);
SwingUtilities.invokeLater(() -> {
updateTimeLabel(position, duration);
});
sliderLock.release();
}
}
}
/**
* 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 slider JSlider component
* @param config Configuration object. Contains info about thumb
* dimensions and colors.
*/
public CircularJSliderUI(JSlider slider, CircularJSliderConfiguration config) {
super(slider);
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 graphic) {
Rectangle thumb = this.thumbRect;
Color original = graphic.getColor();
//Change the thumb view from the rectangle
//controller to an oval.
graphic.setColor(config.getThumbColor());
Dimension thumbDimension = config.getThumbDimension();
graphic.fillOval(thumb.x, thumb.y, thumbDimension.width, thumbDimension.height);
//Preserve the graphics original color
graphic.setColor(original);
}
@Override
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.
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 = graphic.getColor();
//Paint the seen side
graphic.setColor(config.getSeenTrackColor());
graphic.drawLine(track.x, track.y + track.height / 2,
thumbX, thumbY + track.height / 2);
//Paint the unseen side
graphic.setColor(config.getUnseenTrackColor());
graphic.drawLine(thumbX, thumbY + track.height / 2,
track.x + track.width, track.y + track.height / 2);
//Preserve the graphics color.
graphic.setColor(original);
}
@Override
protected TrackListener createTrackListener(JSlider slider) {
return new CustomTrackListener();
}
@Override
protected void scrollDueToClickInTrack(int direction) {
//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();
}
/**
* Applies anti-aliasing if available.
*/
@Override
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(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
* itself under the mouse so that it can always be dragged.
*/
private class CustomTrackListener extends CircularJSliderUI.TrackListener {
@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) {
if (!slider.isEnabled()) {
return;
}
super.mouseReleased(e);
//Unpause once the mouse has been released.
gstPlayBin.play();
}
}
}
@ -524,15 +785,22 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//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();
buttonPanel = 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();
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);
@ -542,7 +810,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, 131, Short.MAX_VALUE)
);
progressSlider.setValue(0);
@ -550,9 +818,11 @@ 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(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
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() {
@ -560,64 +830,145 @@ 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);
buttonPanel.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);
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() {
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);
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);
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, 14, 0, 0);
buttonPanel.add(VolumeIcon, gridBagConstraints);
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));
audioSlider.setUI(new CircularJSliderUI(audioSlider, new CircularJSliderConfiguration(new Dimension(15,15))));
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);
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
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(
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(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)))
.addContainerGap())
.addComponent(infoLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, controlPanelLayout.createSequentialGroup()
.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)
.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)
.addGroup(controlPanelLayout.createSequentialGroup()
.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))
.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, 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))
.addGap(14, 14, 14)
.addComponent(infoLabel))
);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
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)
@ -628,23 +979,96 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
);
}// </editor-fold>//GEN-END:initComponents
private void rewindButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_rewindButtonActionPerformed
long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS);
//Skip 30 seconds.
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
//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 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) {
return;
}
long newTime = currentTime + fastForwardDelta;
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, 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
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;
private javax.swing.JPanel buttonPanel;
private javax.swing.JPanel controlPanel;
private javax.swing.JButton fastForwardButton;
private javax.swing.JLabel infoLabel;
private javax.swing.JPanel playBackPanel;
private javax.swing.JComboBox<String> playBackSpeedComboBox;
private javax.swing.JLabel playBackSpeedLabel;
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
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<mode version="2.4">
<name unique="geolocation"/>
<kind type="editor"/>
<state type="separated"/>
<bounds x="76" y="68" width="500" height="500"/>
<frame state="0"/>
<empty-behavior permanent="false"/>
</mode>

View File

@ -448,6 +448,7 @@
<file name="floatingLeftBottom.wsmode" url="floatingLeftBottomWsmode.xml"/>
<file name="timeline.wsmode" url="timelineWsmode.xml"/>
<file name="cvt.wsmode" url="cvtWsmode.xml"/>
<file name="geolocation.wsmode" url="geolocationWsmode.xml"/>
</folder>
</folder>

View File

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

View File

@ -1,15 +1,15 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018 Basis Technology Corp.
*
* Copyright 2018-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -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.
*
* @param node Node passed into content viewer
* @return highest priority content or null if there is no content
* Gets a Content object from the Lookup of a display Node object,
* preferring to return any Content object other than a BlackboardArtifact
* object.
*
* This method was written with the needs of the hex and strings content
* viewers in mind - the algorithm is exactly what those viewers require.
*
* @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;
}
else {
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() {
}
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2012 Basis Technology Corp.
* Copyright 2012-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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> linkFlags;
private List<FileAttributesFlags> 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> linkFlags;
private final List<FileAttributesFlags> 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<String> linkTargetIdList;
private final List<String> 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<CommonNetworkRelativeLinkFlags> commonNetworkRelativeListFlags;
private NetworkProviderType networkProviderType;
private boolean unicodeNetAndDeviceName;
private String netName;
private String netNameUnicode;
private String deviceName;
private String deviceNameUnicode;
private final List<CommonNetworkRelativeLinkFlags> 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<LinkFlags>();
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<FileAttributesFlags>();
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<CommonNetworkRelativeLinkFlags>();
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<CommonNetworkRelativeLinkFlags> getCommonNetworkRelativeListFlags() {
return commonNetworkRelativeListFlags;
return Collections.unmodifiableList(commonNetworkRelativeListFlags);
}
public String getCommonPathSuffix() {
@ -176,7 +177,7 @@ public class JLNK {
}
public List<FileAttributesFlags> getFileAttributesFlags() {
return fileAttributesFlags;
return Collections.unmodifiableList(fileAttributesFlags);
}
public int getFileSize() {
@ -196,7 +197,7 @@ public class JLNK {
}
public List<String> 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<LinkFlags> getLinkFlags() {
return linkFlags;
return Collections.unmodifiableList(linkFlags);
}
public String getLocalBasePath() {

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2012 Basis Technology Corp.
* Copyright 2012-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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;
}
}
}

View File

@ -0,0 +1,109 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.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 final class PhoneNumUtil {
private static final Logger logger = Logger.getLogger(PhoneNumUtil.class.getName());
private PhoneNumUtil() {
}
/**
* 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;
}
}
}

View File

@ -122,6 +122,7 @@ public class ArtifactStringContent implements StringContent {
case LONG:
case DOUBLE:
case BYTE:
case JSON:
default:
value = attr.getDisplayString();
break;

View File

@ -0,0 +1,258 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.datasourceprocessors.xry;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
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.NoSuchElementException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.apache.commons.io.FilenameUtils;
/**
* 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 implements AutoCloseable {
private static final Logger logger = Logger.getLogger(XRYFileReader.class.getName());
//Assume UTF_16LE
private static final Charset CHARSET = StandardCharsets.UTF_16LE;
//Assume TXT extension
private static final String EXTENSION = "txt";
//Assume 0xFFFE is the BOM
private static final int[] BOM = {0xFF, 0xFE};
//Assume all XRY reports have the type on the 3rd line.
private static final int LINE_WITH_REPORT_TYPE = 3;
//Assume all headers are 5 lines in length.
private static final int HEADER_LENGTH_IN_LINES = 5;
//Underlying reader for the xry file.
private final BufferedReader reader;
private final StringBuilder xryEntity;
/**
* 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.
*
* @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 the reader to the start of the first XRY entity.
for (int i = 0; i < HEADER_LENGTH_IN_LINES; i++) {
reader.readLine();
}
xryEntity = new StringBuilder();
}
/**
* 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.
* @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)) {
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;
}
/**
* Returns an XRY entity if there is one, otherwise an exception is thrown.
*
* @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()) {
String returnVal = xryEntity.toString();
xryEntity.setLength(0);
return returnVal;
} else {
throw new NoSuchElementException();
}
}
/**
* Closes any file handles this reader may have open.
*
* @throws IOException
*/
@Override
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();
}
/**
* 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 cannot be a directory.
if (attr.isSymbolicLink() || attr.isDirectory()) {
return false;
}
//Check 0xFFFE BOM
if (!isXRYBOM(file)) {
return false;
}
try {
Optional<String> reportType = getType(file);
//All valid XRY reports should have a type.
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);
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.
*/
private 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
*/
private static Optional<String> 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();
}
}
}

View File

@ -0,0 +1,77 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.datasourceprocessors.xry;
import java.io.IOException;
import java.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 final class XRYFolder {
//Depth that will contain XRY files. All XRY files will be immediate
//children of their parent folder.
private static final int XRY_FILES_DEPTH = 1;
/**
* 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<Path> allFiles = Files.walk(folder, XRY_FILES_DEPTH)) {
Iterator<Path> allFilesIterator = allFiles.iterator();
while (allFilesIterator.hasNext()) {
Path currentFile = allFilesIterator.next();
if (XRYFileReader.isXRYFile(currentFile)) {
return true;
}
}
return false;
} catch (UncheckedIOException ex) {
throw ex.getCause();
}
}
}

View File

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

View File

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

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013-2018 Basis Technology Corp.
* Copyright 2013-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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<? extends AbstractFile> 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<? extends AbstractFile> 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<FileExtractionTask> 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<? extends AbstractFile> 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<AbstractFile> uniqueFiles = new HashSet<>(selectedFiles);
// make a task for each file
ArrayList<FileExtractionTask> 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<FileExtractionTask> fileExtractionTasks) {
// verify all of the sources and destinations are OK
for (Iterator<FileExtractionTask> 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<Object, Void> {
private final Logger logger = Logger.getLogger(FileExtracter.class.getName());
private ProgressHandle progress;
private final List<FileExtractionTask> extractionTasks;
/**
* Create an instance of the FileExtracter.
*
* @param extractionTasks List of file extraction tasks.
*/
FileExtracter(List<FileExtractionTask> 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;
}
*/
}
}

View File

@ -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<OutputFileData> copyList = new ArrayList<OutputFileData>() {

View File

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

View File

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

View File

@ -0,0 +1,356 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this 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<? extends AbstractFile> 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<FileExtractionTask> 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<? extends AbstractFile> 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<AbstractFile> uniqueFiles = new HashSet<>(selectedFiles);
// make a task for each file
ArrayList<FileExtractionTask> 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<FileExtractionTask> fileExtractionTasks) {
// verify all of the sources and destinations are OK
for (Iterator<FileExtractionTask> it = fileExtractionTasks.iterator(); it.hasNext();) {
FileExtractionTask task = it.next();
if (ContentUtils.isDotDirectory(task.source)) {
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<Object, Void> {
private final Logger logger = Logger.getLogger(FileExtracter.class.getName());
private ProgressHandle progress;
private final List<FileExtractionTask> extractionTasks;
/**
* Create an instance of the FileExtracter.
*
* @param extractionTasks List of file extraction tasks.
*/
FileExtracter(List<FileExtractionTask> 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; }
*/
}
}

View File

@ -0,0 +1,88 @@
/*
* Autopsy
*
* Copyright 2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.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;
private final String userMessage;
/**
* Constructs an AutopsyException with identical exception and user-friendly
* messages.
*
* @param message Exception message.
*/
public AutopsyException(String message) {
super(message);
this.userMessage = message;
}
/**
* 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.
*/
public AutopsyException(String message, String userMessage) {
super(message);
this.userMessage = userMessage;
}
/**
* Constructs an AutopsyException with identical exception and user-friendly
* messages.
*
* @param message Exception message.
* @param cause Exception cause.
*/
public AutopsyException(String message, Throwable cause) {
super(message, cause);
this.userMessage = message;
}
/**
* 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 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 was explicitly set, otherwise
* returns the exception message.
*/
public String getUserMessage() {
return userMessage;
}
}

View File

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

View File

@ -0,0 +1,10 @@
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
RefreshPanel.refreshLabel.text=The geolocation data has been updated, the visualization may be out of date.
RefreshPanel.refreshButton.text=Refresh View
RefreshPanel.closeButton.text=

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.4" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
<SubComponents>
<Container class="org.sleuthkit.autopsy.geolocation.MapPanel" name="mapPanel">
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
<BorderConstraints direction="Center"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
</Container>
</SubComponents>
</Form>

View File

@ -0,0 +1,217 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.geolocation;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeListener;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import javax.swing.SwingWorker;
import 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;
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;
/**
* 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 long serialVersionUID = 1L;
private static final Logger logger = Logger.getLogger(GeolocationTopComponent.class.getName());
private static final Set<IngestManager.IngestModuleEvent> INGEST_MODULE_EVENTS_OF_INTEREST = EnumSet.of(DATA_ADDED);
private final PropertyChangeListener ingestListener;
final RefreshPanel refreshPanel = new RefreshPanel();
@Messages({
"GLTopComponent_name=Geolocation",
"GLTopComponent_initilzation_error=An error occurred during waypoint initilization. Geolocation data maybe incomplete."
})
/**
* 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 for GPS data.
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);
}
@Override
public void componentOpened() {
super.componentOpened();
WindowManager.getDefault().setTopComponentFloating(this, true);
}
/**
* 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<List<Waypoint>, Waypoint> worker = new SwingWorker<List<Waypoint>, Waypoint>() {
@Override
protected List<Waypoint> doInBackground() throws Exception {
Case currentCase = Case.getCurrentCaseThrows();
return MapWaypoint.getWaypoints(currentCase.getSleuthkitCase());
}
@Override
protected void done() {
if (isDone() && !isCancelled()) {
try {
List<Waypoint> 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 (ExecutionException 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 initializing thread for geolocation window was interrupted.", 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")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
mapPanel = new org.sleuthkit.autopsy.geolocation.MapPanel();
setLayout(new java.awt.BorderLayout());
add(mapPanel, java.awt.BorderLayout.CENTER);
}// </editor-fold>//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
}

View File

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.4" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<Properties>
<Property name="focusable" type="boolean" value="false"/>
</Properties>
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,0,-42,0,0,0,58"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
<SubComponents>
<Container class="org.jxmapviewer.JXMapViewer" name="mapViewer">
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
<BorderConstraints direction="Center"/>
</Constraint>
</Constraints>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Container class="javax.swing.JPanel" name="zoomPanel">
<Properties>
<Property name="focusable" type="boolean" value="false"/>
<Property name="opaque" type="boolean" value="false"/>
<Property name="requestFocusEnabled" type="boolean" value="false"/>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="-1" gridY="-1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="4" insetsLeft="4" insetsBottom="4" insetsRight="4" anchor="16" weightX="1.0" weightY="1.0"/>
</Constraint>
</Constraints>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
<Component id="zoomSlider" min="-2" max="-2" attributes="0"/>
<EmptySpace max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="1" attributes="0">
<EmptySpace max="32767" attributes="0"/>
<Component id="zoomSlider" min="-2" max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Component class="javax.swing.JSlider" name="zoomSlider">
<Properties>
<Property name="maximum" type="int" value="15"/>
<Property name="minimum" type="int" value="10"/>
<Property name="minorTickSpacing" type="int" value="1"/>
<Property name="orientation" type="int" value="1"/>
<Property name="paintTicks" type="boolean" value="true"/>
<Property name="snapToTicks" type="boolean" value="true"/>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[35, 100]"/>
</Property>
<Property name="opaque" type="boolean" value="false"/>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[35, 190]"/>
</Property>
</Properties>
<Events>
<EventHandler event="stateChanged" listener="javax.swing.event.ChangeListener" parameters="javax.swing.event.ChangeEvent" handler="zoomSliderStateChanged"/>
</Events>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Container>
</SubComponents>
</Form>

View File

@ -0,0 +1,238 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.geolocation;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.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.
*/
final class MapPanel extends javax.swing.JPanel {
private static final long serialVersionUID = 1L;
private boolean zoomChanging;
// Using a DefaultListModel to store the way points because we get
// a lot of functionality for free, like listeners.
private final DefaultListModel<Waypoint> waypointListModel;
/**
* Creates new form MapPanel
*/
MapPanel() {
waypointListModel = new DefaultListModel<>();
zoomChanging = false;
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());
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<Waypoint> waypointPainter = new WaypointPainter<Waypoint>() {
@Override
public Set<Waypoint> getWaypoints() {
Set<Waypoint> 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);
zoomSlider.setValue((zoomSlider.getMaximum() + zoomSlider.getMinimum())- 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")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//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);
}// </editor-fold>//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
}

View File

@ -0,0 +1,86 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.geolocation;
import java.util.ArrayList;
import java.util.List;
import org.jxmapviewer.viewer.GeoPosition;
import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationDataException;
import org.sleuthkit.autopsy.geolocation.datamodel.Route;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint;
/**
* 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{
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<org.jxmapviewer.viewer.Waypoint> getWaypoints(SleuthkitCase skCase) throws GeoLocationDataException{
List<Waypoint> points = Waypoint.getAllWaypoints(skCase);
List<Route> routes = Route.getRoutes(skCase);
for(Route route: routes) {
points.addAll(route.getRoute());
}
List<org.jxmapviewer.viewer.Waypoint> mapPoints = new ArrayList<>();
for(Waypoint point: points) {
mapPoints.add(new MapWaypoint(point));
}
return mapPoints;
}
/**
* {@inheritDoc}
*/
@Override
public GeoPosition getPosition() {
return position;
}
String getLabel() {
return dataModelWaypoint.getLabel();
}
}

View File

@ -0,0 +1,95 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.geolocation;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import 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 static final long serialVersionUID = 1L;
@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.
PropertyChangeListener 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
}
}

View File

@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<Properties>
<Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
<Color blue="33" green="33" red="0" type="rgb"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,3,15"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
<SubComponents>
<Component class="javax.swing.JLabel" name="refreshLabel">
<Properties>
<Property name="foreground" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
<Color blue="ff" green="ff" red="ff" type="rgb"/>
</Property>
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
<Image iconType="3" name="/org/sleuthkit/autopsy/images/warning16.png"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/geolocation/Bundle.properties" key="RefreshPanel.refreshLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="0" gridY="0" gridWidth="1" gridHeight="1" fill="2" ipadX="0" ipadY="0" insetsTop="15" insetsLeft="10" insetsBottom="15" insetsRight="10" anchor="18" weightX="1.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JButton" name="refreshButton">
<Properties>
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
<Image iconType="3" name="/org/sleuthkit/autopsy/geolocation/images/arrow-circle-double-135.png"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/geolocation/Bundle.properties" key="RefreshPanel.refreshButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="margin" type="java.awt.Insets" editor="org.netbeans.beaninfo.editors.InsetsEditor">
<Insets value="[2, 5, 2, 5]"/>
</Property>
</Properties>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="-1" gridY="-1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
<Component class="javax.swing.JButton" name="closeButton">
<Properties>
<Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
<Color blue="0" green="0" red="0" type="rgb"/>
</Property>
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
<Image iconType="3" name="/org/sleuthkit/autopsy/geolocation/images/cross-script.png"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/geolocation/Bundle.properties" key="RefreshPanel.closeButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="margin" type="java.awt.Insets" editor="org.netbeans.beaninfo.editors.InsetsEditor">
<Insets value="[0, 0, 0, 0]"/>
</Property>
<Property name="opaque" type="boolean" value="false"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="closeButtonActionPerformed"/>
</Events>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
<GridBagConstraints gridX="-1" gridY="-1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="5" insetsBottom="0" insetsRight="5" anchor="10" weightX="0.0" weightY="0.0"/>
</Constraint>
</Constraints>
</Component>
</SubComponents>
</Form>

View File

@ -0,0 +1,115 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.geolocation;
import java.awt.event.ActionListener;
import javax.swing.JPanel;
/**
* Message panel to info user that the MapPanel may need to be refreshed
* due to new artifacts.
*
*/
final class RefreshPanel extends JPanel {
private static final long serialVersionUID = 1L;
/**
* Creates new form RefreshPanel
*/
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")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//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);
}// </editor-fold>//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
}

View File

@ -0,0 +1,6 @@
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

View File

@ -0,0 +1,85 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.geolocation.datamodel;
import java.util.Map;
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 Waypoint {
/**
* Construct a way point with the given artifact.
*
* @param artifact BlackboardArtifact for waypoint
*
* @throws GeoLocationDataException
*/
EXIFWaypoint(BlackboardArtifact artifact) throws GeoLocationDataException {
this(artifact, getAttributesFromArtifactAsMap(artifact), getImageFromArtifact(artifact));
}
/**
* Constructs new waypoint using the given artifact and attribute map.
*
* @param artifact Waypoint BlackboardArtifact
* @param attributeMap Map of artifact attributes
* @param image EXIF AbstractFile image
*
* @throws GeoLocationDataException
*/
private EXIFWaypoint(BlackboardArtifact artifact, Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap, AbstractFile image) throws GeoLocationDataException {
super(artifact,
image != null ? image.getName() : "",
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, null);
}
/**
* 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 GeoLocationDataException
*/
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) {
try {
abstractFile = artifact.getSleuthkitCase().getAbstractFileById(artifact.getObjectID());
} catch (TskCoreException ex) {
throw new GeoLocationDataException(String.format("Unable to getAbstractFileByID for artifactID: %d", artifact.getArtifactID()), ex);
}
}
return abstractFile;
}
}

View File

@ -0,0 +1,47 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.geolocation.datamodel;
/**
* An exception class for the Geolocation dateModel;
*
*/
public class GeoLocationDataException extends Exception {
private static final long serialVersionUID = 1L;
/**
* 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);
}
}

View File

@ -0,0 +1,82 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.geolocation.datamodel;
import java.util.Map;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
/**
* A Last Known Location Waypoint object.
*/
final class LastKnownWaypoint extends Waypoint {
/**
* Constructs a new waypoint.
*
* @param artifact BlackboardArtifact from which to construct the waypoint
*
* @throws GeoLocationDataException
*/
LastKnownWaypoint(BlackboardArtifact artifact) throws GeoLocationDataException {
this(artifact, 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<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> 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, null);
}
/**
* Gets the label for a TSK_LAST_KNOWN_LOCATION.
*
* @param attributeMap Map of artifact attributes for this waypoint
*
* @return String value from attribute TSK_NAME or LastKnownWaypoint_Label
*
* @throws GeoLocationDataException
*/
@Messages({
"LastKnownWaypoint_Label=Last Known Location",})
private static String getLabelFromArtifact(Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> 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();
}
return label;
}
}

View File

@ -0,0 +1,190 @@
/*
*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.geolocation.datamodel;
import java.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
* however the class was written with the assumption that routes may have
* more that two points.
*
*/
public final class Route {
private final List<Waypoint> points;
private final Long timestamp;
// This list is not expected to change after construction so the
// constructor will take care of creating an unmodifiable List
private final List<Waypoint.Property> 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
* were found
*
* @throws GeoLocationDataException
*/
static public List<Route> getRoutes(SleuthkitCase skCase) throws GeoLocationDataException {
List<BlackboardArtifact> 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<Route> routes = new ArrayList<>();
for (BlackboardArtifact artifact : artifacts) {
Route route = new Route(artifact);
routes.add(route);
}
return routes;
}
/**
* Construct a route for the given artifact.
*
* @param artifact TSK_GPS_ROUTE artifact object
*/
Route(BlackboardArtifact artifact) throws GeoLocationDataException {
points = new ArrayList<>();
Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> 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));
}
/**
* Get the list of way points for this route;
*
* @return List an unmodifiableList of ArtifactWaypoints for this route
*/
public List<Waypoint> getRoute() {
return Collections.unmodifiableList(points);
}
/**
* Get the "Other attributes" for this route. The map will contain display
* name, formatted value pairs. This list is unmodifiable.
*
* @return Map of key, value pairs.
*/
public List<Waypoint.Property> getOtherProperties() {
return immutablePropertiesList;
}
/**
* Get the route label.
*/
@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();
}
public Long getTimestamp() {
return timestamp;
}
/**
* Get the route start point.
*
* @param attributeMap Map of artifact attributes for this waypoint.
*
* An exception will be thrown if longitude or latitude is null.
*
* @return Start waypoint
*
* @throws GeoLocationDataException.
*/
@Messages({
"Route_Start_Label=Start"
})
private Waypoint getRouteStartPoint(BlackboardArtifact artifact, Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> 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 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");
}
}
/**
* 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 The end waypoint
*
* @throws GeoLocationDataException
*/
@Messages({
"Route_End_Label=End"
})
private Waypoint getRouteEndPoint(BlackboardArtifact artifact, Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> 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 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");
}
}
}

View File

@ -0,0 +1,77 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.geolocation.datamodel;
import java.util.Map;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
/**
* A SearchWaypoint is a subclass of Waypoint.
*/
final class SearchWaypoint extends Waypoint {
/**
* Construct a waypoint for TSK_GPS_SEARCH artifact.
*
* @throws GeoLocationDataException
*/
SearchWaypoint(BlackboardArtifact artifact) throws GeoLocationDataException {
this(artifact, getAttributesFromArtifactAsMap(artifact));
}
private SearchWaypoint(BlackboardArtifact artifact, Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> 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, null);
}
/**
* Returns a Label for a GPS_SEARCH artifact.
*
* @param attributeMap Map of artifact attributes
*
* @return String label for the artifacts way point.
*
* @throws GeoLocationDataException
*/
@Messages({
"SearchWaypoint_DisplayLabel=GPS Search"
})
private static String getLabelFromArtifact(Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> 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();
}
}

View File

@ -0,0 +1,82 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.geolocation.datamodel;
import java.util.Map;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
/**
* A wrapper class for TSK_GPS_TRACKPOINT artifacts.
*/
final class TrackpointWaypoint extends Waypoint {
/**
* Construct a waypoint for trackpoints.
*
* @throws GeoLocationDataException
*/
TrackpointWaypoint(BlackboardArtifact artifact) throws GeoLocationDataException {
this(artifact, getAttributesFromArtifactAsMap(artifact));
}
private TrackpointWaypoint(BlackboardArtifact artifact, Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> 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, null);
}
/**
* 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 GeoLocationDataException
*/
@Messages({
"TrackpointWaypoint_DisplayLabel=GPS Trackpoint"
})
private static String getLabelFromArtifact(Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> 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_PROG_NAME);
if (attribute != null) {
return attribute.getDisplayString();
}
attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FLAG);
if (attribute != null) {
return attribute.getDisplayString();
}
return Bundle.TrackpointWaypoint_DisplayLabel();
}
}

View File

@ -0,0 +1,514 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.geolocation.datamodel;
import java.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;
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;
/**
* Representation of a Waypoint created from a BlackboardArtifact.
*
*/
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<Waypoint.Property> immutablePropertiesList;
/**
* This is a list of attributes that are already being handled by the
* by getter functions.
*/
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,
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,};
private static final Logger logger = Logger.getLogger(Waypoint.class.getName());
/**
* 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<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> 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<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> 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.
*/
public Long getTimestamp() {
return timestamp;
}
/**
* Get the label for this point object.
*
* @return String label for the point or null if none was set
*/
public String getLabel() {
return label;
}
/**
* Get the latitude for this point.
*
* @return Returns the latitude for the point
*/
public Double getLatitude() {
return latitude;
}
/**
* Get the longitude for this point.
*
* @return Returns the longitude for the point
*/
public Double getLongitude() {
return longitude;
}
/**
* Get the altitude for this point.
*
* @return Returns the altitude for the point or null if none was set
*/
public Double getAltitude() {
return altitude;
}
/**
* Get the image for this waypoint.
*
* @return AbstractFile image or null if one was not set
*/
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<Waypoint.Property> 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<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> 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<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> getAttributesFromArtifactAsMap(BlackboardArtifact artifact) throws GeoLocationDataException {
Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap = new HashMap<>();
try {
List<BlackboardAttribute> 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.
*
* List will include artifacts of type: TSK_GPS_TRACKPOINT TSK_GPS_SEARCH
* TSK_GPS_LAST_KNOWN_LOCATION TSK_GPS_BOOKMARK TSK_METADATA_EXIF
*
* @param skCase Currently open SleuthkitCase
*
* @return List of Waypoint
*
* @throws GeoLocationDataException
*/
public static List<Waypoint> getAllWaypoints(SleuthkitCase skCase) throws GeoLocationDataException {
List<Waypoint> points = new ArrayList<>();
points.addAll(getTrackpointWaypoints(skCase));
points.addAll(getEXIFWaypoints(skCase));
points.addAll(getSearchWaypoints(skCase));
points.addAll(getLastKnownWaypoints(skCase));
points.addAll(getBookmarkWaypoints(skCase));
return points;
}
/**
* Gets a list of Waypoints for TSK_GPS_TRACKPOINT artifacts.
*
* @param skCase Currently open SleuthkitCase
*
* @return List of Waypoint
*
* @throws GeoLocationDataException
*/
public static List<Waypoint> getTrackpointWaypoints(SleuthkitCase skCase) throws GeoLocationDataException {
List<BlackboardArtifact> artifacts = null;
try {
artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT);
} catch (TskCoreException ex) {
throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_TRACKPOINT", ex);
}
List<Waypoint> points = new ArrayList<>();
for (BlackboardArtifact artifact : artifacts) {
try {
Waypoint point = new TrackpointWaypoint(artifact);
points.add(point);
} catch (GeoLocationDataException ex) {
logger.log(Level.WARNING, String.format("No longitude or latitude available for TSK_GPS_TRACKPOINT artifactID: %d", artifact.getArtifactID()));
}
}
return points;
}
/**
* Gets a list of Waypoints for TSK_METADATA_EXIF artifacts.
*
* @param skCase Currently open SleuthkitCase
*
* @return List of Waypoint
*
* @throws GeoLocationDataException
*/
static public List<Waypoint> getEXIFWaypoints(SleuthkitCase skCase) throws GeoLocationDataException {
List<BlackboardArtifact> artifacts = null;
try {
artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF);
} catch (TskCoreException ex) {
throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_LAST_KNOWN_LOCATION", ex);
}
List<Waypoint> points = new ArrayList<>();
if (artifacts != null) {
for (BlackboardArtifact artifact : artifacts) {
try {
Waypoint point = new EXIFWaypoint(artifact);
points.add(point);
} catch (GeoLocationDataException ex) {
// I am a little relucant to log this error because I suspect
// this will happen more often than not. It is valid for
// METADAT_EXIF to not have longitude and latitude
}
}
}
return points;
}
/**
* Gets a list of Waypoints for TSK_GPS_SEARCH artifacts.
*
* @param skCase Currently open SleuthkitCase
*
* @return List of Waypoint
*
* @throws GeoLocationDataException
*/
public static List<Waypoint> getSearchWaypoints(SleuthkitCase skCase) throws GeoLocationDataException {
List<BlackboardArtifact> artifacts = null;
try {
artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH);
} catch (TskCoreException ex) {
throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_SEARCH", ex);
}
List<Waypoint> points = new ArrayList<>();
if (artifacts != null) {
for (BlackboardArtifact artifact : artifacts) {
try {
Waypoint point = new SearchWaypoint(artifact);
points.add(point);
} catch (GeoLocationDataException ex) {
logger.log(Level.WARNING, String.format("No longitude or latitude available for TSK_GPS_SEARCH artifactID: %d", artifact.getArtifactID()));
}
}
}
return points;
}
/**
* Gets a list of Waypoints for TSK_GPS_LAST_KNOWN_LOCATION artifacts.
*
* @param skCase Currently open SleuthkitCase
*
* @return List of Waypoint
*
* @throws GeoLocationDataException
*/
public static List<Waypoint> getLastKnownWaypoints(SleuthkitCase skCase) throws GeoLocationDataException {
List<BlackboardArtifact> artifacts = null;
try {
artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION);
} catch (TskCoreException ex) {
throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_LAST_KNOWN_LOCATION", ex);
}
List<Waypoint> points = new ArrayList<>();
if (artifacts != null) {
for (BlackboardArtifact artifact : artifacts) {
try {
Waypoint point = new LastKnownWaypoint(artifact);
points.add(point);
} catch (GeoLocationDataException ex) {
logger.log(Level.WARNING, String.format("No longitude or latitude available for TSK_GPS_LAST_KNOWN_LOCATION artifactID: %d", artifact.getArtifactID()));
}
}
}
return points;
}
/**
* Gets a list of Waypoints for TSK_GPS_BOOKMARK artifacts.
*
* @param skCase Currently open SleuthkitCase
*
* @return List of Waypoint
*
* @throws GeoLocationDataException
*/
public static List<Waypoint> getBookmarkWaypoints(SleuthkitCase skCase) throws GeoLocationDataException {
List<BlackboardArtifact> artifacts = null;
try {
artifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK);
} catch (TskCoreException ex) {
throw new GeoLocationDataException("Unable to get artifacts for type: TSK_GPS_BOOKMARK", ex);
}
List<Waypoint> points = new ArrayList<>();
if (artifacts != null) {
for (BlackboardArtifact artifact : artifacts) {
try {
Waypoint point = new Waypoint(artifact);
points.add(point);
} catch (GeoLocationDataException ex) {
logger.log(Level.WARNING, String.format("No longitude or latitude available for TSK_GPS_BOOKMARK artifactID: %d", artifact.getArtifactID()));
}
}
}
return points;
}
/**
* Get a list of Waypoint.Property objects for the given artifact. This list
* 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 public List<Waypoint.Property> createGeolocationProperties(Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap) throws GeoLocationDataException {
List<Waypoint.Property> list = new ArrayList<>();
Set<BlackboardAttribute.ATTRIBUTE_TYPE> keys = new HashSet<>(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
* informational.
*/
public static final 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.
*/
private 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;
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 864 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 B

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013-2018 Basis Technology Corp.
* Copyright 2013-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -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;
@ -66,12 +67,16 @@ final class AddContentToHashDbAction extends AbstractAction implements Presenter
"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) {
@ -83,6 +88,19 @@ final class AddContentToHashDbAction extends AbstractAction implements Presenter
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<AbstractFile> selectedFiles) {
return new AddContentToHashDbMenu(selectedFiles);
}
@Override
public JMenuItem getPopupPresenter() {
return new AddContentToHashDbMenu();
@ -96,10 +114,14 @@ final class AddContentToHashDbAction extends AbstractAction implements Presenter
// action.
private final class AddContentToHashDbMenu extends JMenu {
AddContentToHashDbMenu() {
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<AbstractFile> selectedFiles) {
super(SINGLE_SELECTION_NAME);
// Get any AbstractFile objects from the lookup of the currently focused top component.
final Collection<? extends AbstractFile> selectedFiles = Utilities.actionsGlobalContext().lookupAll(AbstractFile.class);
int numberOfFilesSelected = selectedFiles.size();
// Disable the menu if file ingest is in progress.
@ -109,16 +131,13 @@ final class AddContentToHashDbAction extends AbstractAction implements Presenter
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.
@ -137,7 +156,26 @@ final class AddContentToHashDbAction extends AbstractAction implements Presenter
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<AbstractFile> 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.
@ -159,23 +197,16 @@ final class AddContentToHashDbAction extends AbstractAction implements Presenter
empty.setEnabled(false);
add(empty);
}
}
/**
* 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)));
// 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);
}
/**

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2017 Basis Technology Corp.
* Copyright 2011-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -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;
}

View File

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

View File

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

View File

@ -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);
}
/**
@ -93,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);
/*

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018-2018 Basis Technology Corp.
* Copyright 2018-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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; //needs to be the same memory address and not a copy of the contents since we are filling it in
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;

View File

@ -2,16 +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 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 machine translation software did not return any text.
# {0} - exception message
TranslatedContentViewer.errorExtractingText=An error occurred while extracting the text ({0}).
TranslatedContentViewer.extractingText=Extracting text, please wait...
TranslatedContentViewer.fileHasNoText=File has no text.
TranslatedContentViewer.noServiceProvider=The machine translation software was not found.
TranslatedContentViewer.translatingText=Translating text, please wait...
TranslatedContentViewer.translationException=Error encountered while attempting translation.
# {0} - exception message
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.

View File

@ -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,74 @@ public final class TranslatedTextViewer implements TextViewer {
}
/**
* Fetches file text and performs translation.
* Extracts text from a file and optionally translates it.
*/
private class BackgroundTranslationTask extends SwingWorker<String, Void> {
private class ExtractAndTranslateTextTask extends SwingWorker<String, Void> {
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.extractingText=Extracting text, please wait...",
"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.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 {
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_extractingText(), ComponentOrientation.LEFT_TO_RIGHT, Font.ITALIC);
});
String fileText;
try {
fileText = getFileText(file);
} catch (IOException | TextExtractor.InitReaderException 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());
}
}
/**
* 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);
if (this.isCancelled()) {
throw new InterruptedException();
}
if (fileText == null || fileText.isEmpty()) {
return Bundle.TranslatedContentViewer_fileHasNoText();
}
if (!this.translateText) {
return fileText;
}
SwingUtilities.invokeLater(() -> {
panel.display(Bundle.TranslatedContentViewer_translatingText(), ComponentOrientation.LEFT_TO_RIGHT, Font.ITALIC);
});
String translation;
try {
translation = translate(fileText);
} catch (NoServiceProviderException 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 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
@ -227,8 +240,12 @@ 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, 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);
}
}
@ -240,36 +257,21 @@ 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."})
private String translate(String input) throws InterruptedException {
if (this.isCancelled()) {
throw new InterruptedException();
}
panel.display(Bundle.TranslatedContentViewer_translatingText(),
ComponentOrientation.LEFT_TO_RIGHT, Font.ITALIC);
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, "Error translating text", ex);
return Bundle.TranslatedContentViewer_translationException() + " (" + ex.getMessage() + ")";
"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);
if (translatedResult.isEmpty()) {
return Bundle.TranslatedContentViewer_emptyTranslation();
}
return translatedResult;
}
/**
* Extracts text from the given node
*
* @param node Selected node in UI
* @param file Selected node in UI
*
* @return Extracted text
*
@ -277,33 +279,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 +339,7 @@ public final class TranslatedTextViewer implements TextViewer {
textBuilder.append(cbuf, 0, read);
bytesRead += read;
}
return textBuilder.toString();
}
@ -399,23 +390,28 @@ public final class TranslatedTextViewer implements TextViewer {
*/
private abstract class SelectionChangeListener implements ActionListener {
public String currentSelection = null;
private String currentSelection;
public abstract String getSelection();
abstract String getSelection();
@Override
public final void actionPerformed(ActionEvent e) {
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);
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
//the number of jobs running to one.
executorService.execute(updateTask);
executorService.execute(backgroundTask);
}
}
}
@ -426,7 +422,7 @@ public final class TranslatedTextViewer implements TextViewer {
private class DisplayDropDownChangeListener extends SelectionChangeListener {
@Override
public String getSelection() {
String getSelection() {
return panel.getDisplayDropDownSelection();
}
}
@ -437,7 +433,7 @@ public final class TranslatedTextViewer implements TextViewer {
private class OCRDropdownChangeListener extends SelectionChangeListener {
@Override
public String getSelection() {
String getSelection() {
return panel.getSelectedOcrLanguagePack();
}
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2015-2018 Basis Technology Corp.
* Copyright 2015-2019 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -178,6 +178,20 @@ final class FileExportRuleSet implements Serializable, Comparable<FileExportRule
this.artifactConditions = new ArrayList<>();
}
/**
* 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.
*
@ -336,13 +350,13 @@ final class FileExportRuleSet implements Serializable, Comparable<FileExportRule
if (!Objects.equals(this.fileTypeCondition, that.getFileMIMETypeCondition())) {
return false;
}
this.fileSizeConditions.sort(null);
that.fileSizeConditions.sort(null);
this.sortFileSizeConditions();
that.sortFileSizeConditions();
if (!this.fileSizeConditions.equals(that.getFileSizeConditions())) {
return false;
}
this.artifactConditions.sort(null);
that.artifactConditions.sort(null);
this.sortArtifactConditions();
that.sortArtifactConditions();
return this.artifactConditions.equals(that.getArtifactConditions());
}
@ -376,8 +390,8 @@ final class FileExportRuleSet implements Serializable, Comparable<FileExportRule
List<Long> evaluate(long dataSourceId) throws ExportRulesException {
try {
SleuthkitCase db = Case.getCurrentCaseThrows().getSleuthkitCase();
try (SleuthkitCase.CaseDbQuery queryResult = db.executeQuery(getQuery(dataSourceId))) {
ResultSet resultSet = queryResult.getResultSet();
try (SleuthkitCase.CaseDbQuery queryResult = db.executeQuery(getQuery(dataSourceId));
ResultSet resultSet = queryResult.getResultSet();) {
List<Long> fileIds = new ArrayList<>();
while (resultSet.next()) {
fileIds.add(resultSet.getLong("obj_id"));
@ -889,7 +903,7 @@ final class FileExportRuleSet implements Serializable, Comparable<FileExportRule
* @return The value, may be null.
*/
byte[] getByteValue() {
return this.byteValue;
return this.byteValue.clone();
}
/**
@ -908,7 +922,7 @@ final class FileExportRuleSet implements Serializable, Comparable<FileExportRule
* @return The value, may be null.
*/
String getStringRepresentationOfValue() {
String valueText = "";
String valueText;
switch (this.attributeValueType) {
case BYTE:
valueText = new String(Hex.encodeHex(getByteValue()));
@ -1069,20 +1083,20 @@ final class FileExportRuleSet implements Serializable, Comparable<FileExportRule
}
SleuthkitCase caseDb = currentCase.getSleuthkitCase();
BlackboardArtifact.Type artifactType;
BlackboardAttribute.Type attributeType;
try {
artifactType = caseDb.getArtifactType(artifactTypeName);
} catch (TskCoreException ex) {
throw new ExportRulesException(String.format("The specified %s artifact type does not exist in case database for %s", artifactTypeName, currentCase.getCaseDirectory()), ex);
}
BlackboardAttribute.Type attributeType;
try {
attributeType = caseDb.getAttributeType(attributeTypeName);
} catch (TskCoreException ex) {
throw new ExportRulesException(String.format("The specified %s attribute type does not exist in case database for %s", attributeTypeName, currentCase.getCaseDirectory()), ex);
}
String clause = String.format("files.obj_id = arts%d.obj_id AND arts%d.artifact_type_id = %d AND attrs%d.artifact_id = arts%d.artifact_id AND attrs%d.attribute_type_id = %d AND ",
index, index, artifactType.getTypeID(), index, index, index, attributeType.getTypeID());
index, index, artifactType.getTypeID(), index, index, index, attributeType.getTypeID());
switch (this.attributeValueType) {
case INTEGER:
clause += String.format("attrs%d.value_int32 %s %d", index, this.op.getSymbol(), this.intValue);
@ -1100,7 +1114,9 @@ final class FileExportRuleSet implements Serializable, Comparable<FileExportRule
clause += String.format("attrs%d.value_byte %s decode('%s', 'hex')", index, this.op.getSymbol(), new String(Hex.encodeHex(getByteValue())));
break;
case DATETIME:
clause += String.format("attrs%d.value_int64 %s '%s'", index, this.op.getSymbol(), this.dateTimeValue.getMillis()/1000);
clause += String.format("attrs%d.value_int64 %s '%s'", index, this.op.getSymbol(), this.dateTimeValue.getMillis() / 1000);
break;
default:
break;
}
return clause;

View File

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

View File

@ -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,242 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer):
contactsDb.close()
## Adds a 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.
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"), fromId,
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"), fromId,
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"), callerId,
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"), callerId,
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 +430,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 +448,6 @@ class FBMessengerAnalyzer(general.AndroidComponentAnalyzer):
return
self.analyzeContacts(dataSource, fileManager, context)
self.analyzeMessages(dataSource, fileManager, context)
self.analyzeMessagesAndCallLogs(dataSource, fileManager, context)

View File

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

View File

@ -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
"""
)
)

View File

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

View File

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

View File

@ -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])

View File

@ -42,6 +42,7 @@ import org.sleuthkit.autopsy.recentactivity.BinaryCookieReader.Cookie;
*/
final class BinaryCookieReader implements Iterable<Cookie> {
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<Cookie> {
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<Cookie> {
*/
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<Cookie> {
* open.
*
* @param cookieFile binarycookie file
*
* @return An instance of the reader
*
* @throws FileNotFoundException
* @throws IOException
*/
@ -229,6 +230,7 @@ final class BinaryCookieReader implements Iterable<Cookie> {
* 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<Cookie> {
* 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) {

View File

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

View File

@ -6,6 +6,8 @@ Overview
This is the User's Guide for the <a href="http://www.sleuthkit.org/autopsy/">open source Autopsy platform</a>. 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 <a href="http://www.sleuthkit.org/autopsy/docs/api-docs/"> developer's guide</a> 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:

View File

@ -772,6 +772,9 @@ INPUT = main.dox \
regressionTesting.dox \
native_libs.dox \
modDevPython.dox \
modFileIngestTutorial.dox \
modDSIngestTutorial.dox \
modReportModuleTutorial.dox \
debugTsk.dox \
../../Core/src \
../../CoreLibs/src \
@ -867,7 +870,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

View File

@ -1,5 +1,5 @@
<hr/>
<p><i>Copyright &#169; 2012-2018 Basis Technology. Generated on: $date<br/>
<p><i>Copyright &#169; 2012-2019 Basis Technology. Generated on: $date<br/>
This work is licensed under a
<a rel="license" href="http://creativecommons.org/licenses/by-sa/3.0/us/">Creative Commons Attribution-Share Alike 3.0 United States License</a>.
</i></p>

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Some files were not shown because too many files have changed in this diff Show More