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.ArrayList;
37 import java.util.List;
38 import java.util.logging.Level;
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;
56 import org.
sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException;
67 private static final String
REPORT_KML =
"ReportKML.kml";
68 private static final String
STYLESHEETS_PATH =
"/org/sleuthkit/autopsy/report/stylesheets/";
72 private final SimpleDateFormat
kmlDateFormat =
new SimpleDateFormat(
"yyyy-MM-dd'T'HH:mm:ssX");
86 RED(
"style.kml#redFeature"),
87 GREEN(
"style.kml#greenFeature"),
88 BLUE(
"style.kml#blueFeature"),
90 WHITE(
"style.kml#whiteFeature"),
109 if (instance == null) {
123 "KMLReport.unableToExtractPhotos=Could not extract photo information.",
124 "KMLReport.exifPhotoError=Could not extract photos with EXIF metadata.",
125 "KMLReport.bookmarkError=Could not extract Bookmark information.",
126 "KMLReport.gpsBookmarkError=Could not get GPS Bookmarks from database.",
127 "KMLReport.locationError=Could not extract Last Known Location information.",
128 "KMLReport.locationDatabaseError=Could not get GPS Last Known Location from database.",
129 "KMLReport.gpsRouteError=Could not extract GPS Route information.",
130 "KMLReport.gpsRouteDatabaseError=Could not get GPS Routes from database.",
131 "KMLReport.gpsSearchDatabaseError=Could not get GPS Searches from database.",
132 "KMLReport.trackpointError=Could not extract Trackpoint information.",
133 "KMLReport.trackpointDatabaseError=Could not get GPS Trackpoints from database.",
134 "KMLReport.stylesheetError=Error placing KML stylesheet. The .KML file will not function properly.",
135 "KMLReport.kmlFileWriteError=Could not write the KML file.",
137 "KMLReport.errorGeneratingReport=Error adding {0} to case as a report.",
138 "KMLReport.unableToOpenCase=Exception while getting open case.",
139 "Waypoint_Bookmark_Display_String=GPS Bookmark",
140 "Waypoint_Last_Known_Display_String=GPS Last Known Location",
141 "Waypoint_EXIF_Display_String=EXIF Metadata With Location",
142 "Waypoint_Route_Point_Display_String=GPS Individual Route Point",
143 "Waypoint_Search_Display_String=GPS Search",
144 "Waypoint_Trackpoint_Display_String=GPS Trackpoint",
145 "Route_Details_Header=GPS Route"
158 logger.log(Level.SEVERE,
"Exception while getting open case.", ex);
163 progressPanel.
start();
164 progressPanel.
updateStatusLabel(NbBundle.getMessage(
this.getClass(),
"ReportKML.progress.querying"));
165 String kmlFileFullPath = baseReportDir +
REPORT_KML;
166 String errorMessage =
"";
170 progressPanel.
updateStatusLabel(NbBundle.getMessage(
this.getClass(),
"ReportKML.progress.loading"));
178 addLocationsToReport(skCase, baseReportDir);
180 errorMessage =
"Failed to complete report.";
181 logger.log(Level.SEVERE, errorMessage, ex);
187 InputStream input = getClass().getResourceAsStream(STYLESHEETS_PATH + KML_STYLE_FILE);
188 OutputStream output =
new FileOutputStream(baseReportDir + KML_STYLE_FILE);
189 FileUtil.copy(input, output);
190 }
catch (IOException ex) {
191 errorMessage = Bundle.KMLReport_stylesheetError();
192 logger.log(Level.SEVERE, errorMessage, ex);
196 try (FileOutputStream writer =
new FileOutputStream(kmlFileFullPath)) {
197 XMLOutputter outputter =
new XMLOutputter(Format.getPrettyFormat());
198 outputter.output(kmlDocument, writer);
199 String prependedStatus =
"";
201 prependedStatus =
"Incomplete ";
204 NbBundle.getMessage(
this.getClass(),
"ReportKML.genReport.srcModuleName.text"),
205 prependedStatus + NbBundle.getMessage(
this.getClass(),
"ReportKML.genReport.reportName"));
206 }
catch (IOException ex) {
207 errorMessage = Bundle.KMLReport_kmlFileWriteError();
208 logger.log(Level.SEVERE, errorMessage, ex);
210 }
catch (TskCoreException ex) {
211 errorMessage = Bundle.KMLReport_errorGeneratingReport(kmlFileFullPath);
212 logger.log(Level.SEVERE, errorMessage, ex);
215 errorMessage = Bundle.KMLReport_unableToOpenCase();
216 logger.log(Level.SEVERE, errorMessage, ex);
220 progressPanel.
complete(result, errorMessage);
229 ns = Namespace.getNamespace(
"",
"http://www.opengis.net/kml/2.2");
231 Element kml =
new Element(
"kml", ns);
232 kml.addNamespaceDeclaration(Namespace.getNamespace(
"gx",
"http://www.google.com/kml/ext/2.2"));
233 kml.addNamespaceDeclaration(Namespace.getNamespace(
"kml",
"http://www.opengis.net/kml/2.2"));
234 kml.addNamespaceDeclaration(Namespace.getNamespace(
"atom",
"http://www.w3.org/2005/Atom"));
235 Document kmlDocument =
new Document(kml);
237 Element document =
new Element(
"Document", ns);
238 kml.addContent(document);
240 Element name =
new Element(
"name", ns);
243 document.addContent(name);
247 Element ingestwarning =
new Element(
"snippet", ns);
248 ingestwarning.addContent(NbBundle.getMessage(
this.getClass(),
"ReportBodyFile.ingestWarning.text"));
249 document.addContent(ingestwarning);
253 gpsExifMetadataFolder =
new Element(
"Folder", ns);
254 CDATA cdataExifMetadataFolder =
new CDATA(
"https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/camera-icon-16.png");
255 Element hrefExifMetadata =
new Element(
"href", ns).addContent(cdataExifMetadataFolder);
256 gpsExifMetadataFolder.addContent(
new Element(
"Icon", ns).addContent(hrefExifMetadata));
258 gpsBookmarksFolder =
new Element(
"Folder", ns);
259 CDATA cdataBookmarks =
new CDATA(
"https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gpsfav.png");
260 Element hrefBookmarks =
new Element(
"href", ns).addContent(cdataBookmarks);
261 gpsBookmarksFolder.addContent(
new Element(
"Icon", ns).addContent(hrefBookmarks));
263 gpsLastKnownLocationFolder =
new Element(
"Folder", ns);
264 CDATA cdataLastKnownLocation =
new CDATA(
"https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-lastlocation.png");
265 Element hrefLastKnownLocation =
new Element(
"href", ns).addContent(cdataLastKnownLocation);
266 gpsLastKnownLocationFolder.addContent(
new Element(
"Icon", ns).addContent(hrefLastKnownLocation));
268 gpsRouteFolder =
new Element(
"Folder", ns);
269 CDATA cdataRoute =
new CDATA(
"https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-trackpoint.png");
270 Element hrefRoute =
new Element(
"href", ns).addContent(cdataRoute);
271 gpsRouteFolder.addContent(
new Element(
"Icon", ns).addContent(hrefRoute));
273 gpsSearchesFolder =
new Element(
"Folder", ns);
274 CDATA cdataSearches =
new CDATA(
"https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-search.png");
275 Element hrefSearches =
new Element(
"href", ns).addContent(cdataSearches);
276 gpsSearchesFolder.addContent(
new Element(
"Icon", ns).addContent(hrefSearches));
278 gpsTrackpointsFolder =
new Element(
"Folder", ns);
279 CDATA cdataTrackpoints =
new CDATA(
"https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-trackpoint.png");
280 Element hrefTrackpoints =
new Element(
"href", ns).addContent(cdataTrackpoints);
281 gpsTrackpointsFolder.addContent(
new Element(
"Icon", ns).addContent(hrefTrackpoints));
283 gpsExifMetadataFolder.addContent(
new Element(
"name", ns).addContent(
"EXIF Metadata"));
284 gpsBookmarksFolder.addContent(
new Element(
"name", ns).addContent(
"GPS Bookmarks"));
285 gpsLastKnownLocationFolder.addContent(
new Element(
"name", ns).addContent(
"GPS Last Known Location"));
286 gpsRouteFolder.addContent(
new Element(
"name", ns).addContent(
"GPS Routes"));
287 gpsSearchesFolder.addContent(
new Element(
"name", ns).addContent(
"GPS Searches"));
288 gpsTrackpointsFolder.addContent(
new Element(
"name", ns).addContent(
"GPS Trackpoints"));
290 document.addContent(gpsExifMetadataFolder);
291 document.addContent(gpsBookmarksFolder);
292 document.addContent(gpsLastKnownLocationFolder);
293 document.addContent(gpsRouteFolder);
294 document.addContent(gpsSearchesFolder);
295 document.addContent(gpsTrackpointsFolder);
309 void addExifMetadataContent(List<Waypoint> points, String baseReportDirectory)
throws IOException {
312 if (mapPoint == null) {
316 AbstractFile abstractFile = point.getImage();
320 copyFileUsingStream(abstractFile, Paths.get(baseReportDirectory, abstractFile.getName()).toFile());
323 }
catch (TskCoreException ex) {
324 path = Paths.get(abstractFile.getParentPath(), abstractFile.getName());
327 path = Paths.get(abstractFile.getName());
343 void addLocationsToReport(SleuthkitCase skCase, String baseReportDir)
throws GeoLocationDataException, IOException {
344 if (waypointList == null) {
345 addExifMetadataContent(WaypointBuilder.getEXIFWaypoints(skCase), baseReportDir);
346 addWaypoints(WaypointBuilder.getBookmarkWaypoints(skCase),
gpsBookmarksFolder, FeatureColor.BLUE, Bundle.Waypoint_Bookmark_Display_String());
347 addWaypoints(WaypointBuilder.getLastKnownWaypoints(skCase),
gpsLastKnownLocationFolder, FeatureColor.PURPLE, Bundle.Waypoint_Last_Known_Display_String());
348 addWaypoints(WaypointBuilder.getSearchWaypoints(skCase),
gpsSearchesFolder, FeatureColor.WHITE, Bundle.Waypoint_Search_Display_String());
349 addWaypoints(WaypointBuilder.getTrackpointWaypoints(skCase),
gpsTrackpointsFolder, FeatureColor.WHITE, Bundle.Waypoint_Trackpoint_Display_String());
351 addExifMetadataContent(WaypointBuilder.getEXIFWaypoints(waypointList), baseReportDir);
352 addWaypoints(WaypointBuilder.getBookmarkWaypoints(waypointList),
gpsBookmarksFolder, FeatureColor.BLUE, Bundle.Waypoint_Bookmark_Display_String());
353 addWaypoints(WaypointBuilder.getLastKnownWaypoints(waypointList),
gpsLastKnownLocationFolder, FeatureColor.PURPLE, Bundle.Waypoint_Last_Known_Display_String());
354 addWaypoints(WaypointBuilder.getSearchWaypoints(waypointList),
gpsSearchesFolder, FeatureColor.WHITE, Bundle.Waypoint_Search_Display_String());
355 addWaypoints(WaypointBuilder.getTrackpointWaypoints(waypointList),
gpsTrackpointsFolder, FeatureColor.WHITE, Bundle.Waypoint_Trackpoint_Display_String());
367 void addWaypoints(List<Waypoint> points, Element folder, FeatureColor waypointColor, String headerLabel) {
368 for (Waypoint point : points) {
369 addContent(folder, point.getLabel(), waypointColor,
getFormattedDetails(point, headerLabel), point.getTimestamp(),
makePoint(point), point.getLatitude(), point.getLongitude());
385 void addContent(Element folder, String waypointLabel, FeatureColor waypointColor, String formattedDetails, Long timestamp, Element point, Double latitude, Double longitude) {
386 if (folder != null && point != null) {
388 folder.addContent(
makePlacemark(waypointLabel, waypointColor, formattedDetails, timestamp, point, formattedCords));
399 void makeRoutes(SleuthkitCase skCase)
throws GeoLocationDataException {
400 List<Route> routes = null;
402 if (waypointList == null) {
403 routes = Route.getRoutes(skCase);
405 routes =
new ArrayList<>();
408 for (Route route : routes) {
409 addRouteToReport(route);
413 void addRouteToReport(Route route) {
414 List<Waypoint> routePoints = route.getRoute();
415 Waypoint start = null;
422 if (routePoints != null && routePoints.size() > 1) {
423 start = routePoints.get(0);
424 end = routePoints.get(1);
427 if (start == null || end == null) {
431 Element reportRoute =
makeLineString(start.getLatitude(), start.getLongitude(), end.getLatitude(), end.getLongitude());
432 Element startingPoint =
makePoint(start.getLatitude(), start.getLongitude(), start.getAltitude());
433 Element endingPoint =
makePoint(end.getLatitude(), end.getLongitude(), end.getAltitude());
440 if (reportRoute != null) {
444 if (startingPoint != null) {
446 FeatureColor.GREEN,
getFormattedDetails(start, Bundle.Waypoint_Route_Point_Display_String()),
447 start.getTimestamp(), startingPoint, formattedStart));
450 if (endingPoint != null) {
454 end.getTimestamp(), endingPoint, formattedEnd));
466 return kmlDateFormat.format(
new java.util.Date(timeStamp * 1000));
491 private Element
makePoint(Double latitude, Double longitude, Double altitude) {
492 if (latitude == null || longitude == null) {
496 Element point =
new Element(
"Point", ns);
499 Element coordinates =
new Element(
"coordinates", ns).addContent(longitude +
"," + latitude +
"," + (altitude != null ? altitude : 0.0));
501 if (altitude != null && altitude != 0) {
509 Element altitudeMode =
new Element(
"altitudeMode", ns).addContent(
"clampToGround");
510 point.addContent(altitudeMode);
512 point.addContent(coordinates);
533 private Element
makeLineString(Double startLatitude, Double startLongitude, Double stopLatitude, Double stopLongitude) {
534 if (startLatitude == null || startLongitude == null || stopLatitude == null || stopLongitude == null) {
538 Element lineString =
new Element(
"LineString", ns);
539 lineString.addContent(
new Element(
"extrude", ns).addContent(
"1"));
540 lineString.addContent(
new Element(
"tessellate", ns).addContent(
"1"));
541 lineString.addContent(
new Element(
"altitudeMode", ns).addContent(
"clampToGround"));
543 lineString.addContent(
new Element(
"coordinates", ns).addContent(
544 startLongitude +
"," + startLatitude +
",0.0,"
545 + stopLongitude +
"," + stopLatitude +
",0.0"));
563 private Element
makePlacemark(String name,
FeatureColor color, String description, Long timestamp, Element feature, String coordinates) {
564 Element placemark =
new Element(
"Placemark", ns);
565 if (name != null && !name.isEmpty()) {
566 placemark.addContent(
new Element(
"name", ns).addContent(name));
567 }
else if (timestamp != null) {
568 placemark.addContent(
new Element(
"name", ns).addContent(
getTimeStamp(timestamp)));
570 placemark.addContent(
new Element(
"name", ns).addContent(
""));
572 placemark.addContent(
new Element(
"styleUrl", ns).addContent(color.
getColor()));
573 placemark.addContent(
new Element(
"description", ns).addContent(description));
574 if (timestamp != null) {
575 Element time =
new Element(
"TimeStamp", ns);
576 time.addContent(
new Element(
"when", ns).addContent(
getTimeStamp(timestamp)));
577 placemark.addContent(time);
579 placemark.addContent(feature);
580 if (coordinates != null && !coordinates.isEmpty()) {
581 placemark.addContent(
new Element(
"snippet", ns).addContent(coordinates));
602 Element placemark =
new Element(
"Placemark", ns);
603 Element desc =
new Element(
"description", ns);
604 if (name != null && !name.isEmpty()) {
605 placemark.addContent(
new Element(
"name", ns).addContent(name));
606 String image =
"<img src='" + name +
"' width='400'/>";
607 desc.addContent(image);
609 placemark.addContent(
new Element(
"styleUrl", ns).addContent(color.
getColor()));
611 String pathAsString = path.toString();
612 if (pathAsString != null && !pathAsString.isEmpty()) {
613 desc.addContent(description +
"<b>Source Path:</b> " + pathAsString);
616 placemark.addContent(desc);
618 if (timestamp != null) {
619 Element time =
new Element(
"TimeStamp", ns);
620 time.addContent(
new Element(
"when", ns).addContent(
getTimeStamp(timestamp)));
621 placemark.addContent(time);
623 placemark.addContent(feature);
624 if (coordinates != null && !coordinates.isEmpty()) {
625 placemark.addContent(
new Element(
"snippet", ns).addContent(coordinates));
640 private void copyFileUsingStream(AbstractFile inputFile, File outputFile)
throws ReadContentInputStreamException, IOException {
641 byte[] buffer =
new byte[65536];
643 outputFile.createNewFile();
644 try (InputStream is =
new ReadContentInputStream(inputFile);
645 OutputStream os =
new FileOutputStream(outputFile)) {
646 while ((length = is.read(buffer)) != -1) {
647 os.write(buffer, 0, length);
654 String name = NbBundle.getMessage(this.getClass(),
"ReportKML.getName.text");
660 return "ReportKML.kml";
665 String desc = NbBundle.getMessage(this.getClass(),
"ReportKML.getDesc.text");
686 String[] pathSegments = uniquePath.replaceFirst(
"^/*",
"").split(
"/");
689 if (pathSegments.length > 0) {
690 pathSegments[0] = pathSegments[0].replaceFirst(
"^img_",
"");
692 if (pathSegments.length > 1) {
693 pathSegments[1] = pathSegments[1].replaceFirst(
"^vol_",
"");
697 StringBuilder strbuf =
new StringBuilder();
698 for (String segment : pathSegments) {
699 if (!segment.isEmpty()) {
700 strbuf.append(
"/").append(segment);
703 return strbuf.toString();
715 StringBuilder result =
new StringBuilder();
716 result.append(String.format(
"<h3>%s</h3>", header))
720 if (timestamp != null) {
733 String value = prop.getValue();
734 if (value != null && !value.isEmpty()) {
739 return result.toString();
743 return String.format(HTML_PROP_FORMAT, title, value);
754 List<Waypoint> points = route.
getRoute();
755 StringBuilder result =
new StringBuilder();
757 result.append(String.format(
"<h3>%s</h3>", Bundle.Route_Details_Header()))
761 if (timestamp != null) {
765 if (points.size() > 1) {
773 if (altitude != null) {
781 if (altitude != null) {
788 String value = prop.getValue();
789 if (value != null && !value.isEmpty()) {
794 return result.toString();
806 if (latitude == null || longitude == null) {
810 return String.format(
"%.2f, %.2f", latitude, longitude);
static final String HTML_PROP_FORMAT
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()
String getFormattedDetails(Route route)
void complete(ReportStatus reportStatus)
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)
static String removeLeadingImgAndVol(String uniquePath)
Element gpsExifMetadataFolder
Document setupReportDocument()
static final String KML_STYLE_FILE
static final Logger logger
static synchronized KMLReport getDefault()
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)
Element gpsLastKnownLocationFolder
synchronized static Logger getLogger(String name)
static Case getCurrentCaseThrows()
void updateStatusLabel(String statusMessage)
Element gpsSearchesFolder
String getTimeStamp(long timeStamp)
final SimpleDateFormat kmlDateFormat
Element makePoint(Waypoint point)
String getFormattedDetails(Waypoint point, String header)
void generateReport(String baseReportDir, ReportProgressPanel progressPanel)
JPanel getConfigurationPanel()
static final String STYLESHEETS_PATH
String formattedCoordinates(Double latitude, Double longitude)