/* * Autopsy Forensic Browser * * Copyright 2016 Basis Technology Corp. * Contact: carrier <at> sleuthkit <dot> org * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.sleuthkit.autopsy.timeline.snapshot; import com.github.mustachejava.DefaultMustacheFactory; import com.github.mustachejava.Mustache; import com.github.mustachejava.MustacheFactory; import java.awt.image.BufferedImage; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Writer; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import javax.imageio.ImageIO; import org.apache.commons.lang3.StringUtils; import org.joda.time.format.DateTimeFormat; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.report.ReportBranding; import org.sleuthkit.autopsy.timeline.zooming.ZoomParams; /** * Generate and write the Timeline snapshot report to disk. */ public class SnapShotReportWriter { /** * mustache.java template factory. */ private final static MustacheFactory mf = new DefaultMustacheFactory(); private final Case currentCase; private final Path reportFolderPath; private final String reportName; private final ReportBranding reportBranding; private final ZoomParams zoomParams; private final Date generationDate; private final BufferedImage image; /** * Constructor * * @param currentCase The Case to write a report for. * @param reportFolderPath The Path to the folder that will contain the * report. * @param reportName The name of the report. * @param zoomParams The ZoomParams in effect when the snapshot was * taken. * @param generationDate The generation Date of the report. * @param snapshot A snapshot of the view to include in the * report. */ public SnapShotReportWriter(Case currentCase, Path reportFolderPath, String reportName, ZoomParams zoomParams, Date generationDate, BufferedImage snapshot) { this.currentCase = currentCase; this.reportFolderPath = reportFolderPath; this.reportName = reportName; this.zoomParams = zoomParams; this.generationDate = generationDate; this.image = snapshot; this.reportBranding = new ReportBranding(); } /** * Generate and write the report to disk. * * @return The Path to the "main file" of the report. This is the file that * Autopsy shows in the results view when the Reports Node is * selected in the DirectoryTree. * * @throws IOException If there is a problem writing the report. */ public Path writeReport() throws IOException { //ensure directory exists Files.createDirectories(reportFolderPath); //save the snapshot in the report directory ImageIO.write(image, "png", reportFolderPath.resolve("snapshot.png").toFile()); //NON-NLS copyResources(); writeSummaryHTML(); writeSnapShotHTMLFile(); return writeIndexHTML(); } /** * Generate and write the html page that shows the snapshot and the state of * the ZoomParams * * @throws IOException If there is a problem writing the html file to disk. */ private void writeSnapShotHTMLFile() throws IOException { //make a map of context objects to resolve template paramaters against HashMap<String, Object> snapShotContext = new HashMap<>(); snapShotContext.put("reportTitle", reportName); //NON-NLS snapShotContext.put("startTime", zoomParams.getTimeRange().getStart().toString(DateTimeFormat.fullDateTime())); //NON-NLS snapShotContext.put("endTime", zoomParams.getTimeRange().getEnd().toString(DateTimeFormat.fullDateTime())); //NON-NLS snapShotContext.put("zoomParams", zoomParams); //NON-NLS fillTemplateAndWrite("/org/sleuthkit/autopsy/timeline/snapshot/snapshot_template.html", "Snapshot", snapShotContext, reportFolderPath.resolve("snapshot.html")); //NON-NLS } /** * Generate and write the main html page with frames for navigation on the * left and content on the right. * * @return The Path of the written html file. * * @throws IOException If there is a problem writing the html file to disk. */ private Path writeIndexHTML() throws IOException { //make a map of context objects to resolve template paramaters against HashMap<String, Object> indexContext = new HashMap<>(); indexContext.put("reportBranding", reportBranding); //NON-NLS indexContext.put("reportName", reportName); //NON-NLS Path reportIndexFile = reportFolderPath.resolve("index.html"); //NON-NLS fillTemplateAndWrite("/org/sleuthkit/autopsy/timeline/snapshot/index_template.html", "Index", indexContext, reportIndexFile); //NON-NLS return reportIndexFile; } /** * * Generate and write the summary of the current case for this report. * * @throws IOException If there is a problem writing the html file to disk. */ private void writeSummaryHTML() throws IOException { //make a map of context objects to resolve template paramaters against HashMap<String, Object> summaryContext = new HashMap<>(); summaryContext.put("reportName", reportName); //NON-NLS summaryContext.put("reportBranding", reportBranding); //NON-NLS summaryContext.put("generationDateTime", new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(generationDate)); //NON-NLS summaryContext.put("ingestRunning", IngestManager.getInstance().isIngestRunning()); //NON-NLS summaryContext.put("currentCase", currentCase); //NON-NLS fillTemplateAndWrite("/org/sleuthkit/autopsy/timeline/snapshot/summary_template.html", "Summary", summaryContext, reportFolderPath.resolve("summary.html")); //NON-NLS } /** * Fill in the mustache template at the given location using the values from * the given context object and save it to the given outPutFile. * * @param templateLocation The location of the template. suitible for use * with Class.getResourceAsStream * @param templateName The name of the tempalte. (Used by mustache to * cache templates?) * @param context The contect to use to fill in the template * values. * @param outPutFile The filled in tempalte will be saced at this * Path. * * @throws IOException If there is a problem saving the filled in template * to disk. */ private void fillTemplateAndWrite(final String templateLocation, final String templateName, Object context, final Path outPutFile) throws IOException { Mustache summaryMustache = mf.compile(new InputStreamReader(SnapShotReportWriter.class.getResourceAsStream(templateLocation)), templateName); try (Writer writer = Files.newBufferedWriter(outPutFile, Charset.forName("UTF-8"))) { //NON-NLS summaryMustache.execute(writer, context); } } /** * Copy static resources (static html, css, images, etc) to the reports * folder. * * @throws IOException If there is a problem copying the resources. */ private void copyResources() throws IOException { //pull generator and agency logos from branding String generatorLogoPath = reportBranding.getGeneratorLogoPath(); if (StringUtils.isNotBlank(generatorLogoPath)) { Files.copy(Files.newInputStream(Paths.get(generatorLogoPath)), reportFolderPath.resolve("generator_logo.png")); //NON-NLS } String agencyLogoPath = reportBranding.getAgencyLogoPath(); if (StringUtils.isNotBlank(agencyLogoPath)) { Files.copy(Files.newInputStream(Paths.get(agencyLogoPath)), reportFolderPath.resolve("agency_logo.png")); //NON-NLS } //copy navigation html try (InputStream navStream = SnapShotReportWriter.class.getResourceAsStream("/org/sleuthkit/autopsy/timeline/snapshot/navigation.html")) { //NON-NLS Files.copy(navStream, reportFolderPath.resolve("nav.html")); //NON-NLS } //copy favicon if (StringUtils.isBlank(agencyLogoPath)) { // use default Autopsy icon if custom icon is not set try (InputStream faviconStream = SnapShotReportWriter.class.getResourceAsStream("/org/sleuthkit/autopsy/report/images/favicon.ico")) { //NON-NLS Files.copy(faviconStream, reportFolderPath.resolve("favicon.ico")); //NON-NLS } } else { Files.copy(Files.newInputStream(Paths.get(agencyLogoPath)), reportFolderPath.resolve("favicon.ico")); //NON-NLS } //copy report summary icon try (InputStream summaryStream = SnapShotReportWriter.class.getResourceAsStream("/org/sleuthkit/autopsy/report/images/summary.png")) { //NON-NLS Files.copy(summaryStream, reportFolderPath.resolve("summary.png")); //NON-NLS } //copy snapshot icon try (InputStream snapshotIconStream = SnapShotReportWriter.class.getResourceAsStream("/org/sleuthkit/autopsy/timeline/images/image.png")) { //NON-NLS Files.copy(snapshotIconStream, reportFolderPath.resolve("snapshot_icon.png")); //NON-NLS } //copy main report css try (InputStream resource = SnapShotReportWriter.class.getResourceAsStream("/org/sleuthkit/autopsy/timeline/snapshot/index.css")) { //NON-NLS Files.copy(resource, reportFolderPath.resolve("index.css")); //NON-NLS } //copy summary css try (InputStream resource = SnapShotReportWriter.class.getResourceAsStream("/org/sleuthkit/autopsy/timeline/snapshot/summary.css")) { //NON-NLS Files.copy(resource, reportFolderPath.resolve("summary.css")); //NON-NLS } } }