Merge branch 'develop' of github.com:sleuthkit/autopsy into 5322-macos-travis-2

This commit is contained in:
esaunders 2019-10-30 16:16:09 -04:00
commit 810e49a033
37 changed files with 2402 additions and 760 deletions

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

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

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

@ -637,7 +637,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
* thumb at the given width and height. It also paints the track blue as
* the thumb progresses.
*
* @param b JSlider component
* @param slider JSlider component
* @param config Configuration object. Contains info about thumb
* dimensions and colors.
*/

View File

@ -23,32 +23,42 @@ import org.openide.nodes.Node;
import org.sleuthkit.datamodel.BlackboardArtifact;
/**
* Utility classes for content viewers. In theory, this would live in the
* contentviewer package, but the initial method was needed only be viewers in
* corecomponents and therefore can stay out of public API.
* Utility methods for content viewers.
*/
public class DataContentViewerUtility {
/**
* Returns the first non-Blackboard Artifact from a Node. Needed for (at
* least) Hex and Strings that want to view all types of content (not just
* AbstractFile), but don't want to display an artifact unless that's the
* only thing there. Scenario is hash hit or interesting item hit.
* Gets a Content object from the Lookup of a display Node object,
* preferring to return any Content object other than a BlackboardArtifact
* object.
*
* @param node Node passed into content viewer
* This method was written with the needs of the hex and strings content
* viewers in mind - the algorithm is exactly what those viewers require.
*
* @return highest priority content or null if there is no content
* @param node A display Node object.
*
* @return If there are multiple Content objects associated with the Node,
* the first Content object that is not a BlackboardArtifact object
* is returned. If no Content objects other than artifacts are found,
* the first BlackboardArtifact object found is returned. If no
* Content objects are found, null is returned.
*/
public static Content getDefaultContent(Node node) {
Content bbContentSeen = null;
for (Content content : (node).getLookup().lookupAll(Content.class)) {
if (content instanceof BlackboardArtifact) {
bbContentSeen = content;
Content artifact = null;
for (Content content : node.getLookup().lookupAll(Content.class)) {
if (content instanceof BlackboardArtifact && artifact == null) {
artifact = content;
} else {
return content;
}
}
return bbContentSeen;
return artifact;
}
/*
* Private constructor to prevent instantiation of utility class.
*/
private DataContentViewerUtility() {
}
}

View File

@ -0,0 +1,197 @@
/*
* 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.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Representation of a Waypoint created from a BlackboardArtifact.
*
*/
class ArtifactWaypoint implements Waypoint {
final private Long timestamp;
final private Double longitude;
final private Double latitude;
final private Double altitude;
final private String label;
final private AbstractFile image;
final private BlackboardArtifact artifact;
// This list is not expected to change after construction so the
// constructor will take care of creating an unmodifiable List
final private List<Waypoint.Property> immutablePropertiesList;
/**
* Construct a waypoint with the given artifact.
*
* @param artifact BlackboardArtifact for this waypoint
*
* @throws GeoLocationDataException Exception will be thrown if artifact did
* not have a valid longitude and latitude.
*/
ArtifactWaypoint(BlackboardArtifact artifact) throws GeoLocationDataException {
this(artifact,
getAttributesFromArtifactAsMap(artifact));
}
/**
* Constructor that sets all of the member variables.
*
* @param artifact BlackboardArtifact for this waypoint
* @param label String waypoint label
* @param timestamp Long timestamp, unix/java epoch seconds
* @param latitude Double waypoint latitude
* @param longitude Double waypoint longitude
* @param altitude Double waypoint altitude
* @param image AbstractFile image for waypoint, this maybe null
* @param attributeMap A Map of attributes for the given artifact
*
* @throws GeoLocationDataException Exception will be thrown if artifact did
* not have a valid longitude and latitude.
*/
ArtifactWaypoint(BlackboardArtifact artifact, String label, Long timestamp, Double latitude, Double longitude, Double altitude, AbstractFile image, Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap) throws GeoLocationDataException {
if (longitude == null || latitude == null) {
throw new GeoLocationDataException("Invalid waypoint, null value passed for longitude or latitude");
}
this.artifact = artifact;
this.label = label;
this.image = image;
this.timestamp = timestamp;
this.longitude = longitude;
this.latitude = latitude;
this.altitude = altitude;
immutablePropertiesList = Collections.unmodifiableList(Waypoint.createGeolocationProperties(attributeMap));
}
/**
* Constructs a new ArtifactWaypoint.
*
* @param artifact BlackboardArtifact for this waypoint
* @param attributeMap A Map of the BlackboardAttributes for the given
* artifact.
*
* @throws GeoLocationDataException
*/
private ArtifactWaypoint(BlackboardArtifact artifact, Map<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);
}
/**
* Get the BlackboardArtifact that this waypoint represents.
*
* @return BlackboardArtifact for this waypoint.
*/
BlackboardArtifact getArtifact() {
return artifact;
}
@Override
public Long getTimestamp() {
return timestamp;
}
@Override
public String getLabel() {
return label;
}
@Override
public Double getLatitude() {
return latitude;
}
@Override
public Double getLongitude() {
return longitude;
}
@Override
public Double getAltitude() {
return altitude;
}
@Override
public AbstractFile getImage() {
return image;
}
@Override
public List<Waypoint.Property> getOtherProperties() {
return immutablePropertiesList;
}
/**
* Gets the label for this waypoint.
*
* @param artifact BlackboardArtifact for waypoint
*
* @return Returns a label for the waypoint, or empty string if no label was
* found.
*/
private static String getLabelFromArtifact(Map<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;
}
}

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 ArtifactWaypoint {
/**
* 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);
}
/**
* 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 ArtifactWaypoint {
/**
* 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);
}
/**
* 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,189 @@
/*
*
* 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 some routes may have
* more that two points.
*
*/
public final class Route {
private final List<Waypoint> points;
private final Long timestamp;
private final Double altitude;
// This list is not expected to change after construction so the
// constructor will take care of creating an unmodifiable List
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 = ArtifactWaypoint.getAttributesFromArtifactAsMap(artifact);
points.add(getRouteStartPoint(attributeMap));
points.add(getRouteEndPoint(attributeMap));
BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE);
altitude = attribute != null ? attribute.getValueDouble() : null;
attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME);
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 timestamp for this Route
*
* @return The timestamp (java/unix epoch seconds) or null if none was set.
*/
public Long getTimestamp() {
return timestamp;
}
/**
* Get the altitude for this route.
*
* @return The Double altitude value or null if none was set.
*/
public Double getAltitude() {
return altitude;
}
/**
* Get the "Other attributes" for this route. The map will contain display
* name, formatted value pairs. This list is unmodifiable.
*
* @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();
}
/**
* Get the route start point.
*
* @param attributeMap Map of artifact attributes for this waypoint
*
* @return Start RoutePoint
*
* @throws GeoLocationDataException when longitude or latitude is null
*/
@Messages({
"Route_Start_Label=Start"
})
private Waypoint getRouteStartPoint(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);
if (latitude != null && longitude != null) {
return new RoutePoint(this, latitude.getValueDouble(), longitude.getValueDouble(), Bundle.Route_Start_Label());
} else {
throw new GeoLocationDataException("Unable to create route start point, invalid longitude and/or latitude");
}
}
/**
* Get the route End point.
*
* @param attributeMap Map of artifact attributes for this waypoint
*
* @return End RoutePoint or null if valid longitude and latitude are not
* found
*
* @throws GeoLocationDataException when longitude or latitude is null
*/
@Messages({
"Route_End_Label=End"
})
private Waypoint getRouteEndPoint(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);
if (latitude != null && longitude != null) {
return new RoutePoint(this, latitude.getValueDouble(), longitude.getValueDouble(), Bundle.Route_End_Label());
}else {
throw new GeoLocationDataException("Unable to create route end point, invalid longitude and/or latitude");
}
}
}

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.List;
import org.sleuthkit.datamodel.AbstractFile;
/**
* A point in a Route. For future use this point will have a pointer to its
* parent route.
*/
final class RoutePoint implements Waypoint {
private final Route parent;
private final Double longitude;
private final Double latitude;
private final String label;
/**
* Construct a route for a route.
*
* @param parent The parent route object.
* @param latitude Latitude for point
* @param longitude Longitude for point
* @param label Way point label.
*/
RoutePoint(Route parent, double latitude, double longitude, String label) {
this.longitude = longitude;
this.latitude = latitude;
this.label = label;
this.parent = parent;
}
@Override
public Long getTimestamp() {
return parent.getTimestamp();
}
@Override
public String getLabel() {
return label;
}
@Override
public Double getLatitude() {
return latitude;
}
@Override
public Double getLongitude() {
return longitude;
}
@Override
public Double getAltitude() {
return parent.getAltitude();
}
@Override
public List<Waypoint.Property> getOtherProperties() {
return parent.getOtherProperties();
}
@Override
public AbstractFile getImage() {
return null;
}
}

View File

@ -0,0 +1,76 @@
/*
* 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 ArtifactWaypoint.
*/
final class SearchWaypoint extends ArtifactWaypoint {
/**
* 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);
}
/**
* 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 ArtifactWaypoint {
/**
* 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);
}
/**
* 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,359 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 Basis Technology Corp.
* contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.geolocation.datamodel;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
/**
* The basic details of a waypoint.
*
*/
public interface Waypoint {
static final Logger logger = Logger.getLogger(Waypoint.class.getName());
/**
* This is a list of attributes that are already being handled by the
* waypoint classes and will have get functions.
*/
BlackboardAttribute.ATTRIBUTE_TYPE[] ALREADY_HANDLED_ATTRIBUTES = {
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME,
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE,
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE,
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE,
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME,
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED,
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START,
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START,
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END,
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END,};
/**
* Interface to describe a waypoint. A waypoint is made up of
* a longitude, latitude, label, timestamp, type, image and altitude.
*
* A good way point should have at minimum a longitude and latutude.
*
* @return Timestamp in java/unix epoch seconds or null if none was set.
*/
Long getTimestamp();
/**
* Get the label for this point object.
*
* @return String label for the point or null if none was set
*/
String getLabel();
/**
* Get the latitude for this point.
*
* @return Returns the latitude for the point or null if none was set
*/
Double getLatitude();
/**
* Get the longitude for this point.
*
* @return Returns the longitude for the point or null if none was set
*/
Double getLongitude();
/**
* Get the altitude for this point.
*
* @return Returns the altitude for the point or null if none was set
*/
Double getAltitude();
/**
* Gets an unmodifiable List of other properties that may be interesting to this way point.
* The List will not include properties for which getter functions
* exist.
*
* @return A List of waypoint properties
*/
List<Property> getOtherProperties();
/**
* Get the image for this waypoint.
*
* @return AbstractFile image or null if one was not set
*/
AbstractFile getImage();
/**
* 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
*/
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
*/
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{
ArtifactWaypoint point = new TrackpointWaypoint(artifact);
points.add(point);
} catch(GeoLocationDataException ex) {
logger.log(Level.WARNING, String.format("No longitude or latitude available for TSK_GPS_TRACKPOINT artifactID: %d", artifact.getArtifactID()));
}
}
return points;
}
/**
* Gets a list of Waypoints for TSK_METADATA_EXIF artifacts.
*
* @param skCase Currently open SleuthkitCase
*
* @return List of Waypoint
*
* @throws GeoLocationDataException
*/
static 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{
ArtifactWaypoint point = new EXIFWaypoint(artifact);
points.add(point);
} catch(GeoLocationDataException ex) {
// I am a little relucant to log this error because I suspect
// this will happen more often than not. It is valid for
// METADAT_EXIF to not have longitude and latitude
}
}
}
return points;
}
/**
* Gets a list of Waypoints for TSK_GPS_SEARCH artifacts.
*
* @param skCase Currently open SleuthkitCase
*
* @return List of Waypoint
*
* @throws GeoLocationDataException
*/
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{
ArtifactWaypoint point = new SearchWaypoint(artifact);
points.add(point);
} catch(GeoLocationDataException ex) {
logger.log(Level.WARNING, String.format("No longitude or latitude available for TSK_GPS_SEARCH artifactID: %d", artifact.getArtifactID()));
}
}
}
return points;
}
/**
* Gets a list of Waypoints for TSK_GPS_LAST_KNOWN_LOCATION artifacts.
*
* @param skCase Currently open SleuthkitCase
*
* @return List of Waypoint
*
* @throws GeoLocationDataException
*/
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{
ArtifactWaypoint point = new LastKnownWaypoint(artifact);
points.add(point);
} catch(GeoLocationDataException ex) {
logger.log(Level.WARNING, String.format("No longitude or latitude available for TSK_GPS_LAST_KNOWN_LOCATION artifactID: %d", artifact.getArtifactID()));
}
}
}
return points;
}
/**
* Gets a list of Waypoints for TSK_GPS_BOOKMARK artifacts.
*
* @param skCase Currently open SleuthkitCase
*
* @return List of Waypoint
*
* @throws GeoLocationDataException
*/
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{
ArtifactWaypoint point = new ArtifactWaypoint(artifact);
points.add(point);
} catch(GeoLocationDataException ex) {
logger.log(Level.WARNING, String.format("No longitude or latitude available for TSK_GPS_BOOKMARK artifactID: %d", artifact.getArtifactID()));
}
}
}
return points;
}
/**
* Get a list of Waypoint.Property objects for the given artifact. This list
* will not include attributes that the Waypoint interfact has get functions
* for.
*
* @param artifact Blackboard artifact to get attributes\properties from
*
* @return A List of Waypoint.Property objects
*
* @throws GeoLocationDataException
*/
static List<Waypoint.Property> createGeolocationProperties(Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap) throws GeoLocationDataException {
List<Waypoint.Property> list = new ArrayList<>();
Set<BlackboardAttribute.ATTRIBUTE_TYPE> keys = attributeMap.keySet();
for (BlackboardAttribute.ATTRIBUTE_TYPE type : ALREADY_HANDLED_ATTRIBUTES) {
keys.remove(type);
}
for (BlackboardAttribute.ATTRIBUTE_TYPE type : keys) {
String key = type.getDisplayName();
String value = attributeMap.get(type).getDisplayString();
list.add(new Waypoint.Property(key, value));
}
return list;
}
/**
* Simple property class for waypoint properties that a purely
* informational.
*/
class Property {
private final String displayName;
private final String value;
/**
* Construct a Property object.
*
* @param displayName String display name for property. Ideally not null
* or empty string.
* @param value String value for property. Can be null.
*/
Property(String displayName, String value) {
this.displayName = displayName;
this.value = value;
}
/**
* Get the display name for this property.
*
* @return String display name.
*/
public String getDisplayName() {
return displayName;
}
/**
* Get the property value.
*
* @return String value.
*/
public String getValue() {
return value;
}
}
}

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

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

@ -21,13 +21,10 @@ package org.sleuthkit.autopsy.report.modules.kml;
import org.sleuthkit.autopsy.report.GeneralReportModule;
import javax.swing.JPanel;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.*;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.datamodel.BlackboardArtifact;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@ -36,6 +33,7 @@ import java.io.OutputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.logging.Level;
import org.jdom2.Document;
import org.jdom2.Element;
@ -44,10 +42,18 @@ import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;
import org.jdom2.CDATA;
import org.openide.filesystems.FileUtil;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationDataException;
import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint;
import org.sleuthkit.autopsy.geolocation.datamodel.Route;
import org.sleuthkit.autopsy.report.ReportBranding;
import org.sleuthkit.autopsy.report.ReportProgressPanel;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.ReadContentInputStream;
import org.sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Generates a KML file based on geospatial information from the BlackBoard.
@ -63,7 +69,14 @@ class KMLReport implements GeneralReportModule {
private SleuthkitCase skCase;
private final SimpleDateFormat kmlDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
private Namespace ns;
private final String SEP = "<br>";
private final static String HTML_PROP_FORMAT = "<b>%s: </b>%s<br>";
private Element gpsExifMetadataFolder;
private Element gpsBookmarksFolder;
private Element gpsLastKnownLocationFolder;
private Element gpsRouteFolder;
private Element gpsSearchesFolder;
private Element gpsTrackpointsFolder;
private enum FeatureColor {
RED("style.kml#redFeature"),
@ -101,7 +114,7 @@ class KMLReport implements GeneralReportModule {
* @param baseReportDir path to save the report
* @param progressPanel panel to update the report's progress
*/
@NbBundle.Messages({
@Messages({
"KMLReport.unableToExtractPhotos=Could not extract photo information.",
"KMLReport.exifPhotoError=Could not extract photos with EXIF metadata.",
"KMLReport.bookmarkError=Could not extract Bookmark information.",
@ -111,14 +124,22 @@ class KMLReport implements GeneralReportModule {
"KMLReport.gpsRouteError=Could not extract GPS Route information.",
"KMLReport.gpsRouteDatabaseError=Could not get GPS Routes from database.",
"KMLReport.gpsSearchDatabaseError=Could not get GPS Searches from database.",
"KMLReport.trackpointError=Could not extract Trackpoint information.",
"KMLReport.trackpointDatabaseError=Could not get GPS Trackpoints from database.",
"KMLReport.trackpointError=Could not extract Trackpoint information.",
"KMLReport.trackpointDatabaseError=Could not get GPS Trackpoints from database.",
"KMLReport.stylesheetError=Error placing KML stylesheet. The .KML file will not function properly.",
"KMLReport.kmlFileWriteError=Could not write the KML file.",
"# {0} - filePath",
"KMLReport.errorGeneratingReport=Error adding {0} to case as a report.",
"KMLReport.unableToOpenCase=Exception while getting open case."
"KMLReport.unableToOpenCase=Exception while getting open case.",
"Waypoint_Bookmark_Display_String=GPS Bookmark",
"Waypoint_Last_Known_Display_String=GPS Last Known Location",
"Waypoint_EXIF_Display_String=EXIF Metadata With Location",
"Waypoint_Route_Point_Display_String=GPS Individual Route Point",
"Waypoint_Search_Display_String=GPS Search",
"Waypoint_Trackpoint_Display_String=GPS Trackpoint",
"Route_Details_Header=GPS Route"
})
@Override
public void generateReport(String baseReportDir, ReportProgressPanel progressPanel) {
try {
@ -133,272 +154,20 @@ class KMLReport implements GeneralReportModule {
progressPanel.updateStatusLabel(NbBundle.getMessage(this.getClass(), "ReportKML.progress.querying"));
String kmlFileFullPath = baseReportDir + REPORT_KML; //NON-NLS
String errorMessage = "";
skCase = currentCase.getSleuthkitCase();
progressPanel.updateStatusLabel(NbBundle.getMessage(this.getClass(), "ReportKML.progress.loading"));
ns = Namespace.getNamespace("", "http://www.opengis.net/kml/2.2"); //NON-NLS
Element kml = new Element("kml", ns); //NON-NLS
kml.addNamespaceDeclaration(Namespace.getNamespace("gx", "http://www.google.com/kml/ext/2.2")); //NON-NLS
kml.addNamespaceDeclaration(Namespace.getNamespace("kml", "http://www.opengis.net/kml/2.2")); //NON-NLS
kml.addNamespaceDeclaration(Namespace.getNamespace("atom", "http://www.w3.org/2005/Atom")); //NON-NLS
Document kmlDocument = new Document(kml);
Element document = new Element("Document", ns); //NON-NLS
kml.addContent(document);
Element name = new Element("name", ns); //NON-NLS
ReportBranding rb = new ReportBranding();
name.setText(rb.getReportTitle() + " KML"); //NON-NLS
document.addContent(name);
// Check if ingest has finished
if (IngestManager.getInstance().isIngestRunning()) {
Element ingestwarning = new Element("snippet", ns); //NON-NLS
ingestwarning.addContent(NbBundle.getMessage(this.getClass(), "ReportBodyFile.ingestWarning.text")); //NON-NLS
document.addContent(ingestwarning);
}
// Create folder structure
Element gpsExifMetadataFolder = new Element("Folder", ns); //NON-NLS
CDATA cdataExifMetadataFolder = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/camera-icon-16.png"); //NON-NLS
Element hrefExifMetadata = new Element("href", ns).addContent(cdataExifMetadataFolder); //NON-NLS
gpsExifMetadataFolder.addContent(new Element("Icon", ns).addContent(hrefExifMetadata)); //NON-NLS
Element gpsBookmarksFolder = new Element("Folder", ns); //NON-NLS
CDATA cdataBookmarks = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gpsfav.png"); //NON-NLS
Element hrefBookmarks = new Element("href", ns).addContent(cdataBookmarks); //NON-NLS
gpsBookmarksFolder.addContent(new Element("Icon", ns).addContent(hrefBookmarks)); //NON-NLS
Element gpsLastKnownLocationFolder = new Element("Folder", ns); //NON-NLS
CDATA cdataLastKnownLocation = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-lastlocation.png"); //NON-NLS
Element hrefLastKnownLocation = new Element("href", ns).addContent(cdataLastKnownLocation); //NON-NLS
gpsLastKnownLocationFolder.addContent(new Element("Icon", ns).addContent(hrefLastKnownLocation)); //NON-NLS
Element gpsRouteFolder = new Element("Folder", ns); //NON-NLS
CDATA cdataRoute = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-trackpoint.png"); //NON-NLS
Element hrefRoute = new Element("href", ns).addContent(cdataRoute); //NON-NLS
gpsRouteFolder.addContent(new Element("Icon", ns).addContent(hrefRoute)); //NON-NLS
Element gpsSearchesFolder = new Element("Folder", ns); //NON-NLS
CDATA cdataSearches = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-search.png"); //NON-NLS
Element hrefSearches = new Element("href", ns).addContent(cdataSearches); //NON-NLS
gpsSearchesFolder.addContent(new Element("Icon", ns).addContent(hrefSearches)); //NON-NLS
Element gpsTrackpointsFolder = new Element("Folder", ns); //NON-NLS
CDATA cdataTrackpoints = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-trackpoint.png"); //NON-NLS
Element hrefTrackpoints = new Element("href", ns).addContent(cdataTrackpoints); //NON-NLS
gpsTrackpointsFolder.addContent(new Element("Icon", ns).addContent(hrefTrackpoints)); //NON-NLS
gpsExifMetadataFolder.addContent(new Element("name", ns).addContent("EXIF Metadata")); //NON-NLS
gpsBookmarksFolder.addContent(new Element("name", ns).addContent("GPS Bookmarks")); //NON-NLS
gpsLastKnownLocationFolder.addContent(new Element("name", ns).addContent("GPS Last Known Location")); //NON-NLS
gpsRouteFolder.addContent(new Element("name", ns).addContent("GPS Routes")); //NON-NLS
gpsSearchesFolder.addContent(new Element("name", ns).addContent("GPS Searches")); //NON-NLS
gpsTrackpointsFolder.addContent(new Element("name", ns).addContent("GPS Trackpoints")); //NON-NLS
document.addContent(gpsExifMetadataFolder);
document.addContent(gpsBookmarksFolder);
document.addContent(gpsLastKnownLocationFolder);
document.addContent(gpsRouteFolder);
document.addContent(gpsSearchesFolder);
document.addContent(gpsTrackpointsFolder);
Document kmlDocument = setupReportDocument();
ReportProgressPanel.ReportStatus result = ReportProgressPanel.ReportStatus.COMPLETE;
/**
* In the following code, nulls are okay, and are handled when we go to
* write out the KML feature. Nulls are expected to be returned from any
* method where the artifact is not found and is handled in the
* individual feature creation methods. This is done because we don't
* know beforehand which attributes will be included for which artifact,
* as anyone could write a module that adds additional attributes to an
* artifact.
*
* If there are any issues reading the database getting artifacts and
* attributes, or any exceptions thrown during this process, a severe
* error is logged, the report is marked as "Incomplete KML Report", and
* we use a best-effort method to generate KML information on everything
* we can successfully pull out of the database.
*/
try {
for (BlackboardArtifact artifact : skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF)) {
String fileName = "";
long fileId = 0;
try {
Long timestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED);
String desc = getDescriptionFromArtifact(artifact, "EXIF Metadata With Locations"); //NON-NLS
Double lat = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE);
Double lon = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE);
Element point = makePoint(lat, lon, getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE));
if (lat != null && lat != 0.0 && lon != null && lon != 0.0) {
AbstractFile abstractFile = artifact.getSleuthkitCase().getAbstractFileById(artifact.getObjectID());
fileName = abstractFile.getName();
fileId = abstractFile.getId();
Path path;
copyFileUsingStream(abstractFile, Paths.get(baseReportDir, abstractFile.getName()).toFile());
try {
path = Paths.get(removeLeadingImgAndVol(abstractFile.getUniquePath()));
} catch (TskCoreException ex) {
path = Paths.get(abstractFile.getParentPath(), abstractFile.getName());
}
String formattedCoordinates = String.format("%.2f, %.2f", lat, lon);
if (path == null) {
path = Paths.get(abstractFile.getName());
}
gpsExifMetadataFolder.addContent(makePlacemarkWithPicture(abstractFile.getName(), FeatureColor.RED, desc, timestamp, point, path, formattedCoordinates));
}
} catch (ReadContentInputStreamException ex) {
logger.log(Level.WARNING, String.format("Error reading file '%s' (id=%d).", fileName, fileId), ex);
} catch (Exception ex) {
errorMessage = Bundle.KMLReport_unableToExtractPhotos();
logger.log(Level.SEVERE, errorMessage, ex); //NON-NLS
result = ReportProgressPanel.ReportStatus.ERROR;
}
}
} catch (TskCoreException ex) {
errorMessage = Bundle.KMLReport_exifPhotoError();
logger.log(Level.SEVERE, errorMessage, ex); //NON-NLS
result = ReportProgressPanel.ReportStatus.ERROR;
}
try {
for (BlackboardArtifact artifact : skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK)) {
try {
Long timestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME);
String desc = getDescriptionFromArtifact(artifact, "GPS Bookmark"); //NON-NLS
Double lat = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE);
Double lon = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE);
Element point = makePoint(lat, lon, getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE));
String bookmarkName = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME);
String formattedCoordinates = String.format("%.2f, %.2f", lat, lon);
gpsBookmarksFolder.addContent(makePlacemark(bookmarkName, FeatureColor.BLUE, desc, timestamp, point, formattedCoordinates));
} catch (Exception ex) {
errorMessage = Bundle.KMLReport_bookmarkError();
logger.log(Level.SEVERE, errorMessage, ex); //NON-NLS
result = ReportProgressPanel.ReportStatus.ERROR;
}
}
} catch (TskCoreException ex) {
errorMessage = Bundle.KMLReport_gpsBookmarkError();
logger.log(Level.SEVERE, errorMessage, ex); //NON-NLS
result = ReportProgressPanel.ReportStatus.ERROR;
}
try {
for (BlackboardArtifact artifact : skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION)) {
try {
Long timestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME);
String desc = getDescriptionFromArtifact(artifact, "GPS Last Known Location"); //NON-NLS
Double lat = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE);
Double lon = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE);
Double alt = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE);
Element point = makePoint(lat, lon, alt);
String formattedCoordinates = String.format("%.2f, %.2f", lat, lon);
gpsLastKnownLocationFolder.addContent(makePlacemark("Last Known Location", FeatureColor.PURPLE, desc, timestamp, point, formattedCoordinates)); //NON-NLS
} catch (Exception ex) {
errorMessage = Bundle.KMLReport_locationError();
logger.log(Level.SEVERE, errorMessage, ex); //NON-NLS
result = ReportProgressPanel.ReportStatus.ERROR;
}
}
} catch (TskCoreException ex) {
errorMessage = Bundle.KMLReport_locationDatabaseError();
logger.log(Level.SEVERE, errorMessage, ex); //NON-NLS
result = ReportProgressPanel.ReportStatus.ERROR;
}
try {
for (BlackboardArtifact artifact : skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE)) {
try {
Long timestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME);
String desc = getDescriptionFromArtifact(artifact, "GPS Route");
Double latitudeStart = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START);
Double longitudeStart = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START);
Double latitudeEnd = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END);
Double longitudeEnd = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END);
Double altitude = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE);
Element route = makeLineString(latitudeStart, longitudeStart, altitude, latitudeEnd, longitudeEnd, altitude);
Element startingPoint = makePoint(latitudeStart, longitudeStart, altitude);
Element endingPoint = makePoint(latitudeEnd, longitudeEnd, altitude);
String formattedCoordinates = String.format("%.2f, %.2f to %.2f, %.2f", latitudeStart, longitudeStart, latitudeEnd, longitudeEnd);
gpsRouteFolder.addContent(makePlacemark("As-the-crow-flies Route", FeatureColor.GREEN, desc, timestamp, route, formattedCoordinates)); //NON-NLS
formattedCoordinates = String.format("%.2f, %.2f", latitudeStart, longitudeStart);
gpsRouteFolder.addContent(makePlacemark("Start", FeatureColor.GREEN, desc, timestamp, startingPoint, formattedCoordinates)); //NON-NLS
formattedCoordinates = String.format("%.2f, %.2f", latitudeEnd, longitudeEnd);
gpsRouteFolder.addContent(makePlacemark("End", FeatureColor.GREEN, desc, timestamp, endingPoint, formattedCoordinates)); //NON-NLS
} catch (Exception ex) {
errorMessage = Bundle.KMLReport_gpsRouteError();
logger.log(Level.SEVERE, errorMessage, ex); //NON-NLS
result = ReportProgressPanel.ReportStatus.ERROR;
}
}
} catch (TskCoreException ex) {
errorMessage = Bundle.KMLReport_gpsRouteDatabaseError();
logger.log(Level.SEVERE, errorMessage, ex); //NON-NLS
result = ReportProgressPanel.ReportStatus.ERROR;
}
try {
for (BlackboardArtifact artifact : skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH)) {
Long timestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME);
String desc = getDescriptionFromArtifact(artifact, "GPS Search"); //NON-NLS
Double lat = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE);
Double lon = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE);
Double alt = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE);
Element point = makePoint(lat, lon, alt);
String formattedCoordinates = String.format("%.2f, %.2f", lat, lon);
String searchName = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME);
if (searchName == null || searchName.isEmpty()) {
searchName = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION);
}
if (searchName == null || searchName.isEmpty()) {
searchName = "GPS Search";
}
gpsSearchesFolder.addContent(makePlacemark(searchName, FeatureColor.WHITE, desc, timestamp, point, formattedCoordinates)); //NON-NLS
}
} catch (TskCoreException ex) {
errorMessage = Bundle.KMLReport_gpsSearchDatabaseError();
logger.log(Level.SEVERE, errorMessage, ex); //NON-NLS
result = ReportProgressPanel.ReportStatus.ERROR;
}
try {
for (BlackboardArtifact artifact : skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT)) {
try {
Long timestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME);
String desc = getDescriptionFromArtifact(artifact, "GPS Trackpoint"); //NON-NLS
Double lat = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE);
Double lon = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE);
Double alt = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE);
Element point = makePoint(lat, lon, alt);
String formattedCoordinates = String.format("%.2f, %.2f, %.2f", lat, lon, alt);
String trackName = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME);
if (trackName == null || trackName.isEmpty()) {
trackName = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME);
}
if (trackName == null || trackName.isEmpty()) {
trackName = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FLAG);
}
if (trackName == null || trackName.isEmpty()) {
trackName = "GPS Trackpoint";
}
gpsTrackpointsFolder.addContent(makePlacemark(trackName, FeatureColor.YELLOW, desc, timestamp, point, formattedCoordinates));
} catch (Exception ex) {
errorMessage = Bundle.KMLReport_trackpointError();
logger.log(Level.SEVERE, errorMessage, ex); //NON-NLS
result = ReportProgressPanel.ReportStatus.ERROR;
}
}
} catch (TskCoreException ex) {
errorMessage = Bundle.KMLReport_trackpointDatabaseError();
makeRoutes(skCase);
addLocationsToReport(skCase, baseReportDir);
} catch (GeoLocationDataException | IOException ex) {
errorMessage = "Failed to complete report.";
logger.log(Level.SEVERE, errorMessage, ex); //NON-NLS
result = ReportProgressPanel.ReportStatus.ERROR;
}
@ -442,247 +211,256 @@ class KMLReport implements GeneralReportModule {
}
/**
* Get a Double from an artifact if it exists, return null otherwise.
* Do all of the setting up of elements needed for the report.
*
* @param artifact The artifact to query
* @param type The attribute type we're looking for
*
* @return The Double if it exists, or null if not
* @return The report document object.
*/
private Double getDouble(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE type) {
Double returnValue = null;
try {
BlackboardAttribute bba = artifact.getAttribute(new BlackboardAttribute.Type(type));
if (bba != null) {
Double value = bba.getValueDouble();
returnValue = value;
}
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error getting Double value: " + type.toString(), ex); //NON-NLS
private Document setupReportDocument() {
ns = Namespace.getNamespace("", "http://www.opengis.net/kml/2.2"); //NON-NLS
Element kml = new Element("kml", ns); //NON-NLS
kml.addNamespaceDeclaration(Namespace.getNamespace("gx", "http://www.google.com/kml/ext/2.2")); //NON-NLS
kml.addNamespaceDeclaration(Namespace.getNamespace("kml", "http://www.opengis.net/kml/2.2")); //NON-NLS
kml.addNamespaceDeclaration(Namespace.getNamespace("atom", "http://www.w3.org/2005/Atom")); //NON-NLS
Document kmlDocument = new Document(kml);
Element document = new Element("Document", ns); //NON-NLS
kml.addContent(document);
Element name = new Element("name", ns); //NON-NLS
ReportBranding rb = new ReportBranding();
name.setText(rb.getReportTitle() + " KML"); //NON-NLS
document.addContent(name);
// Check if ingest has finished
if (IngestManager.getInstance().isIngestRunning()) {
Element ingestwarning = new Element("snippet", ns); //NON-NLS
ingestwarning.addContent(NbBundle.getMessage(this.getClass(), "ReportBodyFile.ingestWarning.text")); //NON-NLS
document.addContent(ingestwarning);
}
return returnValue;
// Create folder structure
gpsExifMetadataFolder = new Element("Folder", ns); //NON-NLS
CDATA cdataExifMetadataFolder = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/camera-icon-16.png"); //NON-NLS
Element hrefExifMetadata = new Element("href", ns).addContent(cdataExifMetadataFolder); //NON-NLS
gpsExifMetadataFolder.addContent(new Element("Icon", ns).addContent(hrefExifMetadata)); //NON-NLS
gpsBookmarksFolder = new Element("Folder", ns); //NON-NLS
CDATA cdataBookmarks = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gpsfav.png"); //NON-NLS
Element hrefBookmarks = new Element("href", ns).addContent(cdataBookmarks); //NON-NLS
gpsBookmarksFolder.addContent(new Element("Icon", ns).addContent(hrefBookmarks)); //NON-NLS
gpsLastKnownLocationFolder = new Element("Folder", ns); //NON-NLS
CDATA cdataLastKnownLocation = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-lastlocation.png"); //NON-NLS
Element hrefLastKnownLocation = new Element("href", ns).addContent(cdataLastKnownLocation); //NON-NLS
gpsLastKnownLocationFolder.addContent(new Element("Icon", ns).addContent(hrefLastKnownLocation)); //NON-NLS
gpsRouteFolder = new Element("Folder", ns); //NON-NLS
CDATA cdataRoute = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-trackpoint.png"); //NON-NLS
Element hrefRoute = new Element("href", ns).addContent(cdataRoute); //NON-NLS
gpsRouteFolder.addContent(new Element("Icon", ns).addContent(hrefRoute)); //NON-NLS
gpsSearchesFolder = new Element("Folder", ns); //NON-NLS
CDATA cdataSearches = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-search.png"); //NON-NLS
Element hrefSearches = new Element("href", ns).addContent(cdataSearches); //NON-NLS
gpsSearchesFolder.addContent(new Element("Icon", ns).addContent(hrefSearches)); //NON-NLS
gpsTrackpointsFolder = new Element("Folder", ns); //NON-NLS
CDATA cdataTrackpoints = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-trackpoint.png"); //NON-NLS
Element hrefTrackpoints = new Element("href", ns).addContent(cdataTrackpoints); //NON-NLS
gpsTrackpointsFolder.addContent(new Element("Icon", ns).addContent(hrefTrackpoints)); //NON-NLS
gpsExifMetadataFolder.addContent(new Element("name", ns).addContent("EXIF Metadata")); //NON-NLS
gpsBookmarksFolder.addContent(new Element("name", ns).addContent("GPS Bookmarks")); //NON-NLS
gpsLastKnownLocationFolder.addContent(new Element("name", ns).addContent("GPS Last Known Location")); //NON-NLS
gpsRouteFolder.addContent(new Element("name", ns).addContent("GPS Routes")); //NON-NLS
gpsSearchesFolder.addContent(new Element("name", ns).addContent("GPS Searches")); //NON-NLS
gpsTrackpointsFolder.addContent(new Element("name", ns).addContent("GPS Trackpoints")); //NON-NLS
document.addContent(gpsExifMetadataFolder);
document.addContent(gpsBookmarksFolder);
document.addContent(gpsLastKnownLocationFolder);
document.addContent(gpsRouteFolder);
document.addContent(gpsSearchesFolder);
document.addContent(gpsTrackpointsFolder);
return kmlDocument;
}
/**
* Get a Long from an artifact if it exists, return null otherwise.
* For the given point, create the data needed for the EXIF_METADATA
*
* @param artifact The artifact to query
* @param type The attribute type we're looking for
* @param location The geolocation of the data
* @param baseReportDirectory The report directory where the image will be
* created.
*
* @return The Long if it exists, or null if not
* @throws IOException
*/
private Long getLong(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE type) {
Long returnValue = null;
try {
BlackboardAttribute bba = artifact.getAttribute(new BlackboardAttribute.Type(type));
if (bba != null) {
Long value = bba.getValueLong();
returnValue = value;
void addExifMetadataContent(List<Waypoint> points, String baseReportDirectory) throws IOException {
for(Waypoint point: points) {
Element mapPoint = makePoint(point);
if (mapPoint == null) {
return;
}
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error getting Long value: " + type.toString(), ex); //NON-NLS
AbstractFile abstractFile = point.getImage();
String details = getFormattedDetails(point, Bundle.Waypoint_EXIF_Display_String());
Path path;
copyFileUsingStream(abstractFile, Paths.get(baseReportDirectory, abstractFile.getName()).toFile());
try {
path = Paths.get(removeLeadingImgAndVol(abstractFile.getUniquePath()));
} catch (TskCoreException ex) {
path = Paths.get(abstractFile.getParentPath(), abstractFile.getName());
}
if (path == null) {
path = Paths.get(abstractFile.getName());
}
gpsExifMetadataFolder.addContent(makePlacemarkWithPicture(abstractFile.getName(), FeatureColor.RED, details, point.getTimestamp(), mapPoint, path, formattedCoordinates(point.getLatitude(), point.getLongitude())));
}
return returnValue;
}
/**
* Get an Integer from an artifact if it exists, return null otherwise.
* Add the new location to the correct folder based on artifact type.
*
* @param artifact The artifact to query
* @param type The attribute type we're looking for
* @param skCase Currently open case
* @param baseReportDir Output directory for the report.
*
* @return The Integer if it exists, or null if not
* @throws TskCoreException
* @throws IOException
*/
private Integer getInteger(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE type) {
Integer returnValue = null;
try {
BlackboardAttribute bba = artifact.getAttribute(new BlackboardAttribute.Type(type));
if (bba != null) {
Integer value = bba.getValueInt();
returnValue = value;
}
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error getting Integer value: " + type.toString(), ex); //NON-NLS
void addLocationsToReport(SleuthkitCase skCase, String baseReportDir) throws GeoLocationDataException, IOException {
addExifMetadataContent(Waypoint.getEXIFWaypoints(skCase), baseReportDir);
addWaypoints(Waypoint.getBookmarkWaypoints(skCase), gpsBookmarksFolder, FeatureColor.BLUE, Bundle.Waypoint_Bookmark_Display_String());
addWaypoints(Waypoint.getLastKnownWaypoints(skCase), gpsLastKnownLocationFolder, FeatureColor.PURPLE, Bundle.Waypoint_Last_Known_Display_String());
addWaypoints(Waypoint.getSearchWaypoints(skCase), gpsSearchesFolder, FeatureColor.WHITE, Bundle.Waypoint_Search_Display_String());
addWaypoints(Waypoint.getTrackpointWaypoints(skCase), gpsTrackpointsFolder, FeatureColor.WHITE, Bundle.Waypoint_Trackpoint_Display_String());
}
/**
* For each point in the waypoint list an Element to represent the given waypoint
* is created and added it to the given Element folder.
*
* @param points List of waypoints to add to the report
* @param folder The Element folder to add the points to
* @param waypointColor The color the waypoint should appear in the report
*/
void addWaypoints(List<Waypoint> points, Element folder, FeatureColor waypointColor, String headerLabel) {
for(Waypoint point: points) {
addContent(folder, point.getLabel(), waypointColor, getFormattedDetails(point, headerLabel), point.getTimestamp(), makePoint(point), point.getLatitude(), point.getLongitude());
}
}
/**
* Adds the waypoint Element with details to the report in the given folder.
*
* @param folder Element folder to add the waypoint to
* @param waypointLabel String waypoint Label
* @param waypointColor FeatureColor for the waypoint
* @param formattedDetails String HTML formatted waypoint details
* @param timestamp Long timestamp (unix\jave epoch seconds)
* @param point Element point object
* @param latitude Double latitude value
* @param longitude Double longitude value
*/
void addContent(Element folder, String waypointLabel, FeatureColor waypointColor, String formattedDetails, Long timestamp, Element point, Double latitude, Double longitude) {
if(folder != null && point != null) {
String formattedCords = formattedCoordinates(latitude, longitude);
folder.addContent(makePlacemark(waypointLabel, waypointColor, formattedDetails, timestamp, point, formattedCords));
}
return returnValue;
}
/**
* Get a String from an artifact if it exists, return null otherwise.
* Add the route to the route folder in the document.
*
* @param artifact The artifact to query
* @param type The attribute type we're looking for
* @param skCase Currently open case.
*
* @return The String if it exists, or null if not
* @throws TskCoreException
*/
private String getString(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE type) {
String returnValue = null;
try {
BlackboardAttribute bba = artifact.getAttribute(new BlackboardAttribute.Type(type));
if (bba != null) {
String value = bba.getValueString();
if (value != null && !value.isEmpty()) {
returnValue = value;
}
}
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error getting String value: " + type.toString(), ex); //NON-NLS
void makeRoutes(SleuthkitCase skCase) throws GeoLocationDataException {
List<Route> routes = Route.getRoutes(skCase);
if(routes == null) {
return;
}
for (Route route : routes) {
addRouteToReport(route);
}
}
void addRouteToReport(Route route) {
List<Waypoint> routePoints = route.getRoute();
Waypoint start = null;
Waypoint end = null;
// This is hardcoded knowledge that there is only two points
// a start and end. In the long run it would be nice to
// support the idea of a route with multiple points. The Route
// class supports that idea. Would be nice to figure out how to support
// for report.
if (routePoints != null && routePoints.size() > 1) {
start = routePoints.get(0);
end = routePoints.get(1);
}
if (start == null || end == null) {
return;
}
Element reportRoute = makeLineString(start.getLatitude(), start.getLongitude(), end.getLatitude(), end.getLongitude());
Element startingPoint = makePoint(start.getLatitude(), start.getLongitude(), start.getAltitude());
Element endingPoint = makePoint(end.getLatitude(), end.getLongitude(), end.getAltitude());
String formattedEnd = formattedCoordinates(end.getLatitude(), end.getLongitude());
String formattedStart = formattedCoordinates(start.getLatitude(), start.getLongitude());
String formattedCoordinates = String.format("%s to %s", formattedStart, formattedEnd);
if (reportRoute != null) {
gpsRouteFolder.addContent(makePlacemark(route.getLabel(), FeatureColor.GREEN, getFormattedDetails(route), route.getTimestamp(), reportRoute, formattedCoordinates)); //NON-NLS
}
if (startingPoint != null) {
gpsRouteFolder.addContent(makePlacemark(start.getLabel(),
FeatureColor.GREEN, getFormattedDetails(start, Bundle.Waypoint_Route_Point_Display_String()),
start.getTimestamp(), startingPoint, formattedStart)); //NON-NLS
}
if (endingPoint != null) {
gpsRouteFolder.addContent(makePlacemark(end.getLabel(),
FeatureColor.GREEN,
getFormattedDetails(end, Bundle.Waypoint_Route_Point_Display_String()),
end.getTimestamp(), endingPoint, formattedEnd)); //NON-NLS
}
return returnValue;
}
/**
* This method creates a text description for a map feature using all the
* geospatial and time data we can for the Artifact. It queries the
* following attributes:
* Format a point time stamp (in seconds) to the report format.
*
* TSK_GEO_LATITUDE 54; TSK_GEO_LONGITUDE 55; TSK_GEO_LATITUDE_START 98;
* TSK_GEO_LATITUDE_END 99; TSK_GEO_LONGITUDE_START 100;
* TSK_GEO_LONGITUDE_END 101; TSK_GEO_VELOCITY 56; TSK_GEO_ALTITUDE 57;
* TSK_GEO_BEARING 58; TSK_GEO_HPRECISION 59; TSK_GEO_VPRECISION 60;
* TSK_GEO_MAPDATUM 61; TSK_DATETIME_START 83; TSK_DATETIME_END 84;
* TSK_LOCATION 86; TSK_PATH_SOURCE 94;
* @param timeStamp The timestamp in epoch seconds.
*
* @param artifact the artifact to query.
* @param featureType the type of Artifact we're working on.
*
* @return a String with the information we have available
* @return The formatted timestamp
*/
private String getDescriptionFromArtifact(BlackboardArtifact artifact, String featureType) {
StringBuilder result = new StringBuilder("<h3>" + featureType + "</h3>"); //NON-NLS
String name = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME);
if (name != null && !name.isEmpty()) {
result.append("<b>Name:</b> ").append(name).append(SEP); //NON-NLS
}
String location = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION);
if (location != null && !location.isEmpty()) {
result.append("<b>Location:</b> ").append(location).append(SEP); //NON-NLS
}
Long timestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME);
if (timestamp != null) {
result.append("<b>Timestamp:</b> ").append(getTimeStamp(timestamp)).append(SEP); //NON-NLS
result.append("<b>Unix timestamp:</b> ").append(timestamp).append(SEP); //NON-NLS
}
Long startingTimestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START);
if (startingTimestamp != null) {
result.append("<b>Starting Timestamp:</b> ").append(getTimeStamp(startingTimestamp)).append(SEP); //NON-NLS
result.append("<b>Starting Unix timestamp:</b> ").append(startingTimestamp).append(SEP); //NON-NLS
}
Long endingTimestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_END);
if (endingTimestamp != null) {
result.append("<b>Ending Timestamp:</b> ").append(getTimeStamp(endingTimestamp)).append(SEP); //NON-NLS
result.append("<b>Ending Unix timestamp:</b> ").append(endingTimestamp).append(SEP); //NON-NLS
}
Long createdTimestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED);
if (createdTimestamp != null) {
result.append("<b>Created Timestamp:</b> ").append(getTimeStamp(createdTimestamp)).append(SEP); //NON-NLS
result.append("<b>Created Unix timestamp:</b> ").append(createdTimestamp).append(SEP); //NON-NLS
}
Double latitude = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE);
if (latitude != null) {
result.append("<b>Latitude:</b> ").append(latitude).append(SEP); //NON-NLS
}
Double longitude = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE);
if (longitude != null) {
result.append("<b>Longitude:</b> ").append(longitude).append(SEP); //NON-NLS
}
Double latitudeStart = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START);
if (latitudeStart != null) {
result.append("<b>Latitude Start:</b> ").append(latitudeStart).append(SEP); //NON-NLS
}
Double longitudeStart = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START);
if (longitudeStart != null) {
result.append("<b>Longitude Start:</b> ").append(longitudeStart).append(SEP); //NON-NLS
}
Double latitudeEnd = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END);
if (latitudeEnd != null) {
result.append("<b>Latitude End:</b> ").append(latitudeEnd).append(SEP); //NON-NLS
}
Double longitudeEnd = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END);
if (longitudeEnd != null) {
result.append("<b>Longitude End:</b> ").append(longitudeEnd).append(SEP); //NON-NLS
}
Double velocity = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_VELOCITY);
if (velocity != null) {
result.append("<b>Velocity:</b> ").append(velocity).append(SEP); //NON-NLS
}
Double altitude = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE);
if (altitude != null) {
result.append("<b>Altitude:</b> ").append(altitude).append(SEP); //NON-NLS
}
Double bearing = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_BEARING);
if (bearing != null) {
result.append("<b>Bearing:</b> ").append(bearing).append(SEP); //NON-NLS
}
Integer hPrecision = getInteger(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_HPRECISION);
if (hPrecision != null) {
result.append("<b>Horizontal Precision Figure of Merit:</b> ").append(hPrecision).append(SEP); //NON-NLS
}
Integer vPrecision = getInteger(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_VPRECISION);
if (vPrecision != null) {
result.append("<b>Vertical Precision Figure of Merit:</b> ").append(vPrecision).append(SEP); //NON-NLS
}
String mapDatum = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_MAPDATUM);
if (mapDatum != null && !mapDatum.isEmpty()) {
result.append("<b>Map Datum:</b> ").append(mapDatum).append(SEP); //NON-NLS
}
String programName = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME);
if (programName != null && !programName.isEmpty()) {
result.append("<b>Reported by:</b> ").append(programName).append(SEP); //NON-NLS
}
String flag = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FLAG);
if (flag != null && !flag.isEmpty()) {
result.append("<b>Flag:</b> ").append(flag).append(SEP); //NON-NLS
}
String pathSource = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_SOURCE);
if (pathSource != null && !pathSource.isEmpty()) {
result.append("<b>Source:</b> ").append(pathSource).append(SEP); //NON-NLS
}
String deviceMake = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_MAKE);
if (deviceMake != null && !deviceMake.isEmpty()) {
result.append("<b>Device Make:</b> ").append(deviceMake).append(SEP); //NON-NLS
}
String deviceModel = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_MODEL);
if (deviceModel != null && !deviceModel.isEmpty()) {
result.append("<b>Device Model:</b> ").append(deviceModel).append(SEP); //NON-NLS
}
return result.toString();
}
private String getTimeStamp(long timeStamp) {
return kmlDateFormat.format(new java.util.Date(timeStamp * 1000));
}
/**
* Create the point for the given artifact.
*
* @param point Artifact point.
*
* @return point element.
*/
private Element makePoint(Waypoint point) {
return makePoint(point.getLatitude(), point.getLongitude(), point.getAltitude());
}
/**
* Create a Point for use in a Placemark. Note in this method altitude is
* ignored, as Google Earth apparently has trouble using altitudes for
* LineStrings, though the parameters are still in the call. Also note that
* any null value passed in will be set to 0.0, under the idea that it is
* better to show some data with gaps, than to show nothing at all.
* LineStrings, though the parameters are still in the call.
*
* @param latitude point latitude
* @param longitude point longitude
@ -691,21 +469,16 @@ class KMLReport implements GeneralReportModule {
* @return the Point as an Element
*/
private Element makePoint(Double latitude, Double longitude, Double altitude) {
if (latitude == null) {
latitude = 0.0;
}
if (longitude == null) {
longitude = 0.0;
}
if (altitude == null) {
altitude = 0.0;
if (latitude == null || longitude == null) {
return null;
}
Element point = new Element("Point", ns); //NON-NLS
// KML uses lon, lat. Deliberately reversed.
Element coordinates = new Element("coordinates", ns).addContent(longitude + "," + latitude + "," + altitude); //NON-NLS
// KML uses lon, lat. Deliberately reversed.1
Element coordinates = new Element("coordinates", ns).addContent(longitude + "," + latitude + "," + (altitude != null ? altitude : 0.0)); //NON-NLS
if (altitude != 0) {
if (altitude != null && altitude != 0) {
/*
* Though we are including a non-zero altitude, clamp it to the
* ground because inaccuracies from the GPS data can cause the
@ -725,9 +498,10 @@ class KMLReport implements GeneralReportModule {
* Create a LineString for use in a Placemark. Note in this method, start
* and stop altitudes get ignored, as Google Earth apparently has trouble
* using altitudes for LineStrings, though the parameters are still in the
* call. Also note that any null value passed in will be set to 0.0, under
* the idea that it is better to show some data with gaps, than to show
* nothing at all.
* call.
*
* If null values are pass for the latitudes or longitudes a line will not
* be drawn.
*
* @param startLatitude Starting latitude
* @param startLongitude Starting longitude
@ -738,24 +512,9 @@ class KMLReport implements GeneralReportModule {
*
* @return the Line as an Element
*/
private Element makeLineString(Double startLatitude, Double startLongitude, Double startAltitude, Double stopLatitude, Double stopLongitude, Double stopAltitude) {
if (startLatitude == null) {
startLatitude = 0.0;
}
if (startLongitude == null) {
startLongitude = 0.0;
}
if (startAltitude == null) {
startAltitude = 0.0;
}
if (stopLatitude == null) {
stopLatitude = 0.0;
}
if (stopLongitude == null) {
stopLongitude = 0.0;
}
if (stopAltitude == null) {
stopAltitude = 0.0;
private Element makeLineString(Double startLatitude, Double startLongitude, Double stopLatitude, Double stopLongitude) {
if (startLatitude == null || startLongitude == null || stopLatitude == null || stopLongitude == null) {
return null;
}
Element lineString = new Element("LineString", ns); //NON-NLS
@ -925,4 +684,104 @@ class KMLReport implements GeneralReportModule {
}
return strbuf.toString();
}
/**
* Get the nicely formatted details for the given waypoint.
*
* @param point Waypoint object
* @param header String details header
*
* @return HTML formatted String of details for given waypoint
*/
private String getFormattedDetails(Waypoint point, String header) {
StringBuilder result = new StringBuilder(); //NON-NLS
result.append(String.format("<h3>%s</h3>", header))
.append(formatAttribute("Name", point.getLabel()));
Long timestamp = point.getTimestamp();
if (timestamp != null) {
result.append(formatAttribute("Timestamp", getTimeStamp(timestamp)));
}
result.append(formatAttribute("Latitude", point.getLatitude().toString()))
.append(formatAttribute("Longitude", point.getLongitude().toString()));
if (point.getAltitude() != null) {
result.append(formatAttribute("Altitude", point.getAltitude().toString()));
}
List<Waypoint.Property> list = point.getOtherProperties();
for(Waypoint.Property prop: list) {
String value = prop.getValue();
if(value != null && !value.isEmpty()) {
result.append(formatAttribute(prop.getDisplayName(), value));
}
}
return result.toString();
}
private String formatAttribute(String title, String value) {
return String.format(HTML_PROP_FORMAT, title, value);
}
/**
* Returns an HTML formatted string of all the
*
* @param route
*
* @return
*/
private String getFormattedDetails(Route route) {
List<Waypoint> points = route.getRoute();
StringBuilder result = new StringBuilder(); //NON-NLS
result.append(String.format("<h3>%s</h3>", Bundle.Route_Details_Header()))
.append(formatAttribute("Name", route.getLabel()));
Long timestamp = route.getTimestamp();
if (timestamp != null) {
result.append(formatAttribute("Timestamp", getTimeStamp(timestamp)));
}
if (points.size() > 1) {
Waypoint start = points.get(0);
Waypoint end = points.get(1);
result.append(formatAttribute("Start Latitude", start.getLatitude().toString()))
.append(formatAttribute("Start Longitude", start.getLongitude().toString()))
.append(formatAttribute("End Latitude", end.getLatitude().toString()))
.append(formatAttribute("End Longitude", end.getLongitude().toString()));
}
if (route.getAltitude() != null) {
result.append(formatAttribute("Altitude", route.getAltitude().toString()));
}
List<Waypoint.Property> list = route.getOtherProperties();
for(Waypoint.Property prop: list) {
String value = prop.getValue();
if(value != null && !value.isEmpty()) {
result.append(formatAttribute(prop.getDisplayName(), value));
}
}
return result.toString();
}
/**
* Helper functions for consistently formatting longitude and latitude.
*
* @param latitude Double latitude value
* @param longitude Double longitude value
*
* @return String Nicely formatted double values separated by a comma
*/
private String formattedCoordinates(Double latitude, Double longitude) {
if (latitude == null || longitude == null) {
return "";
}
return String.format("%.2f, %.2f", latitude, longitude);
}
}

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

Before

Width:  |  Height:  |  Size: 233 KiB

After

Width:  |  Height:  |  Size: 233 KiB

View File

@ -9,9 +9,9 @@ If these pages don't answer your question, then send the question to the <a href
<h3>Writing Python or Java Modules</h3>
If you want to write Java or Python modules, then there are some tutorials and detailed pages in this document. The Python tutorials include:
- File Ingest Modules: http://www.basistech.com/python-autopsy-module-tutorial-1-the-file-ingest-module/
- Data Source Ingest Modules: http://www.basistech.com/python-autopsy-module-tutorial-2-the-data-source-ingest-module/
- Report Modules: http://www.basistech.com/python-autopsy-module-tutorial-3-the-report-module/
- File Ingest Modules: \subpage mod_python_file_ingest_tutorial_page
- Data Source Ingest Modules: \subpage mod_python_ds_ingest_tutorial_page
- Report Modules: \subpage mod_python_report_tutorial_page
This document contains the following pages:
- \subpage platform_page

View File

@ -0,0 +1,171 @@
/*! \page mod_python_ds_ingest_tutorial_page Python Tutorial #2: Writing a Data Source Ingest Module
In the \ref mod_python_file_ingest_tutorial_page "first tutorial" we built a basic Python Autopsy module that looked for big and round files. In this tutorial we're going to make two data source ingest modules. The first focuses on finding SQLite databases and parsing them, and the second focuses on running a command line tool on a disk image.
The main difference from the first tutorial, which focused on file ingest modules, is that these are data source ingest modules. Data source-ingest modules are given a reference to a data source and the module needs to find the files to analyze, whereas file-level ingest modules are given a reference to each file in the data source.
\section python_tutorial2_assumptions Assumptions
This post assumes you've read the \ref mod_python_file_ingest_tutorial_page "first tutorial". That means that you should know why it is better to write an Autopsy module versus a stand-alone tool, and what you need to set up (Autopsy installed, text editor, etc.). You may also recall the limitations (and benefits) of data source ingest modules. The most notable difference between them is that data source-ingest modules may not have access to carved files or files that are inside of ZIP files. For our example in this post, we are looking for a SQLite database with a specific name, and it will not be inside of a ZIP file, so data source ingest modules are the most efficient and will get us results faster.
The other assumption is that you know something about SQL queries. We have some example queries below and we don't go into detail about how they work.
\section python_tutorial2_getting_started Getting Started
\subsection python_tutorial2_folder Making Your Module Folder
We'll start by making our module folder. As we learned in the \ref mod_python_file_ingest_tutorial_page "first tutorial", every Python module in Autopsy gets its own folder. To find out where you should put your Python module, launch Autopsy and choose the Tools->Python Plugins menu item. That will open a subfolder in your AppData folder, such as "C:\Users\JDoe\AppData\Roaming\Autopsy\python_modules".
Make a folder inside of there to store your module. Call it "DemoScript2". Copy the <a href="https://github.com/sleuthkit/autopsy/blob/develop/pythonExamples/dataSourceIngestModule.py" target="_blank" rel="noopener noreferrer">dataSourcengestModule.py</a> sample file from github into the this new folder and rename it to FindContactsDb.py.
\subsection python_tutorial2_script Writing The Script
We are going to write a script that:
<ul>
<li>Queries the backend database for files of a given name</li>
<li>Opens the database</li>
<li>Queries data from the database and makes an artifact for each row</li>
</ul>
Open the FindContactsDb.py script in your favorite text editor. The sample Autopsy Python modules all have TODO entries in them to let you know what you should change. The below steps jump from one TODO to the next.
<ol>
<li><strong>Factory Class Name</strong>: The first thing to do is rename the sample class name from "SampleJythonDataSourceIngestModuleFactory" to "ContactsDbIngestModuleFactory". In the sample module, there are several uses of this class name, so you should search and replace for these strings.</li>
<li><strong>Name and Description</strong>: The next TODO entries are for names and descriptions. These are shown to users. For this example, we'll name it "Contacts Db Analyzer". The description can be anything you want. Note that Autopsy requires that modules have unique names, so don't make it too generic.</li>
<li><strong>Ingest Module Class Name</strong>: The next thing to do is rename the ingest module class from "SampleJythonDataSourceIngestModule" to "ContactsDbIngestModule". Our usual naming convention is that this class is the same as the factory class with "Factory" removed from the end. There are a couple of places where this name is used, so do a search and replace in your code.</li>
<li><strong>startUp() method</strong>: The startUp() method is where each module initializes. For our example, we don't need to do anything special in here except save a reference to the passed in context object. This is used later on to see if the module has been cancelled.</li>
<li><strong>process() method</strong>: This is where we do our analysis and we'll focus on this more in the next section.</li>
</ol>
That's it. In the file-level ingest module, we had a shutdown() method, but we do not need that with data source-level ingest modules. When their process method is finished, it can shut itself down. The process() method will be called only once.
\subsection python_tutorial2_process The process() Method
The process method in a data source-level ingest module is passed in reference to the data source as a <a href="https://www.sleuthkit.org/sleuthkit/docs/jni-docs/interfaceorg_1_1sleuthkit_1_1datamodel_1_1_content.html" target="_blank" rel="noopener noreferrer">Content</a> object and a <a href="https://sleuthkit.org/autopsy/docs/api-docs/3.1/classorg_1_1sleuthkit_1_1autopsy_1_1ingest_1_1_data_source_ingest_module_progress.html" target="_blank" rel="noopener noreferrer">Progress Bar</a> class to update our progress.</p>
<p>For this tutorial, you can start by deleting the contents of the existing process() method in the sample module. The full source code is linked to at the end of this blog and shows more detail about a fully fledged module. We'll just cover the analytics in the blog.</p>
\subsubsection python_tutorial2_getting_files Getting Files
Because data source-level ingest modules are not passed in specific files to analyze, nearly all of these types of modules will need to use the org.sleuthkit.autopsy.casemodule.services.FileManager service to find relevant files. Check out the methods on that class to see the different ways that you can find files.
NOTE: See the \ref python_tutorial2_running_exes section for an example of when you simply want to run a command line tool on a disk image instead of querying for files to analyze.
For our example, we want to find all files named "contacts.db". The org.sleuthkit.autopsy.casemodule.services.FileManager class contains several findFiles() methods to help. You can search for all files with a given name or files with a given name in a particular folder. You can also use SQL syntax to match file patterns, such as "%.jpg" to find all files with a JPEG extension.
Our example needs these two lines to get the FileManager for the current case and to find the files.
\verbatim
fileManager = Case.getCurrentCase().getServices().getFileManager()
files = fileManager.findFiles(dataSource, "contacts.db")\endverbatim
findFiles() returns a list of <a href="https://sleuthkit.org/sleuthkit/docs/jni-docs/classorg_1_1sleuthkit_1_1datamodel_1_1_abstract_file.html">AbstractFile</a> objects. This gives you access to the file's metadata and content.
For our example, we are going to open these SQLite files. That means that we need to save them to disk. This is less than ideal because it wastes time writing the data to disk and then reading it back in, but it is the only option with many libraries. If you are doing some other type analysis on the content, then you do not need to write it to disk. You can read directly from the AbstractFile (see the sample modules for specific code to do this).
The org.sleuthkit.autopsy.datamodel.ContentUtils class provides a utility to save file content to disk. We'll make a path in the temp folder of our case directory. To prevent naming collisions, we'll name the file based on its unique ID. The following two lines save the file to lclDbPath.
\verbatim
lclDbPath = os.path.join(Case.getCurrentCase().getTempDirectory(), str(file.getId()) + ".db")
ContentUtils.writeToFile(file, File(lclDbPath))\endverbatim
\subsubsection python_tutorial2_analyzing_sqlite Analyzing SQLite
Next, we need to open the SQLite database. We are going to use the Java JDBC infrastructure for this. JDBC is Java's generic way of dealing with different types of databases. To open the database, we do this:
\verbatim
Class.forName("org.sqlite.JDBC").newInstance()
dbConn = DriverManager.getConnection("jdbc:sqlite:%s" % lclDbPath)\endverbatim
With our connection in hand, we can do some queries. In our sample database, we have a single table named "contacts", which has columns for name, email, and phone. We first start by querying for all rows in our simple table:
\verbatim
stmt = dbConn.createStatement()
resultSet = stmt.executeQuery("SELECT * FROM contacts")\endverbatim
For each row, we are going to get the values for the name, e-mail, and phone number and make a TSK_CONTACT artifact. Recall from the first tutorial that posting artifacts to the blackboard allows modules to communicate with each other and also allows you to easily display data to the user. The TSK_CONTACT artifact is for storing contact information.
The basic approach in our example is to make an artifact of a given type (TSK_CONTACT) and have it be associated with the database it came from. We then make attributes for the name, email, and phone. The following code does this for each row in the database:
\verbatim
while resultSet.next():
# Make an artifact on the blackboard and give it attributes
art = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT)
name = resultSet.getString("name")
art.addAttribute(BlackboardAttribute(
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME_PERSON.getTypeID(),
ContactsDbIngestModuleFactory.moduleName, name))
email = resultSet.getString("email")
art.addAttribute(BlackboardAttribute(
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL.getTypeID(),
ContactsDbIngestModuleFactory.moduleName, email))
phone = resultSet.getString("phone")
art.addAttribute(BlackboardAttribute(
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER.getTypeID(),
ContactsDbIngestModuleFactory.moduleName, phone))\endverbatim
That's it. We've just found the databases, queried them, and made artifacts for the user to see. There are some final things though. First, we should fire off an event so that the UI updates and refreshes with the new artifacts. We can fire just one event after each database is parsed (or you could fire one for each artifact - it's up to you).
\verbatim
IngestServices.getInstance().fireModuleDataEvent(
ModuleDataEvent(ContactsDbIngestModuleFactory.moduleName,
BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT, None))\endverbatim
And the final thing is to clean up. We should close the database connections and delete our temporary file.
\verbatim
stmt.close()
dbConn.close()
os.remove(lclDbPath)\endverbatim
\subsection python_tutorial2_niceties Niceties
Data source-level ingest modules can run for quite some time. Therefore, data source-level ingest modules should do some additional things that file-level ingest modules do not need to.
<ul>
<li>Progress bars: Each data source-level ingest module will have its own progress bar in the lower right. A reference to it is passed into the process() method. You should update it to provide user feedback.</li>
<li>Cancellation: A user could cancel ingest while your module is running. You should periodically check if that occurred so that you can bail out as soon as possible. You can do that with a check of:
\verbatim if self.context.isJobCancelled():\endverbatim </li>
</ul>
\subsection python_tutorial2_tips Debugging and Development Tips
You can find the full file along with a small sample database on <a href="https://github.com/sleuthkit/autopsy/tree/develop/pythonExamples/Aug2015DataSourceTutorial">github</a>. To use the database, add it as a logical file and run your module on it.
Whenever you have syntax errors or other errors in your script, you will get some form of dialog from Autopsy when you try to run ingest modules. If that happens, fix the problem and run ingest modules again. You don't need to restart Autopsy each time!
The sample module has some log statements in there to help debug what is going on since we don't know of better ways to debug the scripts while running in Autopsy.
\section python_tutorial2_running_exes Running Executables
While the above example outlined using the FileManager to find files to analyze, the other common use of data source-level ingest modules is to wrap a command line tool that takes a disk image as input. A sample program (RunExe.py) that does that can be found on <a href="https://github.com/sleuthkit/autopsy/tree/develop/pythonExamples/Aug2015DataSourceTutorial">github</a>. I'll cover the big topics of that program in this section. There are more details in the script about error checking and such.
\subsection python_tutorial2_finding_exe Finding The Executable
To write this kind of data source-level ingest module, put the executable in your module's folder (the DemoScript2 folder we previously made). Use "__file__" to get the path to where your script is and then use some os.path methods to get to the executable in the same folder.
\verbatim
path_to_exe = os.path.join(os.path.dirname(os.path.abspath(__file__)), "img_stat.exe")\endverbatim
In our sample program, we do this and verify we can find it in the startup() method so that if we don't, then ingest never starts.
\subsection python_tutorial2_running_the_exe Running The Executable
Data sources can be disk images, but they can also be a folder of files. We only want to run our executable on a disk image. So, verify that:
\verbatim
if not isinstance(dataSource, Image):
self.log(Level.INFO, "Ignoring data source. Not an image")
return IngestModule.ProcessResult.OK \endverbatim
You can get the path to the disk image using dataSource.getPaths().
Once you have the EXE and the disk image, you can use the various <a href="https://pymotw.com/2/subprocess/">subprocess</a> methods to run them.
\subsection python_tutorial2_showing_results Showing the User Results
After the command line tool runs, you have the option of either showing the user the raw output of the tool or parsing it into individual artifacts. Refer to previous sections of this tutorial and the previous tutorial for making artifacts. If you want to simply show the user the output of the tool, then save the output to the Reports folder in the Case directory:
\verbatim
reportPath = os.path.join(Case.getCurrentCase().getCaseDirectory(),
"Reports", "img_stat-" + str(dataSource.getId()) + ".txt") \endverbatim
Then you can add the report to the case so that it shows up in the tree in the main UI panel.
\verbatim Case.getCurrentCase().addReport(reportPath, "Run EXE", "img_stat output")\endverbatim
\section python_tutorial2_conclusion Conclusion
Data source-level ingest modules allow you to query for a subset of files by name or to run on an entire disk image. This tutorial has shown an example of both use cases and shown how to use SQLite in Jython.
*/

View File

@ -15,11 +15,10 @@ Using it is very easy though in Autopsy and it allows you to access all of the J
To develop a module, you should follow this section to get your environment setup and then read the later sections on the different types of modules.
There are also a set of tutorials that Basis Technology published on their blog. While not as thorough as this documentation, they are an easy introduction to the general ideas.
- File Ingest Modules: http://www.basistech.com/python-autopsy-module-tutorial-1-the-file-ingest-module/
- Data Source Ingest Modules: http://www.basistech.com/python-autopsy-module-tutorial-2-the-data-source-ingest-module/
- Report Modules: http://www.basistech.com/python-autopsy-module-tutorial-3-the-report-module/
There are also a set of tutorials that provide an easy introduction to the general ideas.
- File Ingest Modules: \subpage mod_python_file_ingest_tutorial_page
- Data Source Ingest Modules: \subpage mod_python_ds_ingest_tutorial_page
- Report Modules: \subpage mod_python_report_tutorial_page
\section mod_dev_py_setup Basic Setup

View File

@ -0,0 +1,154 @@
/*! \page mod_python_file_ingest_tutorial_page Python Tutorial #1: Writing a File Ingest Module
\section python_tutorial1_why Why Write a File Ingest Module?
<ul>
<li>Autopsy hides the fact that a file is coming from a file system, was carved, was from inside of a ZIP file, or was part of a local file. So, you don't need to spend time supporting all of the ways that your user may want to get data to you. You just need to worry about analyzing the content.</li>
<li>Autopsy displays files automatically and can include them in reports if you use standard blackboard artifacts (described later). That means you don't need to worry about UIs and reports.</li>
<li>Autopsy gives you access to results from other modules. So, you can build on top of their results instead of duplicating them.</li>
</ul>
\section python_tutorial1_ingest_modules Ingest Modules
For our first example, we're going to write an ingest module. Ingest modules in Autopsy run on the data sources that are added to a case. When you add a disk image (or local drive or logical folder) in Autopsy, you'll be presented with a list of modules to run (such as hash lookup and keyword search).
\image html ingest-modules.PNG
Those are all ingest modules. We're going to write one of those. There are two types of ingest modules that we can build:
<ul>
<li>File Ingest Modules are the easiest to write. During their lifetime, they will get passed in each file in the data source. This includes files that are found via carving or inside of ZIP files (if those modules are also enabled).</li>
<li>Data Source Ingest Modules require slightly more work because you have to query the database for the files of interest. If you only care about a small number of files, know their name, and know they won't be inside of ZIP files, then these are your best bet.</li>
</ul>
For this first tutorial, we're going to write a file ingest module. The \ref mod_python_ds_ingest_tutorial_page "second tutorial" will focus on data source ingest modules. Regardless of the type of ingest module you are writing, you will need to work with two classes:
<ul>
<li>The factory class provides Autopsy with module information such as display name and version. It also creates instances of ingest modules as needed.</li>
<li>The ingest module class will do the actual analysis. One of these will be created per thread. For file ingest modules, Autopsy will typically create two or more of these at a time so that it can analyze files in parallel. If you keep things simple, and don't use static variables, then you don't have to think about anything multithreaded.</li>
</ul>
\section python_tutorial1_getting_started Getting Started
To write your first file ingest module, you'll need:
<ul>
<li>An installed copy of Autopsy available from <a href="https://www.sleuthkit.org/autopsy/download.php" target="_blank" rel="noopener noreferrer">SleuthKit</a></li>
<li>A text editor.</li>
<li>A copy of the sample file ingest module from <a href="https://github.com/sleuthkit/autopsy/blob/autopsy-4.12.0/pythonExamples/fileIngestModule.py" target="_blank" rel="noopener noreferrer">Github</a></li>
</ul>
Some other general notes are that you will be writing in Jython, which converts Python-looking code into Java. It has some limitations, including:
<ul>
<li>You can't use Python 3 (you are limited to Python 2.7)</li>
<li>You can't use libraries that use native code</li>
</ul>
But, Jython will give you access to all of the Java classes and services that Autopsy provides. So, if you want to stray from this example, then refer to the Developer docs on what classes and methods you have access to. The comments in the sample file will identify what type of object is being passed in along with a URL to its documentation.
\subsection python_tutorial1_folder Making Your Module Folder
Every Python module in Autopsy gets its own folder. This reduces naming collisions between modules. To find out where you should put your Python module, launch Autopsy and choose the Tools -&gt; Python Plugins menu item. That will open a folder in your AppData folder, such as "C:\Users\JDoe\AppData\Roaming\Autopsy\python_modules".
<p>Make a folder inside of there to store your module. Call it "DemoScript". Copy the fileIngestModule.py sample file listed above into the this new folder and rename it to FindBigRoundFiles.py. Your folder should look like this:
\image html demoScript_folder.png
\subsection python_tutorial1_writing Writing the Script
We are going to write a script that flags any file that is larger than 10MB and whose size is a multiple of 4096. We'll call these big and round files. This kind of technique could be useful for finding encrypted files. An additional check would be for entropy of the file, but we'll keep the example simple.
Open the FindBigRoundFiles.py file in your favorite python text editor. The sample Autopsy Python modules all have TODO entries in them to let you know what you should change. The below steps jump from one TODO to the next.
<ol>
<li><b>Factory Class Name</b>: The first thing to do is rename the sample class name from "SampleJythonFileIngestModuleFactory" to "FindBigRoundFilesIngestModuleFactory". In the sample module, there are several uses of this class name, so you should search and replace for these strings.</li>
<li><b>Name and Description</b>: The next TODO entries are for names and descriptions. These are shown to users. For this example, we'll name it "Big and Round File Finder". The description can be anything you want. Note that Autopsy requires that modules have unique names, so don't make it too generic.</li>
<li><b>Ingest Module Class Name</b>: The next thing to do is rename the ingest module class from "SampleJythonFileIngestModule" to "FindBigRoundFilesIngestModule". Our usual naming convention is that this class is the same as the factory class with "Factory" removed from the end.</li>
<li><b>startUp() method</b>: The startUp() method is where each module initializes. For our example, we don't need to do anything special in here. Typically though, this is where you want to do stuff that could fail because throwing an exception here causes the entire ingest to stop.</li>
<li><b>process() method</b>: This is where we do our analysis. The sample module is well documented with what it does. It ignores non-files, looks at the file name, and makes a blackboard artifact for ".txt" files. There are also a bunch of other things that it does to show examples for easy copy and pasting, but we don't need them in our module. We'll cover what goes into this method in the next section.</li>
<li><b>shutdown() method</b>: The shutDown() method either frees resources that were allocated or sends summary messages. For our module, it will do nothing.</li>
</ol>
\subsection python_tutorial1_process The process() Method
The process() method is passed in a reference to an AbstractFile Object. With this, you have access to all of a file's contents and metadata. We want to flag files that are larger than 10MB and that are a multiple of 4096 bytes. The following code does that:
\verbatim if ((file.getSize() > 10485760) and ((file.getSize() % 4096) == 0)):
\endverbatim
Now that we have found the files, we want to do something with them. In our situation, we just want to alert the user to them. We do this by making an "Interesting Item" blackboard artifact. The <a href="https://sleuthkit.org/sleuthkit/docs/jni-docs/mod_bbpage.html" target="_blank" rel="noopener noreferrer">Blackboard</a> is where ingest modules can communicate with each other and with the Autopsy GUI. The blackboard has a set of artifacts on it and each artifact:</p>
<ul>
<li>Has a type</li>
<li>Is associated with a file</li>
<li>Has one or more attributes. Attributes are simply name and value pairs.</li>
</ul>
For our example, we are going to make an artifact of type "TSK_INTERESTING_FILE" whenever we find a big and round file. These are one of the most generic artifact types and are simply a way of alerting the user that a file is interesting for some reason. Once you make the artifact, it will be shown in the UI. The below code makes an artifact for the file and puts it into the set of "Big and Round Files". You can create whatever set names you want. The Autopsy GUI organizes Interesting Files by their set name.
\verbatim
art = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT)
att = BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID(),
FindBigRoundFilesIngestModuleFactory.moduleName, "Big and Round Files")
art.addAttribute(att)\endverbatim
The above code adds the artifact and a single attribute to the blackboard in the embedded database, but it does not notify other modules or the UI. The UI will eventually refresh, but it is faster to fire an event with this:
\verbatim
IngestServices.getInstance().fireModuleDataEvent(
ModuleDataEvent(FindBigRoundFilesIngestModuleFactory.moduleName,
BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT, None))\endverbatim
That's it. Your process() method should look something like this:
\verbatim
def process(self, file):
# Skip non-files
if ((file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) or
(file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS) or
(file.isFile() == False)):
return IngestModule.ProcessResult.OK
# Look for files bigger than 10MB that are a multiple of 4096
if ((file.getSize() &gt; 10485760) and ((file.getSize() % 4096) == 0)):
# Make an artifact on the blackboard. TSK_INTERESTING_FILE_HIT is a generic type of
# artifact. Refer to the developer docs for other examples.
art = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT)
att = BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID(),
FindBigRoundFilesIngestModuleFactory.moduleName, "Big and Round Files")
art.addAttribute(att)
# Fire an event to notify the UI and others that there is a new artifact
IngestServices.getInstance().fireModuleDataEvent(
ModuleDataEvent(FindBigRoundFilesIngestModuleFactory.moduleName,
BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT, None))
return IngestModule.ProcessResult.OK\endverbatim
Save this file and run the module on some of your data. If you have any big and round files, you should see an entry under the "Interesting Items" node in the tree.
\image html bigAndRoundFiles.png
\subsection python_tutorial1_debug Debugging and Development Tips
Whenever you have syntax errors or other errors in your script, you will get some form of dialog from Autopsy when you try to run ingest modules. If that happens, fix the problem and run ingest modules again. You don't need to restart Autopsy each time!
The sample module has some log statements in there to help debug what is going on since we don't know of better ways to debug the scripts while running in Autopsy.
*/

View File

@ -0,0 +1,123 @@
/*! \page mod_python_report_tutorial_page Python Tutorial #3: Writing a Report Module
In our last two tutorials, we built a Python Autopsy \ref mod_python_file_ingest_tutorial_page "file ingest modules" and \ref mod_python_ds_ingest_tutorial_page "data source ingest modules" that analyzed the data sources as they were added to cases. In our third post, we're going to make an entirely different kind of module, a report module.
Report modules are typically run after the user has completed their analysis. Autopsy comes with report modules to generate HTML, Excel, KML, and other types of reports. We're going to make a report module that outputs data in CSV.
Like in the second tutorial, we are going to assume that you've read at least the \ref mod_python_file_ingest_tutorial_page "first tutorial" to know how to get your environment set up. As a reminder, Python modules in Autopsy are written in Jython and have access to all of the Java classes (which is why we have links to Java documentation below).
\section python_tutorial3_report_modules Report Modules
Autopsy report modules are often run after the user has run some ingest modules, reviewed the results, and tagged some files of interest. The user will be given a list of report modules to choose from.
\image html reports_select.png
The main reasons for writing an Autopsy report module are:
<ul>
<li>You need the results in a custom output format, such as XML or JSON.</li>
<li>You want to upload results to a central location.</li>
<li>You want to perform additional analysis after all ingest modules have run. While the modules have the word "report" in them, there is no actual requirement that they produce a report or export data. The module can simply perform data analysis and post artifacts to the blackboard like ingest modules do.</li>
</ul>
As we dive into the details, you will notice that the report module API is fairly generic. This is because reports are created at a case level, not a data source level. So, when a user chooses to run a report module, all Autopsy does is tell it to run and gives it a path to a directory to store its results in. The report module can store whatever it wants in the directory.
Note that if you look at the \ref mod_report_page "full developer docs", there are other report module types that are supported in Java. These are not supported though in Python.
\subsection python_tutorial3_getting_content Getting Content
With report modules, it is up to you to find the content that you want to include in your report or analysis. Generally, you will want to access some or all of the files, tagged files, or blackboard artifacts. As you may recall from the previous tutorials, blackboard artifacts are how ingest modules in Autopsy store their results so that they can be shown in the UI, used by other modules, and included in the final report. In this tutorial, we will introduce the <a href="https://sleuthkit.org/sleuthkit/docs/jni-docs/classorg_1_1sleuthkit_1_1datamodel_1_1_sleuthkit_case.html">SleuthkitCase</a> class, which we generally don't introduce to module writers because it has lots of methods, many of which are low-level, and there are other classes, such as FileManager, that are more focused and easier to use.
\subsubsection python_tutorial3_getting_files Getting Files
You have three choices for getting files to report on. You can use the FileManager, which we used in \ref mod_python_ds_ingest_tutorial_page "the last Data Source-level Ingest Module tutorial". The only change is that you will need to call it multiple times, one for each data source in the case. You will have code that looks something like this:
\verbatim
dataSources = Case.getCurrentCase().getDataSources()
fileManager = Case.getCurrentCase().getServices().getFileManager()
for dataSource in dataSources:
files = fileManager.findFiles(dataSource, "%.txt")\endverbatim
Another approach is to use the <a href="https://sleuthkit.org/sleuthkit/docs/jni-docs/classorg_1_1sleuthkit_1_1datamodel_1_1_sleuthkit_case.html#a6b14c6b82bbc1cf71aa108f9e5c5ccc1">SleuthkitCase.findAllFilesWhere()</a> method that allows you to specify a SQL query. To use this method, you must know the schema of the database (which makes this a bit more challenging, but more powerful). The schema is defined on the <a href="https://wiki.sleuthkit.org/index.php?title=SQLite_Database_v3_Schema">wiki</a>.
Usually, you just need to focus on the <a href="https://wiki.sleuthkit.org/index.php?title=SQLite_Database_v3_Schema#tsk_files">tsk_files</a> table. You may run into memory problems and you can also use <a href="https://sleuthkit.org/sleuthkit/docs/jni-docs/classorg_1_1sleuthkit_1_1datamodel_1_1_sleuthkit_case.html#a2faec4e68be17f67db298a4ed3933bc3">SleuthkitCase.findAllFileIdsWhere()</a> to get just the IDs and then call <a href="https://sleuthkit.org/sleuthkit/docs/jni-docs/classorg_1_1sleuthkit_1_1datamodel_1_1_sleuthkit_case.html#a8cdd6582b18e9bfa814cffed8302e4b9">SleuthkitCase.getAbstractFileById()</a> to get files as needed.
A third approach is to call org.sleuthkit.autopsy.casemodule.Case.getDataSources(), and then recursively call getChildren() on each Content object. This will traverse all of the folders and files in the case. This is the most memory efficient, but also more complex to code.
\subsubsection python_tutorial3_getting_artifacts Getting Blackboard Artifacts
The blackboard is where modules store their analysis results. If you want to include them in your report, then there are several methods that you could use. If you want all artifacts of a given type, then you can use <a href="https://sleuthkit.org/sleuthkit/docs/jni-docs/classorg_1_1sleuthkit_1_1datamodel_1_1_sleuthkit_case.html#a0b8396fac6c40d8291cc48732dd15d74">SleuthkitCase.getBlackboardArtifacts()</a>. There are many variations of this method that take different arguments. Look at them to find the one that is most convenient for you.
\subsubsection python_tutorial3_getting_tags Getting Tagged Files or Artifacts
If you want to find files or artifacts that are tagged, then you can use the org.sleuthkit.autopsy.casemodule.services.TagsManager. It has methods to get all tags of a given name, such as org.sleuthkit.autopsy.casemodule.services.TagsManager.getContentTagsByTagName().
\section python_tutorial3_getting_started Getting Started
\subsection python_tutorial3_making_the_folder Making the Folder
We'll start by making our module folder. As we learned in \ref mod_python_file_ingest_tutorial_page "the first tutorial", every Python module in Autopsy gets its own folder. To find out where you should put your Python module, launch Autopsy and choose the Tools->Python Plugins menu item. That will open a subfolder in your AppData folder, such as "C:\Users\JDoe\AppData\Roaming\Autopsy\python_modules".
Make a folder inside of there to store your module. Call it "DemoScript3". Copy the <a href="https://github.com/sleuthkit/autopsy/blob/develop/pythonExamples/reportmodule.py">reportmodule.py</a> sample file into the this new folder and rename it to CSVReport.py.
\subsection python_tutorial3_writing_script Writing the Script
We are going to write a script that makes some basic CSV output: file name and MD5 hash. Open the CSVReport.py file in your favorite Python text editor. The sample Autopsy Python modules all have TODO entries in them to let you know what you should change. The below steps jump from one TODO to the next.
<ol>
<li>Factory Class Name: The first thing to do is rename the sample class name from "SampleGeneralReportModule" to "CSVReportModule". In the sample module, there are several uses of this class name, so you should search and replace for these strings.</li>
<li>Name and Description: The next TODO entries are for names and descriptions. These are shown to users. For this example, we'll name it "CSV Hash Report Module". The description can be anything you want. Note that Autopsy requires that modules have unique names, so don't make it too generic.</li>
<li>Relative File Path: The next step is to specify the filename that your module is going to use for the report. Autopsy will later provide you with a folder name to save your report in. If you have multiple file names, then pick the main one. This path will be shown to the user after the report has been generated so that they can open it. For this example, we'll call it "hashes.csv" in the getRelativeFilePath() method.</li>
<li>generateReport() Method: This method is what is called when the user wants to run the module. It gets passed in the base directory to store the results in and a progress bar. It is responsible for making the report and calling Case.addReport() so that it will be shown in the tree. We'll cover the details of this method in a later section.</li>
</ol>
\subsection python_tutorial3_generate_report The generateReport() method
The generateReport() method is where the work is done. The baseReportDir argument is a string for the base directory to store results in. The progressBar argument is a org.sleuthkit.autopsy.report.ReportProgressPanel
that shows the user progress while making long reports and to make the progress bar red if an error occurs.
We'll use one of the basic ideas from the sample, so you can copy and paste from that as you see fit to make this method. Our general approach is going to be this:
<ol>
<li>Open the CSV file.</li>
<li>Query for all files.</li>
<li>Cycle through each of the files and print a line of text.</li>
<li>Add the report to the Case database.</li>
</ol>
To focus on the essential code, we'll skip the progress bar details. However, the final solution that we'll link to at the end contains the progress bar code.
To open the report file in the right folder, we'll need a line such as this:
\verbatim
fileName = os.path.join(baseReportDir, self.getRelativeFilePath())
report = open(fileName, 'w')\endverbatim
Next we need to query for the files. In our case, we want all of the files, but can skip the directories. We'll use lines such as this to get the current case and then call the SleuthkitCase.findAllFilesWhere() method.
\verbatim
sleuthkitCase = Case.getCurrentCase().getSleuthkitCase()
files = sleuthkitCase.findAllFilesWhere("NOT meta_type = " +
str(TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_DIR.getValue()))\endverbatim
Now, we want to print a line for each file. To do this, you'll need something like:
\verbatim
for file in files:
md5 = file.getMd5Hash()
if md5 is None:
md5 = ""
report.write(file.getParentPath() + file.getName() + "," + md5 + "n")\endverbatim
Note that the file will only have an MD5 value if the Hash Lookup ingest module was run on the data source.
Lastly, we want to add the report to the case database so that the user can later find it from the tree and we want to report that we completed successfully.
\verbatim
Case.getCurrentCase().addReport(fileName, self.moduleName, "Hashes CSV")
progressBar.complete(ReportStatus.COMPLETE)\endverbatim
That's it. The final code can be found <a href="https://github.com/sleuthkit/autopsy/tree/develop/pythonExamples/Sept2015ReportTutorial_CSV">on github</a>.
\subsection python_tutorial3_conclusions Conclusions
In this tutorial, we made a basic report module that creates a custom CSV file. The most challenging part of writing a report module is knowing how to get all of the data that you need. Hopefully, the \ref python_tutorial3_getting_content section above covered what you need, but if not, then go on the <a href="https://sleuthkit.discourse.group/">Sleuthkit forum</a> and we'll try to point you in the right direction.</p>
*/

View File

@ -83,7 +83,7 @@ class EmailMessage {
void setSubject(String subject) {
if (subject != null) {
this.subject = subject;
if(subject.matches("^[R|r][E|e].*?:.*")) {
if (subject.matches("^[R|r][E|e].*?:.*")) {
this.simplifiedSubject = subject.replaceAll("[R|r][E|e].*?:", "").trim();
replySubject = true;
} else {
@ -93,19 +93,19 @@ class EmailMessage {
this.simplifiedSubject = "";
}
}
/**
* Returns the orginal subject with the "RE:" stripped off".
*
*
* @return Message subject with the "RE" stripped off
*/
String getSimplifiedSubject() {
return simplifiedSubject;
}
/**
* Returns whether or not the message subject started with "RE:"
*
*
* @return true if the original subject started with RE otherwise false.
*/
boolean isReplySubject() {
@ -121,6 +121,7 @@ class EmailMessage {
this.headers = headers;
}
}
String getTextBody() {
return textBody;
}
@ -211,75 +212,80 @@ class EmailMessage {
this.localPath = localPath;
}
}
/**
* Returns the value of the Message-ID header field of this message or
* empty string if it is not present.
*
* Returns the value of the Message-ID header field of this message or empty
* string if it is not present.
*
* @return the identifier of this message.
*/
String getMessageID() {
return messageID;
}
/**
* Sets the identifier of this message.
*
*
* @param messageID identifer of this message
*/
void setMessageID(String messageID) {
this.messageID = messageID;
if (messageID != null) {
this.messageID = messageID;
} else {
this.messageID = "";
}
}
/**
* Returns the messageID of the parent message or empty String if not present.
*
* Returns the messageID of the parent message or empty String if not
* present.
*
* @return the idenifier of the message parent
*/
String getInReplyToID() {
return inReplyToID;
}
/**
* Sets the messageID of the parent message.
*
*
* @param inReplyToID messageID of the parent message.
*/
void setInReplyToID(String inReplyToID) {
this.inReplyToID = inReplyToID;
}
/**
* Returns a list of Message-IDs listing the parent, grandparent,
* great-grandparent, and so on, of this message.
*
* Returns a list of Message-IDs listing the parent, grandparent,
* great-grandparent, and so on, of this message.
*
* @return The reference list or empty string if none is available.
*/
List<String> getReferences() {
return references;
}
/**
* Set the list of reference message-IDs from the email message header.
*
* @param references
*
* @param references
*/
void setReferences(List<String> references) {
this.references = references;
}
/**
* Sets the ThreadID of this message.
*
*
* @param threadID - the thread ID to set
*/
void setMessageThreadID(String threadID) {
this.messageThreadID = threadID;
}
/**
* Returns the ThreadID for this message.
*
*
* @return - the message thread ID or "" is non is available
*/
String getMessageThreadID() {
@ -308,7 +314,7 @@ class EmailMessage {
private long aTime = 0L;
private long mTime = 0L;
private TskData.EncodingType encodingType = TskData.EncodingType.NONE;
String getName() {
@ -394,14 +400,14 @@ class EmailMessage {
this.mTime = mTime.getTime() / 1000;
}
}
void setEncodingType(TskData.EncodingType encodingType){
void setEncodingType(TskData.EncodingType encodingType) {
this.encodingType = encodingType;
}
TskData.EncodingType getEncodingType(){
TskData.EncodingType getEncodingType() {
return encodingType;
}
}
}