20package org.sleuthkit.autopsy.report.modules.kml;
22import org.sleuthkit.autopsy.report.GeneralReportModule;
23import javax.swing.JPanel;
24import org.openide.util.NbBundle;
25import org.sleuthkit.autopsy.casemodule.Case;
26import org.sleuthkit.autopsy.coreutils.Logger;
27import org.sleuthkit.autopsy.ingest.IngestManager;
29import java.io.FileOutputStream;
30import java.io.IOException;
31import java.io.InputStream;
32import java.io.OutputStream;
33import java.nio.file.Path;
34import java.nio.file.Paths;
35import java.text.SimpleDateFormat;
37import java.util.logging.Level;
38import java.util.stream.Collectors;
39import org.jdom2.Document;
40import org.jdom2.Element;
41import org.jdom2.Namespace;
42import org.jdom2.output.Format;
43import org.jdom2.output.XMLOutputter;
44import org.jdom2.CDATA;
45import org.openide.filesystems.FileUtil;
46import org.openide.util.NbBundle.Messages;
47import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
48import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationDataException;
49import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationParseResult;
50import org.sleuthkit.autopsy.geolocation.datamodel.Waypoint;
51import org.sleuthkit.autopsy.geolocation.datamodel.Route;
52import org.sleuthkit.autopsy.geolocation.datamodel.Track;
53import org.sleuthkit.autopsy.geolocation.datamodel.Area;
54import org.sleuthkit.autopsy.geolocation.datamodel.WaypointBuilder;
55import org.sleuthkit.autopsy.report.GeneralReportSettings;
56import org.sleuthkit.autopsy.report.ReportBranding;
57import org.sleuthkit.autopsy.report.ReportProgressPanel;
58import org.sleuthkit.datamodel.AbstractFile;
59import org.sleuthkit.datamodel.Content;
60import org.sleuthkit.datamodel.ReadContentInputStream;
61import org.sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException;
62import org.sleuthkit.datamodel.SleuthkitCase;
63import org.sleuthkit.datamodel.TskCoreException;
72 private static final String
REPORT_KML =
"ReportKML.kml";
73 private static final String
STYLESHEETS_PATH =
"/org/sleuthkit/autopsy/report/stylesheets/";
77 private final SimpleDateFormat
kmlDateFormat =
new SimpleDateFormat(
"yyyy-MM-dd'T'HH:mm:ssX");
95 RED(
"style.kml#redFeature"),
96 GREEN(
"style.kml#greenFeature"),
97 BLUE(
"style.kml#blueFeature"),
99 WHITE(
"style.kml#whiteFeature"),
132 "KMLReport.failedToCompleteReport=Failed to complete report.",
133 "KMLReport.partialFailure=There was an error creating the report. Some items were not exported.",
134 "KMLReport.unableToExtractPhotos=Could not extract photo information.",
135 "KMLReport.exifPhotoError=Could not extract photos with EXIF metadata.",
136 "KMLReport.bookmarkError=Could not extract Bookmark information.",
137 "KMLReport.gpsBookmarkError=Could not get GPS Bookmarks from database.",
138 "KMLReport.locationError=Could not extract Last Known Location information.",
139 "KMLReport.locationDatabaseError=Could not get GPS Last Known Location from database.",
140 "KMLReport.gpsRouteError=Could not extract GPS Route information.",
141 "KMLReport.gpsRouteDatabaseError=Could not get GPS Routes from database.",
142 "KMLReport.gpsSearchDatabaseError=Could not get GPS Searches from database.",
143 "KMLReport.trackpointError=Could not extract Trackpoint information.",
144 "KMLReport.trackpointDatabaseError=Could not get GPS Trackpoints from database.",
145 "KMLReport.stylesheetError=Error placing KML stylesheet. The .KML file will not function properly.",
146 "KMLReport.kmlFileWriteError=Could not write the KML file.",
148 "KMLReport.errorGeneratingReport=Error adding {0} to case as a report.",
149 "KMLReport.unableToOpenCase=Exception while getting open case.",
150 "Waypoint_Bookmark_Display_String=GPS Bookmark",
151 "Waypoint_Last_Known_Display_String=GPS Last Known Location",
152 "Waypoint_EXIF_Display_String=EXIF Metadata With Location",
153 "Waypoint_Route_Point_Display_String=GPS Individual Route Point",
154 "Waypoint_Search_Display_String=GPS Search",
155 "Waypoint_Trackpoint_Display_String=GPS Trackpoint",
156 "Waypoint_Track_Display_String=GPS Track",
157 "Route_Details_Header=GPS Route",
158 "ReportBodyFile.ingestWarning.text=Ingest Warning message",
159 "Waypoint_Track_Point_Display_String=GPS Individual Track Point",
160 "Waypoint_Area_Point_Display_String=GPS Area Outline Point",
180 logger.log(Level.SEVERE,
"Exception while getting open case.", ex);
184 if(
settings.getSelectedDataSources() ==
null) {
187 List<Long> selectedDataSources =
currentCase.getDataSources()
190 .collect(Collectors.toList());
191 settings.setSelectedDataSources(selectedDataSources);
192 }
catch (TskCoreException ex) {
193 logger.log(Level.SEVERE,
"Could not get the datasources from the case", ex);
198 String baseReportDir =
settings.getReportDirectoryPath();
202 progressPanel.
start();
203 progressPanel.
updateStatusLabel(NbBundle.getMessage(
this.getClass(),
"ReportKML.progress.querying"));
204 String kmlFileFullPath = baseReportDir +
REPORT_KML;
205 String errorMessage =
"";
209 progressPanel.
updateStatusLabel(NbBundle.getMessage(
this.getClass(),
"ReportKML.progress.loading"));
217 boolean entirelySuccessful = makeTracks(
skCase);
218 if (!entirelySuccessful) {
220 errorMessage = Bundle.KMLReport_partialFailure();
222 entirelySuccessful = makeAreas(
skCase);
223 if (!entirelySuccessful) {
225 errorMessage = Bundle.KMLReport_partialFailure();
228 addLocationsToReport(
skCase, baseReportDir);
230 errorMessage = Bundle.KMLReport_failedToCompleteReport();
231 logger.log(Level.SEVERE, errorMessage, ex);
238 OutputStream output =
new FileOutputStream(baseReportDir +
KML_STYLE_FILE);
239 FileUtil.copy(input, output);
240 }
catch (IOException ex) {
241 errorMessage = Bundle.KMLReport_stylesheetError();
242 logger.log(Level.SEVERE, errorMessage, ex);
246 try (FileOutputStream writer =
new FileOutputStream(kmlFileFullPath)) {
247 XMLOutputter outputter =
new XMLOutputter(Format.getPrettyFormat());
248 outputter.output(kmlDocument, writer);
249 String prependedStatus =
"";
251 prependedStatus =
"Incomplete ";
254 NbBundle.getMessage(
this.getClass(),
"ReportKML.genReport.srcModuleName.text"),
255 prependedStatus + NbBundle.getMessage(
this.getClass(),
"ReportKML.genReport.reportName"));
256 }
catch (IOException ex) {
257 errorMessage = Bundle.KMLReport_kmlFileWriteError();
258 logger.log(Level.SEVERE, errorMessage, ex);
260 }
catch (TskCoreException ex) {
261 errorMessage = Bundle.KMLReport_errorGeneratingReport(kmlFileFullPath);
262 logger.log(Level.SEVERE, errorMessage, ex);
265 errorMessage = Bundle.KMLReport_unableToOpenCase();
266 logger.log(Level.SEVERE, errorMessage, ex);
270 progressPanel.
complete(result, errorMessage);
279 ns = Namespace.getNamespace(
"",
"http://www.opengis.net/kml/2.2");
281 Element
kml =
new Element(
"kml",
ns);
282 kml.addNamespaceDeclaration(Namespace.getNamespace(
"gx",
"http://www.google.com/kml/ext/2.2"));
283 kml.addNamespaceDeclaration(Namespace.getNamespace(
"kml",
"http://www.opengis.net/kml/2.2"));
284 kml.addNamespaceDeclaration(Namespace.getNamespace(
"atom",
"http://www.w3.org/2005/Atom"));
285 Document kmlDocument =
new Document(
kml);
287 Element document =
new Element(
"Document",
ns);
288 kml.addContent(document);
290 Element name =
new Element(
"name",
ns);
293 document.addContent(name);
297 Element ingestwarning =
new Element(
"snippet",
ns);
298 ingestwarning.addContent(NbBundle.getMessage(
this.getClass(),
"ReportBodyFile.ingestWarning.text"));
299 document.addContent(ingestwarning);
304 CDATA cdataExifMetadataFolder =
new CDATA(
"https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/camera-icon-16.png");
305 Element hrefExifMetadata =
new Element(
"href",
ns).addContent(cdataExifMetadataFolder);
309 CDATA cdataBookmarks =
new CDATA(
"https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gpsfav.png");
310 Element hrefBookmarks =
new Element(
"href",
ns).addContent(cdataBookmarks);
314 CDATA cdataLastKnownLocation =
new CDATA(
"https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-lastlocation.png");
315 Element hrefLastKnownLocation =
new Element(
"href",
ns).addContent(cdataLastKnownLocation);
319 CDATA cdataRoute =
new CDATA(
"https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-trackpoint.png");
320 Element hrefRoute =
new Element(
"href",
ns).addContent(cdataRoute);
324 CDATA cdataSearches =
new CDATA(
"https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-search.png");
325 Element hrefSearches =
new Element(
"href",
ns).addContent(cdataSearches);
329 CDATA cdataTrackpoints =
new CDATA(
"https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-trackpoint.png");
330 Element hrefTrackpoints =
new Element(
"href",
ns).addContent(cdataTrackpoints);
334 CDATA cdataTrack =
new CDATA(
"https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-trackpoint.png");
335 Element hrefTrack =
new Element(
"href",
ns).addContent(cdataTrack);
339 CDATA cdataArea =
new CDATA(
"https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-area.png");
340 Element hrefArea =
new Element(
"href",
ns).addContent(cdataArea);
346 gpsRouteFolder.addContent(
new Element(
"name",
ns).addContent(
"GPS Routes"));
373 void addExifMetadataContent(List<Waypoint> points, String baseReportDirectory)
throws IOException, TskCoreException {
380 if (mapPoint ==
null) {
384 AbstractFile abstractFile = point.getImage();
388 copyFileUsingStream(abstractFile, Paths.get(baseReportDirectory, abstractFile.getName()).toFile());
391 }
catch (TskCoreException ex) {
392 path = Paths.get(abstractFile.getParentPath(), abstractFile.getName());
395 path = Paths.get(abstractFile.getName());
411 void addLocationsToReport(SleuthkitCase
skCase, String baseReportDir)
throws GeoLocationDataException, IOException, TskCoreException {
413 addExifMetadataContent(WaypointBuilder.getEXIFWaypoints(
skCase), baseReportDir);
419 addExifMetadataContent(WaypointBuilder.getEXIFWaypoints(
waypointList), baseReportDir);
435 void addWaypoints(List<Waypoint> points, Element folder,
FeatureColor waypointColor, String headerLabel)
throws TskCoreException {
436 for (Waypoint point : points) {
440 addContent(folder, point.getLabel(), waypointColor,
getFormattedDetails(point, headerLabel), point.getTimestamp(),
makePoint(point), point.getLatitude(), point.getLongitude());
456 void addContent(Element folder, String waypointLabel,
FeatureColor waypointColor, String formattedDetails, Long timestamp, Element point, Double latitude, Double longitude) {
457 if (folder !=
null && point !=
null) {
459 folder.addContent(
makePlacemark(waypointLabel, waypointColor, formattedDetails, timestamp, point, formattedCords));
470 void makeRoutes(SleuthkitCase
skCase)
throws GeoLocationDataException, TskCoreException {
471 List<Route> routes =
null;
474 routes = Route.getRoutes(
skCase);
479 for (Route route : routes) {
493 List<Waypoint> routePoints = route.
getRoute();
501 if (routePoints !=
null && routePoints.size() > 1) {
502 start = routePoints.get(0);
503 end = routePoints.get(1);
506 if (start ==
null || end ==
null) {
519 if (reportRoute !=
null) {
523 if (startingPoint !=
null) {
529 if (endingPoint !=
null) {
546 List<Track> tracks =
null;
547 boolean successful =
true;
557 for (Track track : tracks) {
573 List<Waypoint> trackPoints = track.
getPath();
577 Element trackFolder =
new Element(
"Folder",
ns);
578 trackFolder.addContent(
new Element(
"name",
ns).addContent(track.
getLabel()));
581 for (
Waypoint point : trackPoints) {
582 Element element =
makePoint(point.getLatitude(), point.getLongitude(), point.getAltitude());
585 point.getTimestamp(), element,
formattedCoordinates(point.getLatitude(), point.getLongitude())));
599 boolean successful =
true;
609 for (Area area : areas) {
625 List<Waypoint> areaPoints = area.
getPath();
627 if (areaPoints.isEmpty()) {
633 Element areaFolder =
new Element(
"Folder",
ns);
634 areaFolder.addContent(
new Element(
"name",
ns).addContent(area.
getLabel()));
639 Waypoint firstWp = areaPoints.get(0);
653 return kmlDateFormat.format(
new java.util.Date(timeStamp * 1000));
678 private Element
makePoint(Double latitude, Double longitude, Double altitude) {
679 if (latitude ==
null || longitude ==
null) {
683 Element point =
new Element(
"Point",
ns);
686 Element coordinates =
new Element(
"coordinates",
ns).addContent(longitude +
"," + latitude +
"," + (altitude !=
null ? altitude : 0.0));
688 if (altitude !=
null && altitude != 0) {
696 Element altitudeMode =
new Element(
"altitudeMode",
ns).addContent(
"clampToGround");
697 point.addContent(altitudeMode);
699 point.addContent(coordinates);
720 private Element
makeLineString(Double startLatitude, Double startLongitude, Double stopLatitude, Double stopLongitude) {
721 if (startLatitude ==
null || startLongitude ==
null || stopLatitude ==
null || stopLongitude ==
null) {
725 Element lineString =
new Element(
"LineString",
ns);
726 lineString.addContent(
new Element(
"extrude",
ns).addContent(
"1"));
727 lineString.addContent(
new Element(
"tessellate",
ns).addContent(
"1"));
728 lineString.addContent(
new Element(
"altitudeMode",
ns).addContent(
"clampToGround"));
730 lineString.addContent(
new Element(
"coordinates",
ns).addContent(
731 startLongitude +
"," + startLatitude +
",0.0,"
732 + stopLongitude +
"," + stopLatitude +
",0.0"));
745 Element polygon =
new Element(
"Polygon",
ns);
747 Element altitudeMode =
new Element(
"altitudeMode",
ns).addContent(
"clampToGround");
748 polygon.addContent(altitudeMode);
751 Element coordinates =
new Element(
"coordinates",
ns);
753 coordinates.addContent(wp.getLongitude() +
"," + wp.getLatitude() +
",0 ");
756 coordinates.addContent(waypoints.get(0).getLongitude() +
"," + waypoints.get(0).getLatitude() +
",0 ");
758 Element linearRing =
new Element(
"LinearRing",
ns).addContent(coordinates);
759 Element outerBoundary =
new Element(
"outerBoundaryIs",
ns).addContent(linearRing);
760 polygon.addContent(outerBoundary);
779 private Element
makePlacemark(String name,
FeatureColor color, String description, Long timestamp, Element feature, String coordinates) {
780 Element placemark =
new Element(
"Placemark",
ns);
781 if (name !=
null && !name.isEmpty()) {
782 placemark.addContent(
new Element(
"name",
ns).addContent(name));
783 }
else if (timestamp !=
null) {
784 placemark.addContent(
new Element(
"name",
ns).addContent(
getTimeStamp(timestamp)));
786 placemark.addContent(
new Element(
"name",
ns).addContent(
""));
788 placemark.addContent(
new Element(
"styleUrl",
ns).addContent(color.
getColor()));
789 placemark.addContent(
new Element(
"description",
ns).addContent(description));
790 if (timestamp !=
null) {
791 Element time =
new Element(
"TimeStamp",
ns);
792 time.addContent(
new Element(
"when",
ns).addContent(
getTimeStamp(timestamp)));
793 placemark.addContent(time);
795 placemark.addContent(feature);
796 if (coordinates !=
null && !coordinates.isEmpty()) {
797 placemark.addContent(
new Element(
"snippet",
ns).addContent(coordinates));
818 Element placemark =
new Element(
"Placemark",
ns);
819 Element desc =
new Element(
"description",
ns);
820 if (name !=
null && !name.isEmpty()) {
821 placemark.addContent(
new Element(
"name",
ns).addContent(name));
822 String image =
"<img src='" + name +
"' width='400'/>";
823 desc.addContent(image);
825 placemark.addContent(
new Element(
"styleUrl",
ns).addContent(color.
getColor()));
827 String pathAsString = path.toString();
828 if (pathAsString !=
null && !pathAsString.isEmpty()) {
829 desc.addContent(description +
"<b>Source Path:</b> " + pathAsString);
832 placemark.addContent(desc);
834 if (timestamp !=
null) {
835 Element time =
new Element(
"TimeStamp",
ns);
836 time.addContent(
new Element(
"when",
ns).addContent(
getTimeStamp(timestamp)));
837 placemark.addContent(time);
839 placemark.addContent(feature);
840 if (coordinates !=
null && !coordinates.isEmpty()) {
841 placemark.addContent(
new Element(
"snippet",
ns).addContent(coordinates));
856 private void copyFileUsingStream(AbstractFile inputFile, File outputFile)
throws ReadContentInputStreamException, IOException {
857 byte[] buffer =
new byte[65536];
859 outputFile.createNewFile();
860 try (InputStream is =
new ReadContentInputStream(inputFile);
861 OutputStream os =
new FileOutputStream(outputFile)) {
862 while ((length = is.read(buffer)) != -1) {
863 os.write(buffer, 0, length);
870 String name = NbBundle.getMessage(this.getClass(),
"ReportKML.getName.text");
876 return "ReportKML.kml";
881 String desc = NbBundle.getMessage(this.getClass(),
"ReportKML.getDesc.text");
902 String[] pathSegments = uniquePath.replaceFirst(
"^/*",
"").split(
"/");
905 if (pathSegments.length > 0) {
906 pathSegments[0] = pathSegments[0].replaceFirst(
"^img_",
"");
908 if (pathSegments.length > 1) {
909 pathSegments[1] = pathSegments[1].replaceFirst(
"^vol_",
"");
913 StringBuilder strbuf =
new StringBuilder();
914 for (String segment : pathSegments) {
915 if (!segment.isEmpty()) {
916 strbuf.append(
"/").append(segment);
919 return strbuf.toString();
931 StringBuilder result =
new StringBuilder();
932 result.append(String.format(
"<h3>%s</h3>", header))
936 if (timestamp !=
null) {
949 String value = prop.getValue();
950 if (value !=
null && !value.isEmpty()) {
955 return result.toString();
978 List<Waypoint> points = route.
getRoute();
979 StringBuilder result =
new StringBuilder();
981 result.append(String.format(
"<h3>%s</h3>", Bundle.Route_Details_Header()))
985 if (timestamp !=
null) {
989 if (points.size() > 1) {
997 if (altitude !=
null) {
1005 if (altitude !=
null) {
1012 String value = prop.getValue();
1013 if (value !=
null && !value.isEmpty()) {
1018 return result.toString();
1030 if (latitude ==
null || longitude ==
null) {
1034 return String.format(
"%.2f, %.2f", latitude, longitude);
1044 long dataSourceId = content.getDataSource().getId();
static Case getCurrentCaseThrows()
void addReport(String localPath, String srcModuleName, String reportName)
synchronized static Logger getLogger(String name)
boolean isSuccessfullyParsed()
final List< Waypoint > getPath()
static GeoLocationParseResult< Track > getTracks(SleuthkitCase skCase, List<? extends Content > sourceList)
static GeoLocationParseResult< Area > getAreas(SleuthkitCase skCase, List<? extends Content > sourceList)
List< Waypoint > getRoute()
List< Waypoint.Property > getOtherProperties()
List< Waypoint.Property > getOtherProperties()
static synchronized IngestManager getInstance()
boolean isIngestRunning()
void setReportDirectoryPath(String reportDirectoryPath)
List< Long > getSelectedDataSources()
void updateStatusLabel(String statusMessage)
void complete(ReportStatus reportStatus)
void setIndeterminate(boolean indeterminate)
void generateReport(String baseReportDir, ReportProgressPanel progressPanel, List< Waypoint > waypointList)
boolean supportsDataSourceSelection()
Element makePolygon(List< Waypoint > waypoints)
Document setupReportDocument()
void generateReport(GeneralReportSettings settings, ReportProgressPanel progressPanel)
static KMLReport instance
Element makePlacemark(String name, FeatureColor color, String description, Long timestamp, Element feature, String coordinates)
Element makePlacemarkWithPicture(String name, FeatureColor color, String description, Long timestamp, Element feature, Path path, String coordinates)
Element makeLineString(Double startLatitude, Double startLongitude, Double stopLatitude, Double stopLongitude)
Element gpsTrackpointsFolder
String formattedCoordinates(Double latitude, Double longitude)
boolean shouldFilterFromReport(Content content)
static final String KML_STYLE_FILE
String getFormattedDetails(Waypoint point, String header)
static final Logger logger
static final String STYLESHEETS_PATH
Element makePoint(Waypoint point)
Element gpsLastKnownLocationFolder
List< Waypoint > waypointList
static final String REPORT_KML
static String removeLeadingImgAndVol(String uniquePath)
void addAreaToReport(Area area)
String formatAttribute(String title, String value)
static final String HTML_PROP_FORMAT
static synchronized KMLReport getDefault()
Element gpsBookmarksFolder
void addRouteToReport(Route route)
Element makePoint(Double latitude, Double longitude, Double altitude)
void copyFileUsingStream(AbstractFile inputFile, File outputFile)
void addTrackToReport(Track track)
JPanel getConfigurationPanel()
String getFormattedDetails(Route route)
String getRelativeFilePath()
Element gpsSearchesFolder
String getTimeStamp(long timeStamp)
final SimpleDateFormat kmlDateFormat
Element gpsExifMetadataFolder
GeneralReportSettings settings
FeatureColor(String color)