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 org.jdom2.Document;
39 import org.jdom2.Element;
40 import org.jdom2.Namespace;
41 import org.jdom2.output.Format;
42 import org.jdom2.output.XMLOutputter;
43 import org.jdom2.CDATA;
44 import org.openide.filesystems.FileUtil;
45 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");
87 RED(
"style.kml#redFeature"),
88 GREEN(
"style.kml#greenFeature"),
89 BLUE(
"style.kml#blueFeature"),
91 WHITE(
"style.kml#whiteFeature"),
110 if (instance == null) {
124 "KMLReport.unableToExtractPhotos=Could not extract photo information.",
125 "KMLReport.exifPhotoError=Could not extract photos with EXIF metadata.",
126 "KMLReport.bookmarkError=Could not extract Bookmark information.",
127 "KMLReport.gpsBookmarkError=Could not get GPS Bookmarks from database.",
128 "KMLReport.locationError=Could not extract Last Known Location information.",
129 "KMLReport.locationDatabaseError=Could not get GPS Last Known Location from database.",
130 "KMLReport.gpsRouteError=Could not extract GPS Route information.",
131 "KMLReport.gpsRouteDatabaseError=Could not get GPS Routes from database.",
132 "KMLReport.gpsSearchDatabaseError=Could not get GPS Searches from database.",
133 "KMLReport.trackpointError=Could not extract Trackpoint information.",
134 "KMLReport.trackpointDatabaseError=Could not get GPS Trackpoints from database.",
135 "KMLReport.stylesheetError=Error placing KML stylesheet. The .KML file will not function properly.",
136 "KMLReport.kmlFileWriteError=Could not write the KML file.",
138 "KMLReport.errorGeneratingReport=Error adding {0} to case as a report.",
139 "KMLReport.unableToOpenCase=Exception while getting open case.",
140 "Waypoint_Bookmark_Display_String=GPS Bookmark",
141 "Waypoint_Last_Known_Display_String=GPS Last Known Location",
142 "Waypoint_EXIF_Display_String=EXIF Metadata With Location",
143 "Waypoint_Route_Point_Display_String=GPS Individual Route Point",
144 "Waypoint_Search_Display_String=GPS Search",
145 "Waypoint_Trackpoint_Display_String=GPS Trackpoint",
146 "Waypoint_Track_Display_String=GPS Track",
147 "Route_Details_Header=GPS Route",
148 "ReportBodyFile.ingestWarning.text=Ingest Warning message",
149 "Waypoint_Track_Point_Display_String=GPS Individual Track Point"
162 logger.log(Level.SEVERE,
"Exception while getting open case.", ex);
167 progressPanel.
start();
168 progressPanel.
updateStatusLabel(NbBundle.getMessage(
this.getClass(),
"ReportKML.progress.querying"));
169 String kmlFileFullPath = baseReportDir +
REPORT_KML;
170 String errorMessage =
"";
174 progressPanel.
updateStatusLabel(NbBundle.getMessage(
this.getClass(),
"ReportKML.progress.loading"));
183 addLocationsToReport(skCase, baseReportDir);
185 errorMessage =
"Failed to complete report.";
186 logger.log(Level.SEVERE, errorMessage, ex);
192 InputStream input = getClass().getResourceAsStream(STYLESHEETS_PATH + KML_STYLE_FILE);
193 OutputStream output =
new FileOutputStream(baseReportDir + KML_STYLE_FILE);
194 FileUtil.copy(input, output);
195 }
catch (IOException ex) {
196 errorMessage = Bundle.KMLReport_stylesheetError();
197 logger.log(Level.SEVERE, errorMessage, ex);
201 try (FileOutputStream writer =
new FileOutputStream(kmlFileFullPath)) {
202 XMLOutputter outputter =
new XMLOutputter(Format.getPrettyFormat());
203 outputter.output(kmlDocument, writer);
204 String prependedStatus =
"";
206 prependedStatus =
"Incomplete ";
209 NbBundle.getMessage(
this.getClass(),
"ReportKML.genReport.srcModuleName.text"),
210 prependedStatus + NbBundle.getMessage(
this.getClass(),
"ReportKML.genReport.reportName"));
211 }
catch (IOException ex) {
212 errorMessage = Bundle.KMLReport_kmlFileWriteError();
213 logger.log(Level.SEVERE, errorMessage, ex);
215 }
catch (TskCoreException ex) {
216 errorMessage = Bundle.KMLReport_errorGeneratingReport(kmlFileFullPath);
217 logger.log(Level.SEVERE, errorMessage, ex);
220 errorMessage = Bundle.KMLReport_unableToOpenCase();
221 logger.log(Level.SEVERE, errorMessage, ex);
225 progressPanel.
complete(result, errorMessage);
234 ns = Namespace.getNamespace(
"",
"http://www.opengis.net/kml/2.2");
236 Element kml =
new Element(
"kml", ns);
237 kml.addNamespaceDeclaration(Namespace.getNamespace(
"gx",
"http://www.google.com/kml/ext/2.2"));
238 kml.addNamespaceDeclaration(Namespace.getNamespace(
"kml",
"http://www.opengis.net/kml/2.2"));
239 kml.addNamespaceDeclaration(Namespace.getNamespace(
"atom",
"http://www.w3.org/2005/Atom"));
240 Document kmlDocument =
new Document(kml);
242 Element document =
new Element(
"Document", ns);
243 kml.addContent(document);
245 Element name =
new Element(
"name", ns);
248 document.addContent(name);
252 Element ingestwarning =
new Element(
"snippet", ns);
253 ingestwarning.addContent(NbBundle.getMessage(
this.getClass(),
"ReportBodyFile.ingestWarning.text"));
254 document.addContent(ingestwarning);
258 gpsExifMetadataFolder =
new Element(
"Folder", ns);
259 CDATA cdataExifMetadataFolder =
new CDATA(
"https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/camera-icon-16.png");
260 Element hrefExifMetadata =
new Element(
"href", ns).addContent(cdataExifMetadataFolder);
261 gpsExifMetadataFolder.addContent(
new Element(
"Icon", ns).addContent(hrefExifMetadata));
263 gpsBookmarksFolder =
new Element(
"Folder", ns);
264 CDATA cdataBookmarks =
new CDATA(
"https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gpsfav.png");
265 Element hrefBookmarks =
new Element(
"href", ns).addContent(cdataBookmarks);
266 gpsBookmarksFolder.addContent(
new Element(
"Icon", ns).addContent(hrefBookmarks));
268 gpsLastKnownLocationFolder =
new Element(
"Folder", ns);
269 CDATA cdataLastKnownLocation =
new CDATA(
"https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-lastlocation.png");
270 Element hrefLastKnownLocation =
new Element(
"href", ns).addContent(cdataLastKnownLocation);
271 gpsLastKnownLocationFolder.addContent(
new Element(
"Icon", ns).addContent(hrefLastKnownLocation));
273 gpsRouteFolder =
new Element(
"Folder", ns);
274 CDATA cdataRoute =
new CDATA(
"https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-trackpoint.png");
275 Element hrefRoute =
new Element(
"href", ns).addContent(cdataRoute);
276 gpsRouteFolder.addContent(
new Element(
"Icon", ns).addContent(hrefRoute));
278 gpsSearchesFolder =
new Element(
"Folder", ns);
279 CDATA cdataSearches =
new CDATA(
"https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-search.png");
280 Element hrefSearches =
new Element(
"href", ns).addContent(cdataSearches);
281 gpsSearchesFolder.addContent(
new Element(
"Icon", ns).addContent(hrefSearches));
283 gpsTrackpointsFolder =
new Element(
"Folder", ns);
284 CDATA cdataTrackpoints =
new CDATA(
"https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-trackpoint.png");
285 Element hrefTrackpoints =
new Element(
"href", ns).addContent(cdataTrackpoints);
286 gpsTrackpointsFolder.addContent(
new Element(
"Icon", ns).addContent(hrefTrackpoints));
288 gpsTracksFolder =
new Element(
"Folder", ns);
289 CDATA cdataTrack =
new CDATA(
"https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-trackpoint.png");
290 Element hrefTrack =
new Element(
"href", ns).addContent(cdataTrack);
291 gpsTracksFolder.addContent(
new Element(
"Icon", ns).addContent(hrefTrack));
293 gpsExifMetadataFolder.addContent(
new Element(
"name", ns).addContent(
"EXIF Metadata"));
294 gpsBookmarksFolder.addContent(
new Element(
"name", ns).addContent(
"GPS Bookmarks"));
295 gpsLastKnownLocationFolder.addContent(
new Element(
"name", ns).addContent(
"GPS Last Known Location"));
296 gpsRouteFolder.addContent(
new Element(
"name", ns).addContent(
"GPS Routes"));
297 gpsSearchesFolder.addContent(
new Element(
"name", ns).addContent(
"GPS Searches"));
298 gpsTrackpointsFolder.addContent(
new Element(
"name", ns).addContent(
"GPS Trackpoints"));
299 gpsTracksFolder.addContent(
new Element(
"name", ns).addContent(
"GPS Tracks"));
301 document.addContent(gpsExifMetadataFolder);
302 document.addContent(gpsBookmarksFolder);
303 document.addContent(gpsLastKnownLocationFolder);
304 document.addContent(gpsRouteFolder);
305 document.addContent(gpsSearchesFolder);
306 document.addContent(gpsTrackpointsFolder);
307 document.addContent(gpsTracksFolder);
321 void addExifMetadataContent(List<Waypoint> points, String baseReportDirectory)
throws IOException {
324 if (mapPoint == null) {
328 AbstractFile abstractFile = point.getImage();
332 copyFileUsingStream(abstractFile, Paths.get(baseReportDirectory, abstractFile.getName()).toFile());
335 }
catch (TskCoreException ex) {
336 path = Paths.get(abstractFile.getParentPath(), abstractFile.getName());
339 path = Paths.get(abstractFile.getName());
355 void addLocationsToReport(SleuthkitCase skCase, String baseReportDir)
throws GeoLocationDataException, IOException {
356 if (waypointList == null) {
357 addExifMetadataContent(WaypointBuilder.getEXIFWaypoints(skCase), baseReportDir);
358 addWaypoints(WaypointBuilder.getBookmarkWaypoints(skCase),
gpsBookmarksFolder, FeatureColor.BLUE, Bundle.Waypoint_Bookmark_Display_String());
359 addWaypoints(WaypointBuilder.getLastKnownWaypoints(skCase),
gpsLastKnownLocationFolder, FeatureColor.PURPLE, Bundle.Waypoint_Last_Known_Display_String());
360 addWaypoints(WaypointBuilder.getSearchWaypoints(skCase),
gpsSearchesFolder, FeatureColor.WHITE, Bundle.Waypoint_Search_Display_String());
361 addWaypoints(WaypointBuilder.getTrackpointWaypoints(skCase),
gpsTrackpointsFolder, FeatureColor.WHITE, Bundle.Waypoint_Trackpoint_Display_String());
363 addExifMetadataContent(WaypointBuilder.getEXIFWaypoints(waypointList), baseReportDir);
364 addWaypoints(WaypointBuilder.getBookmarkWaypoints(waypointList),
gpsBookmarksFolder, FeatureColor.BLUE, Bundle.Waypoint_Bookmark_Display_String());
365 addWaypoints(WaypointBuilder.getLastKnownWaypoints(waypointList),
gpsLastKnownLocationFolder, FeatureColor.PURPLE, Bundle.Waypoint_Last_Known_Display_String());
366 addWaypoints(WaypointBuilder.getSearchWaypoints(waypointList),
gpsSearchesFolder, FeatureColor.WHITE, Bundle.Waypoint_Search_Display_String());
367 addWaypoints(WaypointBuilder.getTrackpointWaypoints(waypointList),
gpsTrackpointsFolder, FeatureColor.WHITE, Bundle.Waypoint_Trackpoint_Display_String());
379 void addWaypoints(List<Waypoint> points, Element folder, FeatureColor waypointColor, String headerLabel) {
380 for (Waypoint point : points) {
381 addContent(folder, point.getLabel(), waypointColor,
getFormattedDetails(point, headerLabel), point.getTimestamp(),
makePoint(point), point.getLatitude(), point.getLongitude());
397 void addContent(Element folder, String waypointLabel, FeatureColor waypointColor, String formattedDetails, Long timestamp, Element point, Double latitude, Double longitude) {
398 if (folder != null && point != null) {
400 folder.addContent(
makePlacemark(waypointLabel, waypointColor, formattedDetails, timestamp, point, formattedCords));
411 void makeRoutes(SleuthkitCase skCase)
throws GeoLocationDataException {
412 List<Route> routes = null;
414 if (waypointList == null) {
415 routes = Route.getRoutes(skCase);
417 routes = WaypointBuilder.getRoutes(waypointList);
420 for (Route route : routes) {
431 List<Waypoint> routePoints = route.
getRoute();
439 if (routePoints != null && routePoints.size() > 1) {
440 start = routePoints.get(0);
441 end = routePoints.get(1);
444 if (start == null || end == null) {
457 if (reportRoute != null) {
461 if (startingPoint != null) {
467 if (endingPoint != null) {
482 void makeTracks(SleuthkitCase skCase)
throws GeoLocationDataException {
483 List<Track> tracks = null;
485 if (waypointList == null) {
488 tracks = WaypointBuilder.getTracks(waypointList);
491 for (Track track : tracks) {
502 List<Waypoint> trackPoints = track.
getPath();
506 Element trackFolder =
new Element(
"Folder", ns);
507 trackFolder.addContent(
new Element(
"name", ns).addContent(track.
getLabel()));
508 gpsTracksFolder.addContent(trackFolder);
510 for (
Waypoint point : trackPoints) {
511 Element element =
makePoint(point.getLatitude(), point.getLongitude(), point.getAltitude());
514 point.getTimestamp(), element,
formattedCoordinates(point.getLatitude(), point.getLongitude())));
526 return kmlDateFormat.format(
new java.util.Date(timeStamp * 1000));
551 private Element
makePoint(Double latitude, Double longitude, Double altitude) {
552 if (latitude == null || longitude == null) {
556 Element point =
new Element(
"Point", ns);
559 Element coordinates =
new Element(
"coordinates", ns).addContent(longitude +
"," + latitude +
"," + (altitude != null ? altitude : 0.0));
561 if (altitude != null && altitude != 0) {
569 Element altitudeMode =
new Element(
"altitudeMode", ns).addContent(
"clampToGround");
570 point.addContent(altitudeMode);
572 point.addContent(coordinates);
593 private Element
makeLineString(Double startLatitude, Double startLongitude, Double stopLatitude, Double stopLongitude) {
594 if (startLatitude == null || startLongitude == null || stopLatitude == null || stopLongitude == null) {
598 Element lineString =
new Element(
"LineString", ns);
599 lineString.addContent(
new Element(
"extrude", ns).addContent(
"1"));
600 lineString.addContent(
new Element(
"tessellate", ns).addContent(
"1"));
601 lineString.addContent(
new Element(
"altitudeMode", ns).addContent(
"clampToGround"));
603 lineString.addContent(
new Element(
"coordinates", ns).addContent(
604 startLongitude +
"," + startLatitude +
",0.0,"
605 + stopLongitude +
"," + stopLatitude +
",0.0"));
623 private Element
makePlacemark(String name,
FeatureColor color, String description, Long timestamp, Element feature, String coordinates) {
624 Element placemark =
new Element(
"Placemark", ns);
625 if (name != null && !name.isEmpty()) {
626 placemark.addContent(
new Element(
"name", ns).addContent(name));
627 }
else if (timestamp != null) {
628 placemark.addContent(
new Element(
"name", ns).addContent(
getTimeStamp(timestamp)));
630 placemark.addContent(
new Element(
"name", ns).addContent(
""));
632 placemark.addContent(
new Element(
"styleUrl", ns).addContent(color.
getColor()));
633 placemark.addContent(
new Element(
"description", ns).addContent(description));
634 if (timestamp != null) {
635 Element time =
new Element(
"TimeStamp", ns);
636 time.addContent(
new Element(
"when", ns).addContent(
getTimeStamp(timestamp)));
637 placemark.addContent(time);
639 placemark.addContent(feature);
640 if (coordinates != null && !coordinates.isEmpty()) {
641 placemark.addContent(
new Element(
"snippet", ns).addContent(coordinates));
662 Element placemark =
new Element(
"Placemark", ns);
663 Element desc =
new Element(
"description", ns);
664 if (name != null && !name.isEmpty()) {
665 placemark.addContent(
new Element(
"name", ns).addContent(name));
666 String image =
"<img src='" + name +
"' width='400'/>";
667 desc.addContent(image);
669 placemark.addContent(
new Element(
"styleUrl", ns).addContent(color.
getColor()));
671 String pathAsString = path.toString();
672 if (pathAsString != null && !pathAsString.isEmpty()) {
673 desc.addContent(description +
"<b>Source Path:</b> " + pathAsString);
676 placemark.addContent(desc);
678 if (timestamp != null) {
679 Element time =
new Element(
"TimeStamp", ns);
680 time.addContent(
new Element(
"when", ns).addContent(
getTimeStamp(timestamp)));
681 placemark.addContent(time);
683 placemark.addContent(feature);
684 if (coordinates != null && !coordinates.isEmpty()) {
685 placemark.addContent(
new Element(
"snippet", ns).addContent(coordinates));
700 private void copyFileUsingStream(AbstractFile inputFile, File outputFile)
throws ReadContentInputStreamException, IOException {
701 byte[] buffer =
new byte[65536];
703 outputFile.createNewFile();
704 try (InputStream is =
new ReadContentInputStream(inputFile);
705 OutputStream os =
new FileOutputStream(outputFile)) {
706 while ((length = is.read(buffer)) != -1) {
707 os.write(buffer, 0, length);
714 String name = NbBundle.getMessage(this.getClass(),
"ReportKML.getName.text");
720 return "ReportKML.kml";
725 String desc = NbBundle.getMessage(this.getClass(),
"ReportKML.getDesc.text");
746 String[] pathSegments = uniquePath.replaceFirst(
"^/*",
"").split(
"/");
749 if (pathSegments.length > 0) {
750 pathSegments[0] = pathSegments[0].replaceFirst(
"^img_",
"");
752 if (pathSegments.length > 1) {
753 pathSegments[1] = pathSegments[1].replaceFirst(
"^vol_",
"");
757 StringBuilder strbuf =
new StringBuilder();
758 for (String segment : pathSegments) {
759 if (!segment.isEmpty()) {
760 strbuf.append(
"/").append(segment);
763 return strbuf.toString();
775 StringBuilder result =
new StringBuilder();
776 result.append(String.format(
"<h3>%s</h3>", header))
780 if (timestamp != null) {
793 String value = prop.getValue();
794 if (value != null && !value.isEmpty()) {
799 return result.toString();
811 return String.format(HTML_PROP_FORMAT, title, value);
822 List<Waypoint> points = route.
getRoute();
823 StringBuilder result =
new StringBuilder();
825 result.append(String.format(
"<h3>%s</h3>", Bundle.Route_Details_Header()))
829 if (timestamp != null) {
833 if (points.size() > 1) {
841 if (altitude != null) {
849 if (altitude != null) {
856 String value = prop.getValue();
857 if (value != null && !value.isEmpty()) {
862 return result.toString();
874 if (latitude == null || longitude == null) {
878 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)
static List< Track > getTracks(SleuthkitCase skCase, List<?extends Content > sourceList)
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)
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()
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)
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)