20 package org.sleuthkit.autopsy.report.modules.kml;
23 import javax.swing.JPanel;
24 import org.openide.util.NbBundle;
29 import java.io.FileOutputStream;
30 import java.io.IOException;
31 import java.io.InputStream;
32 import java.io.OutputStream;
33 import java.nio.file.Path;
34 import java.nio.file.Paths;
35 import java.text.SimpleDateFormat;
36 import java.util.List;
37 import java.util.logging.Level;
38 import java.util.stream.Collectors;
39 import org.jdom2.Document;
40 import org.jdom2.Element;
41 import org.jdom2.Namespace;
42 import org.jdom2.output.Format;
43 import org.jdom2.output.XMLOutputter;
44 import org.jdom2.CDATA;
45 import org.openide.filesystems.FileUtil;
46 import org.openide.util.NbBundle.Messages;
60 import org.
sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException;
71 private static final String
REPORT_KML =
"ReportKML.kml";
72 private static final String
STYLESHEETS_PATH =
"/org/sleuthkit/autopsy/report/stylesheets/";
76 private final SimpleDateFormat
kmlDateFormat =
new SimpleDateFormat(
"yyyy-MM-dd'T'HH:mm:ssX");
93 RED(
"style.kml#redFeature"),
94 GREEN(
"style.kml#greenFeature"),
95 BLUE(
"style.kml#blueFeature"),
97 WHITE(
"style.kml#whiteFeature"),
116 if (instance == null) {
130 "KMLReport.failedToCompleteReport=Failed to complete report.",
131 "KMLReport.partialFailure=There was an error creating the report. Some items were not exported.",
132 "KMLReport.unableToExtractPhotos=Could not extract photo information.",
133 "KMLReport.exifPhotoError=Could not extract photos with EXIF metadata.",
134 "KMLReport.bookmarkError=Could not extract Bookmark information.",
135 "KMLReport.gpsBookmarkError=Could not get GPS Bookmarks from database.",
136 "KMLReport.locationError=Could not extract Last Known Location information.",
137 "KMLReport.locationDatabaseError=Could not get GPS Last Known Location from database.",
138 "KMLReport.gpsRouteError=Could not extract GPS Route information.",
139 "KMLReport.gpsRouteDatabaseError=Could not get GPS Routes from database.",
140 "KMLReport.gpsSearchDatabaseError=Could not get GPS Searches from database.",
141 "KMLReport.trackpointError=Could not extract Trackpoint information.",
142 "KMLReport.trackpointDatabaseError=Could not get GPS Trackpoints from database.",
143 "KMLReport.stylesheetError=Error placing KML stylesheet. The .KML file will not function properly.",
144 "KMLReport.kmlFileWriteError=Could not write the KML file.",
146 "KMLReport.errorGeneratingReport=Error adding {0} to case as a report.",
147 "KMLReport.unableToOpenCase=Exception while getting open case.",
148 "Waypoint_Bookmark_Display_String=GPS Bookmark",
149 "Waypoint_Last_Known_Display_String=GPS Last Known Location",
150 "Waypoint_EXIF_Display_String=EXIF Metadata With Location",
151 "Waypoint_Route_Point_Display_String=GPS Individual Route Point",
152 "Waypoint_Search_Display_String=GPS Search",
153 "Waypoint_Trackpoint_Display_String=GPS Trackpoint",
154 "Waypoint_Track_Display_String=GPS Track",
155 "Route_Details_Header=GPS Route",
156 "ReportBodyFile.ingestWarning.text=Ingest Warning message",
157 "Waypoint_Track_Point_Display_String=GPS Individual Track Point"
177 logger.log(Level.SEVERE,
"Exception while getting open case.", ex);
187 .collect(Collectors.toList());
189 }
catch (TskCoreException ex) {
190 logger.log(Level.SEVERE,
"Could not get the datasources from the case", ex);
199 progressPanel.
start();
200 progressPanel.
updateStatusLabel(NbBundle.getMessage(
this.getClass(),
"ReportKML.progress.querying"));
201 String kmlFileFullPath = baseReportDir +
REPORT_KML;
202 String errorMessage =
"";
206 progressPanel.
updateStatusLabel(NbBundle.getMessage(
this.getClass(),
"ReportKML.progress.loading"));
214 boolean entirelySuccessful = makeTracks(skCase);
215 if (!entirelySuccessful) {
217 errorMessage = Bundle.KMLReport_partialFailure();
220 addLocationsToReport(skCase, baseReportDir);
222 errorMessage = Bundle.KMLReport_failedToCompleteReport();
223 logger.log(Level.SEVERE, errorMessage, ex);
229 InputStream input = getClass().getResourceAsStream(STYLESHEETS_PATH + KML_STYLE_FILE);
230 OutputStream output =
new FileOutputStream(baseReportDir + KML_STYLE_FILE);
231 FileUtil.copy(input, output);
232 }
catch (IOException ex) {
233 errorMessage = Bundle.KMLReport_stylesheetError();
234 logger.log(Level.SEVERE, errorMessage, ex);
238 try (FileOutputStream writer =
new FileOutputStream(kmlFileFullPath)) {
239 XMLOutputter outputter =
new XMLOutputter(Format.getPrettyFormat());
240 outputter.output(kmlDocument, writer);
241 String prependedStatus =
"";
243 prependedStatus =
"Incomplete ";
246 NbBundle.getMessage(
this.getClass(),
"ReportKML.genReport.srcModuleName.text"),
247 prependedStatus + NbBundle.getMessage(
this.getClass(),
"ReportKML.genReport.reportName"));
248 }
catch (IOException ex) {
249 errorMessage = Bundle.KMLReport_kmlFileWriteError();
250 logger.log(Level.SEVERE, errorMessage, ex);
252 }
catch (TskCoreException ex) {
253 errorMessage = Bundle.KMLReport_errorGeneratingReport(kmlFileFullPath);
254 logger.log(Level.SEVERE, errorMessage, ex);
257 errorMessage = Bundle.KMLReport_unableToOpenCase();
258 logger.log(Level.SEVERE, errorMessage, ex);
262 progressPanel.
complete(result, errorMessage);
271 ns = Namespace.getNamespace(
"",
"http://www.opengis.net/kml/2.2");
273 Element kml =
new Element(
"kml", ns);
274 kml.addNamespaceDeclaration(Namespace.getNamespace(
"gx",
"http://www.google.com/kml/ext/2.2"));
275 kml.addNamespaceDeclaration(Namespace.getNamespace(
"kml",
"http://www.opengis.net/kml/2.2"));
276 kml.addNamespaceDeclaration(Namespace.getNamespace(
"atom",
"http://www.w3.org/2005/Atom"));
277 Document kmlDocument =
new Document(kml);
279 Element document =
new Element(
"Document", ns);
280 kml.addContent(document);
282 Element name =
new Element(
"name", ns);
285 document.addContent(name);
289 Element ingestwarning =
new Element(
"snippet", ns);
290 ingestwarning.addContent(NbBundle.getMessage(
this.getClass(),
"ReportBodyFile.ingestWarning.text"));
291 document.addContent(ingestwarning);
295 gpsExifMetadataFolder =
new Element(
"Folder", ns);
296 CDATA cdataExifMetadataFolder =
new CDATA(
"https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/camera-icon-16.png");
297 Element hrefExifMetadata =
new Element(
"href", ns).addContent(cdataExifMetadataFolder);
298 gpsExifMetadataFolder.addContent(
new Element(
"Icon", ns).addContent(hrefExifMetadata));
300 gpsBookmarksFolder =
new Element(
"Folder", ns);
301 CDATA cdataBookmarks =
new CDATA(
"https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gpsfav.png");
302 Element hrefBookmarks =
new Element(
"href", ns).addContent(cdataBookmarks);
303 gpsBookmarksFolder.addContent(
new Element(
"Icon", ns).addContent(hrefBookmarks));
305 gpsLastKnownLocationFolder =
new Element(
"Folder", ns);
306 CDATA cdataLastKnownLocation =
new CDATA(
"https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-lastlocation.png");
307 Element hrefLastKnownLocation =
new Element(
"href", ns).addContent(cdataLastKnownLocation);
308 gpsLastKnownLocationFolder.addContent(
new Element(
"Icon", ns).addContent(hrefLastKnownLocation));
310 gpsRouteFolder =
new Element(
"Folder", ns);
311 CDATA cdataRoute =
new CDATA(
"https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-trackpoint.png");
312 Element hrefRoute =
new Element(
"href", ns).addContent(cdataRoute);
313 gpsRouteFolder.addContent(
new Element(
"Icon", ns).addContent(hrefRoute));
315 gpsSearchesFolder =
new Element(
"Folder", ns);
316 CDATA cdataSearches =
new CDATA(
"https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-search.png");
317 Element hrefSearches =
new Element(
"href", ns).addContent(cdataSearches);
318 gpsSearchesFolder.addContent(
new Element(
"Icon", ns).addContent(hrefSearches));
320 gpsTrackpointsFolder =
new Element(
"Folder", ns);
321 CDATA cdataTrackpoints =
new CDATA(
"https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-trackpoint.png");
322 Element hrefTrackpoints =
new Element(
"href", ns).addContent(cdataTrackpoints);
323 gpsTrackpointsFolder.addContent(
new Element(
"Icon", ns).addContent(hrefTrackpoints));
325 gpsTracksFolder =
new Element(
"Folder", ns);
326 CDATA cdataTrack =
new CDATA(
"https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-trackpoint.png");
327 Element hrefTrack =
new Element(
"href", ns).addContent(cdataTrack);
328 gpsTracksFolder.addContent(
new Element(
"Icon", ns).addContent(hrefTrack));
330 gpsExifMetadataFolder.addContent(
new Element(
"name", ns).addContent(
"EXIF Metadata"));
331 gpsBookmarksFolder.addContent(
new Element(
"name", ns).addContent(
"GPS Bookmarks"));
332 gpsLastKnownLocationFolder.addContent(
new Element(
"name", ns).addContent(
"GPS Last Known Location"));
333 gpsRouteFolder.addContent(
new Element(
"name", ns).addContent(
"GPS Routes"));
334 gpsSearchesFolder.addContent(
new Element(
"name", ns).addContent(
"GPS Searches"));
335 gpsTrackpointsFolder.addContent(
new Element(
"name", ns).addContent(
"GPS Trackpoints"));
336 gpsTracksFolder.addContent(
new Element(
"name", ns).addContent(
"GPS Tracks"));
338 document.addContent(gpsExifMetadataFolder);
339 document.addContent(gpsBookmarksFolder);
340 document.addContent(gpsLastKnownLocationFolder);
341 document.addContent(gpsRouteFolder);
342 document.addContent(gpsSearchesFolder);
343 document.addContent(gpsTrackpointsFolder);
344 document.addContent(gpsTracksFolder);
358 void addExifMetadataContent(List<Waypoint> points, String baseReportDirectory)
throws IOException, TskCoreException {
365 if (mapPoint == null) {
369 AbstractFile abstractFile = point.getImage();
373 copyFileUsingStream(abstractFile, Paths.get(baseReportDirectory, abstractFile.getName()).toFile());
376 }
catch (TskCoreException ex) {
377 path = Paths.get(abstractFile.getParentPath(), abstractFile.getName());
380 path = Paths.get(abstractFile.getName());
396 void addLocationsToReport(SleuthkitCase skCase, String baseReportDir)
throws GeoLocationDataException, IOException, TskCoreException {
397 if (waypointList == null) {
398 addExifMetadataContent(WaypointBuilder.getEXIFWaypoints(skCase), baseReportDir);
399 addWaypoints(WaypointBuilder.getBookmarkWaypoints(skCase),
gpsBookmarksFolder, FeatureColor.BLUE, Bundle.Waypoint_Bookmark_Display_String());
400 addWaypoints(WaypointBuilder.getLastKnownWaypoints(skCase),
gpsLastKnownLocationFolder, FeatureColor.PURPLE, Bundle.Waypoint_Last_Known_Display_String());
401 addWaypoints(WaypointBuilder.getSearchWaypoints(skCase),
gpsSearchesFolder, FeatureColor.WHITE, Bundle.Waypoint_Search_Display_String());
402 addWaypoints(WaypointBuilder.getTrackpointWaypoints(skCase),
gpsTrackpointsFolder, FeatureColor.WHITE, Bundle.Waypoint_Trackpoint_Display_String());
404 addExifMetadataContent(WaypointBuilder.getEXIFWaypoints(waypointList), baseReportDir);
405 addWaypoints(WaypointBuilder.getBookmarkWaypoints(waypointList),
gpsBookmarksFolder, FeatureColor.BLUE, Bundle.Waypoint_Bookmark_Display_String());
406 addWaypoints(WaypointBuilder.getLastKnownWaypoints(waypointList),
gpsLastKnownLocationFolder, FeatureColor.PURPLE, Bundle.Waypoint_Last_Known_Display_String());
407 addWaypoints(WaypointBuilder.getSearchWaypoints(waypointList),
gpsSearchesFolder, FeatureColor.WHITE, Bundle.Waypoint_Search_Display_String());
408 addWaypoints(WaypointBuilder.getTrackpointWaypoints(waypointList),
gpsTrackpointsFolder, FeatureColor.WHITE, Bundle.Waypoint_Trackpoint_Display_String());
420 void addWaypoints(List<Waypoint> points, Element folder, FeatureColor waypointColor, String headerLabel)
throws TskCoreException {
421 for (Waypoint point : points) {
425 addContent(folder, point.getLabel(), waypointColor,
getFormattedDetails(point, headerLabel), point.getTimestamp(),
makePoint(point), point.getLatitude(), point.getLongitude());
441 void addContent(Element folder, String waypointLabel, FeatureColor waypointColor, String formattedDetails, Long timestamp, Element point, Double latitude, Double longitude) {
442 if (folder != null && point != null) {
444 folder.addContent(
makePlacemark(waypointLabel, waypointColor, formattedDetails, timestamp, point, formattedCords));
455 void makeRoutes(SleuthkitCase skCase)
throws GeoLocationDataException, TskCoreException {
456 List<Route> routes = null;
458 if (waypointList == null) {
459 routes = Route.getRoutes(skCase);
461 routes = WaypointBuilder.getRoutes(waypointList);
464 for (Route route : routes) {
478 List<Waypoint> routePoints = route.
getRoute();
486 if (routePoints != null && routePoints.size() > 1) {
487 start = routePoints.get(0);
488 end = routePoints.get(1);
491 if (start == null || end == null) {
504 if (reportRoute != null) {
508 if (startingPoint != null) {
514 if (endingPoint != null) {
530 boolean makeTracks(SleuthkitCase skCase)
throws GeoLocationDataException, TskCoreException {
531 List<Track> tracks = null;
532 boolean successful =
true;
534 if (waypointList == null) {
539 tracks = WaypointBuilder.getTracks(waypointList);
542 for (Track track : tracks) {
558 List<Waypoint> trackPoints = track.
getPath();
562 Element trackFolder =
new Element(
"Folder", ns);
563 trackFolder.addContent(
new Element(
"name", ns).addContent(track.
getLabel()));
564 gpsTracksFolder.addContent(trackFolder);
566 for (
Waypoint point : trackPoints) {
567 Element element =
makePoint(point.getLatitude(), point.getLongitude(), point.getAltitude());
570 point.getTimestamp(), element,
formattedCoordinates(point.getLatitude(), point.getLongitude())));
582 return kmlDateFormat.format(
new java.util.Date(timeStamp * 1000));
607 private Element
makePoint(Double latitude, Double longitude, Double altitude) {
608 if (latitude == null || longitude == null) {
612 Element point =
new Element(
"Point", ns);
615 Element coordinates =
new Element(
"coordinates", ns).addContent(longitude +
"," + latitude +
"," + (altitude != null ? altitude : 0.0));
617 if (altitude != null && altitude != 0) {
625 Element altitudeMode =
new Element(
"altitudeMode", ns).addContent(
"clampToGround");
626 point.addContent(altitudeMode);
628 point.addContent(coordinates);
649 private Element
makeLineString(Double startLatitude, Double startLongitude, Double stopLatitude, Double stopLongitude) {
650 if (startLatitude == null || startLongitude == null || stopLatitude == null || stopLongitude == null) {
654 Element lineString =
new Element(
"LineString", ns);
655 lineString.addContent(
new Element(
"extrude", ns).addContent(
"1"));
656 lineString.addContent(
new Element(
"tessellate", ns).addContent(
"1"));
657 lineString.addContent(
new Element(
"altitudeMode", ns).addContent(
"clampToGround"));
659 lineString.addContent(
new Element(
"coordinates", ns).addContent(
660 startLongitude +
"," + startLatitude +
",0.0,"
661 + stopLongitude +
"," + stopLatitude +
",0.0"));
679 private Element
makePlacemark(String name,
FeatureColor color, String description, Long timestamp, Element feature, String coordinates) {
680 Element placemark =
new Element(
"Placemark", ns);
681 if (name != null && !name.isEmpty()) {
682 placemark.addContent(
new Element(
"name", ns).addContent(name));
683 }
else if (timestamp != null) {
684 placemark.addContent(
new Element(
"name", ns).addContent(
getTimeStamp(timestamp)));
686 placemark.addContent(
new Element(
"name", ns).addContent(
""));
688 placemark.addContent(
new Element(
"styleUrl", ns).addContent(color.
getColor()));
689 placemark.addContent(
new Element(
"description", ns).addContent(description));
690 if (timestamp != null) {
691 Element time =
new Element(
"TimeStamp", ns);
692 time.addContent(
new Element(
"when", ns).addContent(
getTimeStamp(timestamp)));
693 placemark.addContent(time);
695 placemark.addContent(feature);
696 if (coordinates != null && !coordinates.isEmpty()) {
697 placemark.addContent(
new Element(
"snippet", ns).addContent(coordinates));
718 Element placemark =
new Element(
"Placemark", ns);
719 Element desc =
new Element(
"description", ns);
720 if (name != null && !name.isEmpty()) {
721 placemark.addContent(
new Element(
"name", ns).addContent(name));
722 String image =
"<img src='" + name +
"' width='400'/>";
723 desc.addContent(image);
725 placemark.addContent(
new Element(
"styleUrl", ns).addContent(color.
getColor()));
727 String pathAsString = path.toString();
728 if (pathAsString != null && !pathAsString.isEmpty()) {
729 desc.addContent(description +
"<b>Source Path:</b> " + pathAsString);
732 placemark.addContent(desc);
734 if (timestamp != null) {
735 Element time =
new Element(
"TimeStamp", ns);
736 time.addContent(
new Element(
"when", ns).addContent(
getTimeStamp(timestamp)));
737 placemark.addContent(time);
739 placemark.addContent(feature);
740 if (coordinates != null && !coordinates.isEmpty()) {
741 placemark.addContent(
new Element(
"snippet", ns).addContent(coordinates));
756 private void copyFileUsingStream(AbstractFile inputFile, File outputFile)
throws ReadContentInputStreamException, IOException {
757 byte[] buffer =
new byte[65536];
759 outputFile.createNewFile();
760 try (InputStream is =
new ReadContentInputStream(inputFile);
761 OutputStream os =
new FileOutputStream(outputFile)) {
762 while ((length = is.read(buffer)) != -1) {
763 os.write(buffer, 0, length);
770 String name = NbBundle.getMessage(this.getClass(),
"ReportKML.getName.text");
776 return "ReportKML.kml";
781 String desc = NbBundle.getMessage(this.getClass(),
"ReportKML.getDesc.text");
802 String[] pathSegments = uniquePath.replaceFirst(
"^/*",
"").split(
"/");
805 if (pathSegments.length > 0) {
806 pathSegments[0] = pathSegments[0].replaceFirst(
"^img_",
"");
808 if (pathSegments.length > 1) {
809 pathSegments[1] = pathSegments[1].replaceFirst(
"^vol_",
"");
813 StringBuilder strbuf =
new StringBuilder();
814 for (String segment : pathSegments) {
815 if (!segment.isEmpty()) {
816 strbuf.append(
"/").append(segment);
819 return strbuf.toString();
831 StringBuilder result =
new StringBuilder();
832 result.append(String.format(
"<h3>%s</h3>", header))
836 if (timestamp != null) {
849 String value = prop.getValue();
850 if (value != null && !value.isEmpty()) {
855 return result.toString();
867 return String.format(HTML_PROP_FORMAT, title, value);
878 List<Waypoint> points = route.
getRoute();
879 StringBuilder result =
new StringBuilder();
881 result.append(String.format(
"<h3>%s</h3>", Bundle.Route_Details_Header()))
885 if (timestamp != null) {
889 if (points.size() > 1) {
897 if (altitude != null) {
905 if (altitude != null) {
912 String value = prop.getValue();
913 if (value != null && !value.isEmpty()) {
918 return result.toString();
930 if (latitude == null || longitude == null) {
934 return String.format(
"%.2f, %.2f", latitude, longitude);
944 long dataSourceId = content.getDataSource().getId();
static final String HTML_PROP_FORMAT
List< Content > getDataSources()
boolean shouldFilterFromReport(Content content)
void copyFileUsingStream(AbstractFile inputFile, File outputFile)
Element makeLineString(Double startLatitude, Double startLongitude, Double stopLatitude, Double stopLongitude)
String getRelativeFilePath()
static synchronized IngestManager getInstance()
List< Waypoint > waypointList
List< Waypoint.Property > getOtherProperties()
void generateReport(GeneralReportSettings settings, ReportProgressPanel progressPanel)
String getFormattedDetails(Route route)
void complete(ReportStatus reportStatus)
static GeoLocationParseResult< Track > getTracks(SleuthkitCase skCase, List<?extends Content > sourceList)
Element makePlacemarkWithPicture(String name, FeatureColor color, String description, Long timestamp, Element feature, Path path, String coordinates)
Element makePoint(Double latitude, Double longitude, Double altitude)
boolean isIngestRunning()
Element gpsTrackpointsFolder
Element gpsBookmarksFolder
List< Waypoint > getRoute()
void addReport(String localPath, String srcModuleName, String reportName)
void setIndeterminate(boolean indeterminate)
void addRouteToReport(Route route)
static String removeLeadingImgAndVol(String uniquePath)
void addTrackToReport(Track track)
Element gpsExifMetadataFolder
Document setupReportDocument()
static final String KML_STYLE_FILE
static final Logger logger
static synchronized KMLReport getDefault()
void setReportDirectoryPath(String reportDirectoryPath)
final List< Waypoint > getPath()
List< Waypoint.Property > getOtherProperties()
void generateReport(String baseReportDir, ReportProgressPanel progressPanel, List< Waypoint > waypointList)
SleuthkitCase getSleuthkitCase()
static final String REPORT_KML
FeatureColor(String color)
String formatAttribute(String title, String value)
static KMLReport instance
Element makePlacemark(String name, FeatureColor color, String description, Long timestamp, Element feature, String coordinates)
List< Long > getSelectedDataSources()
Element gpsLastKnownLocationFolder
synchronized static Logger getLogger(String name)
static Case getCurrentCaseThrows()
void updateStatusLabel(String statusMessage)
Element gpsSearchesFolder
void setSelectedDataSources(List< Long > selectedDataSources)
boolean isSuccessfullyParsed()
String getTimeStamp(long timeStamp)
final SimpleDateFormat kmlDateFormat
boolean supportsDataSourceSelection()
Element makePoint(Waypoint point)
String getFormattedDetails(Waypoint point, String header)
String getReportDirectoryPath()
JPanel getConfigurationPanel()
GeneralReportSettings settings
static final String STYLESHEETS_PATH
String formattedCoordinates(Double latitude, Double longitude)