diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.java index 18fd2c6202..337fa49f0b 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.java @@ -22,6 +22,7 @@ import java.awt.Dimension; import java.awt.Toolkit; import java.io.File; import java.io.IOException; +import java.nio.file.Paths; import java.util.logging.Level; import javax.swing.JFileChooser; import javax.swing.JFrame; @@ -31,6 +32,7 @@ import org.apache.commons.io.FilenameUtils; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ModuleSettings; +import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDb; import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDb.KnownFilesType; import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDbManagerException; @@ -249,9 +251,16 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog { }// //GEN-END:initComponents private void openButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_openButtonActionPerformed + String lastBaseDirectory = Paths.get(PlatformUtil.getUserConfigDirectory(), "HashDatabases").toString(); if (ModuleSettings.settingExists(ModuleSettings.MAIN_SETTINGS, LAST_FILE_PATH_KEY)) { - fileChooser.setCurrentDirectory(new File(ModuleSettings.getConfigSetting(ModuleSettings.MAIN_SETTINGS, LAST_FILE_PATH_KEY))); + lastBaseDirectory = ModuleSettings.getConfigSetting(ModuleSettings.MAIN_SETTINGS, LAST_FILE_PATH_KEY); } + File hashDbFolder = new File(lastBaseDirectory); + // create the folder if it doesn't exist + if (!hashDbFolder.exists()) { + hashDbFolder.mkdir(); + } + fileChooser.setCurrentDirectory(hashDbFolder); if (fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { File databaseFile = fileChooser.getSelectedFile(); try { diff --git a/Core/src/org/sleuthkit/autopsy/report/Bundle.properties b/Core/src/org/sleuthkit/autopsy/report/Bundle.properties index ba477498c3..6e920ad631 100644 --- a/Core/src/org/sleuthkit/autopsy/report/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/report/Bundle.properties @@ -225,7 +225,8 @@ ReportHTML.writeSum.noCaseNum=No case number ReportBodyFile.generateReport.srcModuleName.text=TSK Body File ReportExcel.endReport.srcModuleName.text=Excel Report ReportHTML.writeIndex.srcModuleName.text=HTML Report -ReportKML.genReport.srcModuleName.text=KML Report +ReportKML.genReport.srcModuleName.text=Geospatial Data +ReportKML.genReport.reportName=KML Report ReportGenerator.artTableColHdr.extension.text=Extension ReportGenerator.artTableColHdr.mimeType.text=MIME Type ReportGenerator.artTableColHdr.processorArchitecture.text=Processor Architecture diff --git a/Core/src/org/sleuthkit/autopsy/report/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/report/Bundle_ja.properties index 6ff657b68a..a877c52899 100644 --- a/Core/src/org/sleuthkit/autopsy/report/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/report/Bundle_ja.properties @@ -188,7 +188,7 @@ ReportExcel.endReport.srcModuleName.text=Excel\u30ec\u30dd\u30fc\u30c8 ReportGenerator.artTableColHdr.extension.text=\u62e1\u5f35\u5b50 ReportGenerator.artTableColHdr.mimeType.text=MIME\u30bf\u30a4\u30d7 ReportHTML.writeIndex.srcModuleName.text=HTML\u30ec\u30dd\u30fc\u30c8 -ReportKML.genReport.srcModuleName.text=KML\u30ec\u30dd\u30fc\u30c8 +ReportKML.genReport.reportName=KML\u30ec\u30dd\u30fc\u30c8 ReportGenerator.artTableColHdr.associatedArtifact=\u95a2\u4fc2\u3059\u308b\u30a2\u30fc\u30c6\u30a3\u30d5\u30a1\u30af\u30c8 ReportGenerator.artTableColHdr.count=\u30ab\u30a6\u30f3\u30c8 ReportGenerator.artTableColHdr.devMake=\u6a5f\u5668\u578b\u540d diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportKML.java b/Core/src/org/sleuthkit/autopsy/report/ReportKML.java index abdd7dee96..44185bfc94 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportKML.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportKML.java @@ -2,7 +2,7 @@ * * Autopsy Forensic Browser * - * Copyright 2014 Basis Technology Corp. + * Copyright 2014-2016 Basis Technology Corp. * contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,33 +27,56 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.*; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.datamodel.BlackboardArtifact; -import java.io.BufferedReader; -import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; -import java.io.FileReader; -import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.SimpleDateFormat; import java.util.logging.Level; import org.jdom2.Document; import org.jdom2.Element; import org.jdom2.Namespace; import org.jdom2.output.Format; import org.jdom2.output.XMLOutputter; -import org.apache.commons.lang.StringEscapeUtils; +import org.jdom2.CDATA; +import org.openide.filesystems.FileUtil; /** - * Generates a KML file based on geo coordinates store in blackboard. + * Generates a KML file based on geospatial information from the BlackBoard. */ class ReportKML implements GeneralReportModule { private static final Logger logger = Logger.getLogger(ReportKML.class.getName()); + private static final String KML_STYLE_FILE = "style.kml"; + private static final String REPORT_KML = "ReportKML.kml"; + private static final String STYLESHEETS_PATH = "/org/sleuthkit/autopsy/report/stylesheets/"; private static ReportKML instance = null; private Case currentCase; private SleuthkitCase skCase; - private String reportPath; + private final SimpleDateFormat kmlDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX"); + private Namespace ns; + private final String SEP = "
"; + + private enum FeatureColor { + RED("style.kml#redFeature"), + GREEN("style.kml#greenFeature"), + BLUE("style.kml#blueFeature"), + PURPLE("style.kml#purpleFeature"), + WHITE("style.kml#whiteFeature"), + YELLOW("style.kml#yellowFeature"); + private String color; + + FeatureColor(String color) { + this.color = color; + } + + String getColor() { + return this.color; + } + } // Hidden constructor for the report private ReportKML() { @@ -80,279 +103,684 @@ class ReportKML implements GeneralReportModule { progressPanel.setIndeterminate(false); progressPanel.start(); progressPanel.updateStatusLabel(NbBundle.getMessage(this.getClass(), "ReportKML.progress.querying")); - reportPath = baseReportDir + "ReportKML.kml"; //NON-NLS - String reportPath2 = baseReportDir + "ReportKML.txt"; //NON-NLS + String kmlFileFullPath = baseReportDir + REPORT_KML; //NON-NLS currentCase = Case.getCurrentCase(); skCase = currentCase.getSleuthkitCase(); progressPanel.updateStatusLabel(NbBundle.getMessage(this.getClass(), "ReportKML.progress.loading")); - // Check if ingest has finished - String ingestwarning = ""; - if (IngestManager.getInstance().isIngestRunning()) { - ingestwarning = NbBundle.getMessage(this.getClass(), "ReportBodyFile.ingestWarning.text"); - } + progressPanel.setMaximumProgress(5); progressPanel.increment(); - // @@@ BC: I don't get why we do this in two passes. - // Why not just print the coordinates as we find them and make some utility methods to do the printing? - // Should pull out time values for all of these points and store in TimeSpan element + 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); + + /** + * 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. + * + */ try { - try (BufferedWriter out = new BufferedWriter(new FileWriter(reportPath2))) { - - double lat = 0; // temp latitude - double lon = 0; //temp longitude - AbstractFile aFile; - String geoPath = ""; // will hold values of images to put in kml - String imageName = ""; - - File f; - for (BlackboardArtifact artifact : skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF)) { - lat = 0; - lon = 0; - geoPath = ""; - String extractedToPath; - for (BlackboardAttribute attribute : artifact.getAttributes()) { - if (attribute.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID()) //latitude - { - - lat = attribute.getValueDouble(); - } - if (attribute.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE.getTypeID()) //longitude - { - lon = attribute.getValueDouble(); - } - } - if (lon != 0 && lat != 0) { - aFile = artifact.getSleuthkitCase().getAbstractFileById(artifact.getObjectID()); - - if (aFile != null) { - extractedToPath = reportPath + aFile.getName(); - geoPath = extractedToPath; - f = new File(extractedToPath); - f.createNewFile(); - copyFileUsingStream(aFile, f); - imageName = aFile.getName(); - } - out.write(String.valueOf(lat)); - out.write(";"); - out.write(String.valueOf(lon)); - out.write(";"); - out.write(String.valueOf(geoPath)); - out.write(";"); - out.write(String.valueOf(imageName)); - out.write("\n"); - // lat lon path name + for (BlackboardArtifact artifact : skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF)) { + 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)); + Path destination = null; + if (lat != null && lat != 0.0 && lon != null && lon != 0.0) { + AbstractFile abstractFile = artifact.getSleuthkitCase().getAbstractFileById(artifact.getObjectID()); + if (abstractFile != null) { + destination = Paths.get(baseReportDir, abstractFile.getName()); + copyFileUsingStream(abstractFile, destination.toFile()); } + String formattedCoordinates = String.format("%.2f, %.2f", lat, lon); + gpsExifMetadataFolder.addContent(makePlacemarkWithPicture(abstractFile.getName(), FeatureColor.RED, desc, timestamp, point, destination, formattedCoordinates)); } - for (BlackboardArtifact artifact : skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT)) { - lat = 0; - lon = 0; - for (BlackboardAttribute attribute : artifact.getAttributes()) { - if (attribute.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID()) //latitude - { - lat = attribute.getValueDouble(); - } - if (attribute.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE.getTypeID()) //longitude - { - lon = attribute.getValueDouble(); - } - } - if (lon != 0 && lat != 0) { - out.write(lat + ";" + lon + "\n"); - } - } - - for (BlackboardArtifact artifact : skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE)) { - lat = 0; - lon = 0; - double destlat = 0; - double destlon = 0; - String name = ""; - String location = ""; - for (BlackboardAttribute attribute : artifact.getAttributes()) { - if (attribute.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START.getTypeID()) //latitude - { - lat = attribute.getValueDouble(); - } else if (attribute.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END.getTypeID()) //longitude - { - destlat = attribute.getValueDouble(); - } else if (attribute.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START.getTypeID()) //longitude - { - lon = attribute.getValueDouble(); - } else if (attribute.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END.getTypeID()) //longitude - { - destlon = attribute.getValueDouble(); - } else if (attribute.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME.getTypeID()) //longitude - { - name = attribute.getValueString(); - } else if (attribute.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION.getTypeID()) //longitude - { - location = attribute.getValueString(); - } - } - - // @@@ Should do something more fancy with these in KML and store them as a single point. - String display = name; - if (display.isEmpty()) { - display = location; - } - - if (lon != 0 && lat != 0) { - out.write(NbBundle.getMessage(this.getClass(), "ReportKML.latLongStartPoint", lat, lon, display)); - } - if (destlat != 0 && destlon != 0) { - out.write(NbBundle.getMessage(this.getClass(), "ReportKML.latLongEndPoint", destlat, destlon, - display)); - } - } - - out.flush(); - - progressPanel.increment(); - /* - * Step 1: generate XML stub - */ - Namespace ns = Namespace.getNamespace("", "http://earth.google.com/kml/2.2"); //NON-NLS - // kml - Element kml = new Element("kml", ns); //NON-NLS - Document kmlDocument = new Document(kml); - - // Document - Element document = new Element("Document", ns); //NON-NLS - kml.addContent(document); - - // name - Element name = new Element("name", ns); //NON-NLS - name.setText("Java Generated KML Document"); //NON-NLS - document.addContent(name); - - /* - * Step 2: add in Style elements - */ - // Style - Element style = new Element("Style", ns); //NON-NLS - style.setAttribute("id", "redIcon"); //NON-NLS - document.addContent(style); - - // IconStyle - Element iconStyle = new Element("IconStyle", ns); //NON-NLS - style.addContent(iconStyle); - - // color - Element color = new Element("color", ns); //NON-NLS - color.setText("990000ff"); //NON-NLS - iconStyle.addContent(color); - - // Icon - Element icon = new Element("Icon", ns); //NON-NLS - iconStyle.addContent(icon); - - // href - Element href = new Element("href", ns); //NON-NLS - href.setText("http://www.cs.mun.ca/~hoeber/teaching/cs4767/notes/02.1-kml/circle.png"); //NON-NLS - icon.addContent(href); - progressPanel.increment(); - /* - * Step 3: read data from source location and add in a Placemark - * for each data element - */ - - File file = new File(reportPath2); - try (BufferedReader reader = new BufferedReader(new FileReader(file))) { - String line = reader.readLine(); - while (line != null) { - String[] lineParts = line.split(";"); - if (lineParts.length > 1) { - String coordinates = lineParts[1].trim() + "," + lineParts[0].trim(); //lat,lon - // Placemark - Element placemark = new Element("Placemark", ns); //NON-NLS - document.addContent(placemark); - - if (lineParts.length == 4) { - // name - Element pmName = new Element("name", ns); //NON-NLS - pmName.setText(lineParts[3].trim()); - placemark.addContent(pmName); - - String savedPath = lineParts[2].trim(); - if (savedPath.isEmpty() == false) { - // Path - Element pmPath = new Element("Path", ns); //NON-NLS - pmPath.setText(savedPath); - placemark.addContent(pmPath); - - // description - Element pmDescription = new Element("description", ns); //NON-NLS - String xml = "
" + featureType + ""); //NON-NLS + + String name = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); + if (name != null && !name.isEmpty()) { + result.append("Name: ").append(name).append(SEP); //NON-NLS + } + + String location = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION); + if (location != null && !location.isEmpty()) { + result.append("Location: ").append(location).append(SEP); //NON-NLS + } + + Long timestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME); + if (timestamp != null) { + result.append("Timestamp: ").append(getTimeStamp(timestamp)).append(SEP); //NON-NLS + result.append("Unix timestamp: ").append(timestamp).append(SEP); //NON-NLS + } + + Long startingTimestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START); + if (startingTimestamp != null) { + result.append("Starting Timestamp: ").append(getTimeStamp(startingTimestamp)).append(SEP); //NON-NLS + result.append("Starting Unix timestamp: ").append(startingTimestamp).append(SEP); //NON-NLS + } + + Long endingTimestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_END); + if (endingTimestamp != null) { + result.append("Ending Timestamp: ").append(getTimeStamp(endingTimestamp)).append(SEP); //NON-NLS + result.append("Ending Unix timestamp: ").append(endingTimestamp).append(SEP); //NON-NLS + } + + Long createdTimestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED); + if (createdTimestamp != null) { + result.append("Created Timestamp: ").append(getTimeStamp(createdTimestamp)).append(SEP); //NON-NLS + result.append("Created Unix timestamp: ").append(createdTimestamp).append(SEP); //NON-NLS + } + + Double latitude = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE); + if (latitude != null) { + result.append("Latitude: ").append(latitude).append(SEP); //NON-NLS + } + + Double longitude = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE); + if (longitude != null) { + result.append("Longitude: ").append(longitude).append(SEP); //NON-NLS + } + + Double latitudeStart = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START); + if (latitudeStart != null) { + result.append("Latitude Start: ").append(latitudeStart).append(SEP); //NON-NLS + } + + Double longitudeStart = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START); + if (longitudeStart != null) { + result.append("Longitude Start: ").append(longitudeStart).append(SEP); //NON-NLS + } + + Double latitudeEnd = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END); + if (latitudeEnd != null) { + result.append("Latitude End: ").append(latitudeEnd).append(SEP); //NON-NLS + } + + Double longitudeEnd = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END); + if (longitudeEnd != null) { + result.append("Longitude End: ").append(longitudeEnd).append(SEP); //NON-NLS + } + + Double velocity = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_VELOCITY); + if (velocity != null) { + result.append("Velocity: ").append(velocity).append(SEP); //NON-NLS + } + + Double altitude = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE); + if (altitude != null) { + result.append("Altitude: ").append(altitude).append(SEP); //NON-NLS + } + + Double bearing = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_BEARING); + if (bearing != null) { + result.append("Bearing: ").append(bearing).append(SEP); //NON-NLS + } + + Integer hPrecision = getInteger(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_HPRECISION); + if (hPrecision != null) { + result.append("Horizontal Precision Figure of Merit: ").append(hPrecision).append(SEP); //NON-NLS + } + + Integer vPrecision = getInteger(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_VPRECISION); + if (vPrecision != null) { + result.append("Vertical Precision Figure of Merit: ").append(vPrecision).append(SEP); //NON-NLS + } + + String mapDatum = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_MAPDATUM); + if (mapDatum != null && !mapDatum.isEmpty()) { + result.append("Map Datum: ").append(mapDatum).append(SEP); //NON-NLS + } + + String programName = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME); + if (programName != null && !programName.isEmpty()) { + result.append("Reported by: ").append(programName).append(SEP); //NON-NLS + } + + String flag = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FLAG); + if (flag != null && !flag.isEmpty()) { + result.append("Flag: ").append(flag).append(SEP); //NON-NLS + } + + String pathSource = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_SOURCE); + if (pathSource != null && !pathSource.isEmpty()) { + result.append("Source: ").append(pathSource).append(SEP); //NON-NLS + } + + String deviceMake = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_MAKE); + if (deviceMake != null && !deviceMake.isEmpty()) { + result.append("Device Make: ").append(deviceMake).append(SEP); //NON-NLS + } + + String deviceModel = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_MODEL); + if (deviceModel != null && !deviceModel.isEmpty()) { + result.append("Device Model: ").append(deviceModel).append(SEP); //NON-NLS + } + + return result.toString(); + } + + private String getTimeStamp(long timeStamp) { + return kmlDateFormat.format(new java.util.Date(timeStamp * 1000)); + } + + /** + * 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. + * + * @param latitude point latitude + * @param longitude point longitude + * @param altitude point altitude. Currently ignored. + * + * @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; + } + 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 + + if (altitude != 0) { + /* + Though we are including a non-zero altitude, clamp it to the + ground because inaccuracies from the GPS data can cause the terrain + to occlude points when zoomed in otherwise. Show the altitude, but + keep the point clamped to the ground. We may change this later for + flying GPS sensors. + */ + Element altitudeMode = new Element("altitudeMode", ns).addContent("clampToGround"); //NON-NLS + point.addContent(altitudeMode); + } + point.addContent(coordinates); + + return point; + } + + /** + * 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. + * + * @param startLatitude Starting latitude + * @param startLongitude Starting longitude + * @param startAltitude Starting altitude. Currently ignored. + * @param stopLatitude Ending latitude + * @param stopLongitude Ending longitude + * @param stopAltitude Ending altitude. Currently ignored. + * + * @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; + } + + Element lineString = new Element("LineString", ns); //NON-NLS + lineString.addContent(new Element("extrude", ns).addContent("1")); //NON-NLS + lineString.addContent(new Element("tessellate", ns).addContent("1")); //NON-NLS + lineString.addContent(new Element("altitudeMode", ns).addContent("clampToGround")); //NON-NLS + // KML uses lon, lat. Deliberately reversed. + lineString.addContent(new Element("coordinates", ns).addContent( + startLongitude + "," + startLatitude + ",0.0," + + stopLongitude + "," + stopLatitude + ",0.0")); //NON-NLS + return lineString; + } + + /** + * Make a Placemark for use in displaying features. Takes a + * coordinate-bearing feature (Point, LineString, etc) and places it in the + * Placemark element. + * + * @param name Placemark name + * @param color Placemark color + * @param description Description for the info bubble on the map + * @param timestamp Placemark timestamp + * @param feature The feature to show. Could be Point, LineString, etc. + * @param coordinates The coordinates to display in the list view snippet + * + * @return the entire KML placemark + */ + private Element makePlacemark(String name, FeatureColor color, String description, Long timestamp, Element feature, String coordinates) { + Element placemark = new Element("Placemark", ns); //NON-NLS + if (name != null && !name.isEmpty()) { + placemark.addContent(new Element("name", ns).addContent(name)); //NON-NLS + } else if (timestamp != null) { + placemark.addContent(new Element("name", ns).addContent(getTimeStamp(timestamp))); //NON-NLS + } else { + placemark.addContent(new Element("name", ns).addContent("")); //NON-NLS + } + placemark.addContent(new Element("styleUrl", ns).addContent(color.getColor())); //NON-NLS + placemark.addContent(new Element("description", ns).addContent(description)); //NON-NLS + if (timestamp != null) { + Element time = new Element("TimeStamp", ns); //NON-NLS + time.addContent(new Element("when", ns).addContent(getTimeStamp(timestamp))); //NON-NLS + placemark.addContent(time); + } + placemark.addContent(feature); + if (coordinates != null && !coordinates.isEmpty()) { + placemark.addContent(new Element("snippet", ns).addContent(coordinates)); //NON-NLS + } + return placemark; + } + + /** + * Make a Placemark for use in displaying features. Takes a + * coordinate-bearing feature (Point, LineString, etc) and places it in the + * Placemark element. + * + * @param name Placemark name + * @param color Placemark color + * @param description Description for the info bubble on the map + * @param timestamp Placemark timestamp + * @param feature The feature to show. Could be Point, LineString, etc. + * @param path The path to the file on disk + * @param coordinates The coordinates to display in the list view snippet + * + * @return the entire KML Placemark, including a picture. + */ + private Element makePlacemarkWithPicture(String name, FeatureColor color, String description, Long timestamp, Element feature, Path path, String coordinates) { + Element placemark = new Element("Placemark", ns); //NON-NLS + if (name != null && !name.isEmpty()) { + placemark.addContent(new Element("name", ns).addContent(name)); //NON-NLS + } + + placemark.addContent(new Element("styleUrl", ns).addContent(color.getColor())); //NON-NLS + Element desc = new Element("description", ns); //NON-NLS + + if (path != null) { + String pathAsString = path.toString(); + if (pathAsString != null && !pathAsString.isEmpty()) { + String image = ""; //NON-NLS + desc.addContent(image); + } + } + + desc.addContent(description + "File Path: " + path.toString()); + placemark.addContent(desc); + if (timestamp != null) { + Element time = new Element("TimeStamp", ns); //NON-NLS + time.addContent(new Element("when", ns).addContent(getTimeStamp(timestamp))); //NON-NLS + placemark.addContent(time); + } + placemark.addContent(feature); + if (coordinates != null && !coordinates.isEmpty()) { + placemark.addContent(new Element("snippet", ns).addContent(coordinates)); //NON-NLS + } + return placemark; + } + + /** + * Extracts the file to the output folder. + * + * @param inputFile The input AbstractFile to copy + * @param outputFile the output file + * + * @throws IOException + */ + private void copyFileUsingStream(AbstractFile inputFile, File outputFile) throws IOException { + byte[] buffer = new byte[65536]; + int length; + outputFile.createNewFile(); + try (InputStream is = new ReadContentInputStream(inputFile); + OutputStream os = new FileOutputStream(outputFile)) { while ((length = is.read(buffer)) != -1) { os.write(buffer, 0, length); } - - } finally { - is.close(); - os.close(); } } diff --git a/Core/src/org/sleuthkit/autopsy/report/stylesheets/style.kml b/Core/src/org/sleuthkit/autopsy/report/stylesheets/style.kml new file mode 100755 index 0000000000..aeb3992df1 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/report/stylesheets/style.kml @@ -0,0 +1,299 @@ + + + + + + normal + #n_YellowPushpin + + + highlight + #h_YellowPushpin + + + + + + + normal + #n_bluePushpin + + + highlight + #h_bluePushpin + + + + + + + normal + #n_redPushpin + + + highlight + #h_redPushpin + + + + + + + normal + #n_greenPushpin + + + highlight + #h_greenPushpin + + + + + + + normal + #n_purplePushpin + + + highlight + #h_purplePushpin + + + + + + + normal + #n_whitePushpin + + + highlight + #h_whitePushpin + + + + + + \ No newline at end of file diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalEditListPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalEditListPanel.java index 5b3364451e..c848b2f8ff 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalEditListPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalEditListPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2015 Basis Technology Corp. + * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -45,6 +45,13 @@ import org.openide.util.NbBundle; import org.sleuthkit.autopsy.corecomponents.OptionsPanel; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestManager; +import java.awt.Component; +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import static javax.swing.SwingConstants.CENTER; +import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.table.TableCellRenderer; +import org.openide.util.NbBundle.Messages; /** * GlobalEditListPanel widget to manage keywords in lists @@ -52,7 +59,8 @@ import org.sleuthkit.autopsy.ingest.IngestManager; class GlobalEditListPanel extends javax.swing.JPanel implements ListSelectionListener, OptionsPanel { private static final Logger logger = Logger.getLogger(GlobalEditListPanel.class.getName()); - private KeywordTableModel tableModel; + private static final long serialVersionUID = 1L; + private final KeywordTableModel tableModel; private KeywordList currentKeywordList; private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); @@ -85,7 +93,8 @@ class GlobalEditListPanel extends javax.swing.JPanel implements ListSelectionLis column.setPreferredWidth(((int) (width * 0.90))); } else { column.setPreferredWidth(((int) (width * 0.10))); - //column.setCellRenderer(new CheckBoxRenderer()); + column.setCellRenderer(new CheckBoxRenderer()); + column.setHeaderRenderer(new HeaderRenderer(keywordTable)); } } keywordTable.setCellSelectionEnabled(false); @@ -686,4 +695,55 @@ class GlobalEditListPanel extends javax.swing.JPanel implements ListSelectionLis resync(); } } + + /** + * A cell renderer for boolean cells that shows a center-aligned green check + * mark if true, nothing if false. + */ + private class CheckBoxRenderer extends DefaultTableCellRenderer { + + private static final long serialVersionUID = 1L; + final ImageIcon theCheck = new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/keywordsearch/checkmark.png")); // NON-NLS + + CheckBoxRenderer() { + setHorizontalAlignment(CENTER); + } + + @Override + @Messages("IsRegularExpression=Keyword is a regular expression") + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + + if ((value instanceof Boolean)) { + if ((Boolean) value) { + setIcon(theCheck); + setToolTipText(Bundle.IsRegularExpression()); + } else { + setIcon(null); + setToolTipText(null); + } + } + return this; + } + } + + /** + * A cell renderer for header cells that center-aligns the header text. + */ + private static class HeaderRenderer implements TableCellRenderer { + + private DefaultTableCellRenderer renderer; + + public HeaderRenderer(JTable table) { + renderer = (DefaultTableCellRenderer) table.getTableHeader().getDefaultRenderer(); + renderer.setHorizontalAlignment(JLabel.CENTER); + } + + @Override + public Component getTableCellRendererComponent( + JTable table, Object value, boolean isSelected, + boolean hasFocus, int row, int col) { + return renderer.getTableCellRendererComponent( + table, value, isSelected, hasFocus, row, col); + } + } } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/checkmark.png b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/checkmark.png new file mode 100755 index 0000000000..31d82447c8 Binary files /dev/null and b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/checkmark.png differ