23 package org.sleuthkit.autopsy.report;
 
   25 import java.awt.image.BufferedImage;
 
   26 import java.io.BufferedWriter;
 
   28 import java.io.FileNotFoundException;
 
   29 import java.io.FileOutputStream;
 
   30 import java.io.IOException;
 
   31 import java.io.InputStream;
 
   32 import java.io.OutputStream;
 
   33 import java.io.OutputStreamWriter;
 
   34 import java.io.UnsupportedEncodingException;
 
   35 import java.io.Writer;
 
   36 import java.nio.file.Files;
 
   37 import java.nio.file.Path;
 
   38 import java.nio.file.Paths;
 
   39 import java.text.DateFormat;
 
   40 import java.text.SimpleDateFormat;
 
   41 import java.util.ArrayList;
 
   42 import java.util.Date;
 
   43 import java.util.HashMap;
 
   44 import java.util.List;
 
   47 import java.util.TreeMap;
 
   48 import java.util.logging.Level;
 
   49 import javax.imageio.ImageIO;
 
   50 import javax.swing.JPanel;
 
   51 import org.apache.commons.lang3.StringEscapeUtils;
 
   52 import org.openide.filesystems.FileUtil;
 
   53 import org.openide.util.NbBundle;
 
   67 import org.
sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
 
   76 import org.
sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM;
 
   78 class ReportHTML 
implements TableReportModule {
 
   80     private static final Logger logger = Logger.getLogger(ReportHTML.class.getName());
 
   81     private static final String THUMBS_REL_PATH = 
"thumbs" + File.separator; 
 
   82     private static ReportHTML instance;
 
   83     private static final int MAX_THUMBS_PER_PAGE = 1000;
 
   84     private static final String HTML_SUBDIR = 
"content";
 
   85     private Case currentCase;
 
   86     static Integer THUMBNAIL_COLUMNS = 5;
 
   88     private Map<String, Integer> dataTypes;
 
   90     private String thumbsPath;
 
   91     private String subPath;
 
   92     private String currentDataType; 
 
   93     private Integer rowCount;       
 
   96     private ReportHTMLConfigurationPanel configPanel;
 
   98     private final ReportBranding reportBranding;
 
  101     public static synchronized ReportHTML getDefault() {
 
  102         if (instance == null) {
 
  103             instance = 
new ReportHTML();
 
  109     private ReportHTML() {
 
  110         reportBranding = 
new ReportBranding();
 
  114     public JPanel getConfigurationPanel() {
 
  115         if (configPanel == null) {
 
  116             configPanel = 
new ReportHTMLConfigurationPanel();
 
  122     private void refresh() throws NoCurrentCaseException {
 
  123         currentCase = Case.getCurrentCaseThrows();
 
  125         dataTypes = 
new TreeMap<>();
 
  130         currentDataType = 
"";
 
  136             } 
catch (IOException ex) {
 
  148     private String dataTypeToFileName(String dataType) {
 
  152         fileName = fileName.replaceAll(
" ", 
"_");
 
  161     private String useDataTypeIcon(String dataType) {
 
  165         OutputStream output = null;
 
  167         logger.log(Level.INFO, 
"useDataTypeIcon: dataType = {0}", dataType); 
 
  170         BlackboardArtifact.ARTIFACT_TYPE artifactType = null;
 
  171         for (ARTIFACT_TYPE v : ARTIFACT_TYPE.values()) {
 
  172             if (v.getDisplayName().equals(dataType)) {
 
  177         if (null != artifactType) {
 
  179             iconFileName = dataTypeToFileName(artifactType.getDisplayName()) + 
".png"; 
 
  180             iconFilePath = subPath + File.separator + iconFileName;
 
  183             switch (artifactType) {
 
  184                 case TSK_WEB_BOOKMARK:
 
  185                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/bookmarks.png"); 
 
  188                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/cookies.png"); 
 
  190                 case TSK_WEB_HISTORY:
 
  191                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/history.png"); 
 
  193                 case TSK_WEB_DOWNLOAD:
 
  194                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/downloads.png"); 
 
  196                 case TSK_RECENT_OBJECT:
 
  197                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/recent.png"); 
 
  199                 case TSK_INSTALLED_PROG:
 
  200                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/installed.png"); 
 
  202                 case TSK_KEYWORD_HIT:
 
  203                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/keywords.png"); 
 
  205                 case TSK_HASHSET_HIT:
 
  206                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/hash.png"); 
 
  208                 case TSK_DEVICE_ATTACHED:
 
  209                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/devices.png"); 
 
  211                 case TSK_WEB_SEARCH_QUERY:
 
  212                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/search.png"); 
 
  214                 case TSK_METADATA_EXIF:
 
  215                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/exif.png"); 
 
  218                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/userbookmarks.png"); 
 
  220                 case TSK_TAG_ARTIFACT:
 
  221                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/userbookmarks.png"); 
 
  223                 case TSK_SERVICE_ACCOUNT:
 
  224                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/account-icon-16.png"); 
 
  227                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/contact.png"); 
 
  230                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/message.png"); 
 
  233                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/calllog.png"); 
 
  235                 case TSK_CALENDAR_ENTRY:
 
  236                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/calendar.png"); 
 
  238                 case TSK_SPEED_DIAL_ENTRY:
 
  239                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/speeddialentry.png"); 
 
  241                 case TSK_BLUETOOTH_PAIRING:
 
  242                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/bluetooth.png"); 
 
  244                 case TSK_GPS_BOOKMARK:
 
  245                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gpsfav.png"); 
 
  247                 case TSK_GPS_LAST_KNOWN_LOCATION:
 
  248                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gps-lastlocation.png"); 
 
  251                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gps-search.png"); 
 
  254                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/computer.png"); 
 
  256                 case TSK_GPS_TRACKPOINT:
 
  257                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gps_trackpoint.png"); 
 
  260                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gps_trackpoint.png"); 
 
  263                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/mail-icon-16.png"); 
 
  265                 case TSK_ENCRYPTION_SUSPECTED:
 
  266                 case TSK_ENCRYPTION_DETECTED:
 
  267                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/encrypted-file.png"); 
 
  269                 case TSK_EXT_MISMATCH_DETECTED:
 
  270                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/mismatch-16.png"); 
 
  272                 case TSK_INTERESTING_ARTIFACT_HIT:
 
  273                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/interesting_item.png"); 
 
  275                 case TSK_INTERESTING_FILE_HIT:
 
  276                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/interesting_item.png"); 
 
  279                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/installed.png"); 
 
  281                 case TSK_REMOTE_DRIVE:
 
  282                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/drive_network.png"); 
 
  285                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/accounts.png"); 
 
  287                 case TSK_WIFI_NETWORK:
 
  288                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/network-wifi.png"); 
 
  290                 case TSK_WIFI_NETWORK_ADAPTER:
 
  291                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/network-wifi.png"); 
 
  293                 case TSK_SIM_ATTACHED:
 
  294                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/sim_card.png"); 
 
  296                 case TSK_BLUETOOTH_ADAPTER:
 
  297                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/Bluetooth.png"); 
 
  299                 case TSK_DEVICE_INFO:
 
  300                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/devices.png"); 
 
  302                 case TSK_VERIFICATION_FAILED:
 
  303                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/validationFailed.png"); 
 
  306                     logger.log(Level.WARNING, 
"useDataTypeIcon: unhandled artifact type = {0}", dataType); 
 
  307                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/star.png"); 
 
  308                     iconFileName = 
"star.png"; 
 
  309                     iconFilePath = subPath + File.separator + iconFileName;
 
  312         } 
else if (dataType.startsWith(ARTIFACT_TYPE.TSK_ACCOUNT.getDisplayName())) {
 
  320             in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/accounts.png"); 
 
  321             iconFileName = 
"accounts.png"; 
 
  322             iconFilePath = subPath + File.separator + iconFileName;
 
  324             in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/star.png"); 
 
  325             iconFileName = 
"star.png"; 
 
  326             iconFilePath = subPath + File.separator + iconFileName;
 
  330             output = 
new FileOutputStream(iconFilePath);
 
  331             FileUtil.copy(in, output);
 
  334         } 
catch (IOException ex) {
 
  335             logger.log(Level.SEVERE, 
"Failed to extract images for HTML report.", ex); 
 
  337             if (output != null) {
 
  341                 } 
catch (IOException ex) {
 
  347                 } 
catch (IOException ex) {
 
  362     public void startReport(String baseReportDir) {
 
  364         ModuleSettings.setConfigSetting(
"HTMLReport", 
"header", configPanel.getHeader()); 
 
  365         ModuleSettings.setConfigSetting(
"HTMLReport", 
"footer", configPanel.getFooter()); 
 
  370         } 
catch (NoCurrentCaseException ex) {
 
  371             logger.log(Level.SEVERE, 
"Exception while getting open case."); 
 
  375         this.path = baseReportDir; 
 
  376         this.subPath = this.path + HTML_SUBDIR + File.separator;
 
  377         this.thumbsPath = this.subPath + THUMBS_REL_PATH; 
 
  379             FileUtil.createFolder(
new File(this.subPath));
 
  380             FileUtil.createFolder(
new File(this.thumbsPath));
 
  381         } 
catch (IOException ex) {
 
  382             logger.log(Level.SEVERE, 
"Unable to make HTML report folder."); 
 
  395     public void endReport() {
 
  400             } 
catch (IOException ex) {
 
  401                 logger.log(Level.WARNING, 
"Could not close the output writer when ending report.", ex); 
 
  415     public void startDataType(String name, String description) {
 
  416         String title = dataTypeToFileName(name);
 
  418             out = 
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(subPath + title + 
".html"), 
"UTF-8")); 
 
  419         } 
catch (FileNotFoundException ex) {
 
  420             logger.log(Level.SEVERE, 
"File not found: {0}", ex); 
 
  421         } 
catch (UnsupportedEncodingException ex) {
 
  422             logger.log(Level.SEVERE, 
"Unrecognized encoding"); 
 
  426             StringBuilder page = 
new StringBuilder();
 
  427             page.append(
"<html>\n<head>\n\t<title>").append(name).append(
"</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"index.css\" />\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n</head>\n<body>\n") 
 
  428             .append(writePageHeader())
 
  429             .append(
"<div id=\"header\">").append(name).append(
"</div>\n")
 
  430             .append(
"<div id=\"content\">\n"); 
 
  431             if (!description.isEmpty()) {
 
  432                 page.append(
"<p><strong>"); 
 
  433                 page.append(description);
 
  434                 page.append(
"</strong></p>\n"); 
 
  436             out.write(page.toString());
 
  437             currentDataType = name;
 
  439         } 
catch (IOException ex) {
 
  440             logger.log(Level.SEVERE, 
"Failed to write page head: {0}", ex); 
 
  449     public void endDataType() {
 
  450         dataTypes.put(currentDataType, rowCount);
 
  452             StringBuilder builder = 
new StringBuilder();
 
  453             builder.append(writePageFooter());
 
  454             builder.append(
"</div>\n</body>\n</html>\n"); 
 
  455             out.write(builder.toString());
 
  456         } 
catch (IOException ex) {
 
  457             logger.log(Level.SEVERE, 
"Failed to write end of HTML report.", ex); 
 
  463                 } 
catch (IOException ex) {
 
  464                     logger.log(Level.WARNING, 
"Could not close the output writer when ending data type.", ex); 
 
  477     private String writePageHeader() {
 
  478         StringBuilder output = 
new StringBuilder();
 
  479         String pageHeader = configPanel.getHeader();
 
  480         if (pageHeader.isEmpty() == 
false) {
 
  481             output.append(
"<div id=\"pageHeaderFooter\">")
 
  482                     .append(StringEscapeUtils.escapeHtml4(pageHeader))
 
  485         return output.toString();
 
  494     private String writePageFooter() {
 
  495         StringBuilder output = 
new StringBuilder();
 
  496         String pageFooter = configPanel.getFooter();
 
  497         if (pageFooter.isEmpty() == 
false) {
 
  498             output.append(
"<br/><div id=\"pageHeaderFooter\">")
 
  499                     .append(StringEscapeUtils.escapeHtml4(pageFooter))
 
  502         return output.toString();
 
  511     public void startSet(String setName) {
 
  512         StringBuilder set = 
new StringBuilder();
 
  513         set.append(
"<h1><a name=\"").append(setName).append(
"\">").append(setName).append(
"</a></h1>\n"); 
 
  514         set.append(
"<div class=\"keyword_list\">\n"); 
 
  517             out.write(set.toString());
 
  518         } 
catch (IOException ex) {
 
  519             logger.log(Level.SEVERE, 
"Failed to write set: {0}", ex); 
 
  527     public void endSet() {
 
  529             out.write(
"</div>\n"); 
 
  530         } 
catch (IOException ex) {
 
  531             logger.log(Level.SEVERE, 
"Failed to write end of set: {0}", ex); 
 
  541     public void addSetIndex(List<String> sets) {
 
  542         StringBuilder index = 
new StringBuilder();
 
  543         index.append(
"<ul>\n"); 
 
  544         for (String set : sets) {
 
  545             index.append(
"\t<li><a href=\"#").append(set).append(
"\">").append(set).append(
"</a></li>\n"); 
 
  547         index.append(
"</ul>\n"); 
 
  549             out.write(index.toString());
 
  550         } 
catch (IOException ex) {
 
  551             logger.log(Level.SEVERE, 
"Failed to add set index: {0}", ex); 
 
  561     public void addSetElement(String elementName) {
 
  563             out.write(
"<h4>" + elementName + 
"</h4>\n"); 
 
  564         } 
catch (IOException ex) {
 
  565             logger.log(Level.SEVERE, 
"Failed to write set element: {0}", ex); 
 
  575     public void startTable(List<String> titles) {
 
  576         StringBuilder ele = 
new StringBuilder();
 
  577         ele.append(
"<table>\n<thead>\n\t<tr>\n"); 
 
  578         for (String title : titles) {
 
  579             ele.append(
"\t\t<th>").append(title).append(
"</th>\n"); 
 
  581         ele.append(
"\t</tr>\n</thead>\n"); 
 
  584             out.write(ele.toString());
 
  585         } 
catch (IOException ex) {
 
  586             logger.log(Level.SEVERE, 
"Failed to write table start: {0}", ex); 
 
  597     public void startContentTagsTable(List<String> columnHeaders) {
 
  598         StringBuilder htmlOutput = 
new StringBuilder();
 
  599         htmlOutput.append(
"<table>\n<thead>\n\t<tr>\n"); 
 
  602         for (String columnHeader : columnHeaders) {
 
  603             htmlOutput.append(
"\t\t<th>").append(columnHeader).append(
"</th>\n"); 
 
  607         htmlOutput.append(
"\t\t<th></th>\n"); 
 
  609         htmlOutput.append(
"\t</tr>\n</thead>\n"); 
 
  612             out.write(htmlOutput.toString());
 
  613         } 
catch (IOException ex) {
 
  614             logger.log(Level.SEVERE, 
"Failed to write table start: {0}", ex); 
 
  622     public void endTable() {
 
  624             out.write(
"</table>\n"); 
 
  625         } 
catch (IOException ex) {
 
  626             logger.log(Level.SEVERE, 
"Failed to write end of table: {0}", ex); 
 
  637     public void addRow(List<String> row) {
 
  648     private void addRow(List<String> row, 
boolean escapeText) {
 
  649         StringBuilder builder = 
new StringBuilder();
 
  650         builder.append(
"\t<tr>\n"); 
 
  651         for (String cell : row) {
 
  652             String cellText = escapeText ? EscapeUtil.escapeHtml(cell) : cell;
 
  653             builder.append(
"\t\t<td>").append(cellText).append(
"</td>\n"); 
 
  655         builder.append(
"\t</tr>\n"); 
 
  659             out.write(builder.toString());
 
  660         } 
catch (IOException ex) {
 
  661             logger.log(Level.SEVERE, 
"Failed to write row to out.", ex); 
 
  662         } 
catch (NullPointerException ex) {
 
  663             logger.log(Level.SEVERE, 
"Output writer is null. Page was not initialized before writing.", ex); 
 
  677     public void addRowWithTaggedContentHyperlink(List<String> row, ContentTag contentTag) {
 
  678         Content content = contentTag.getContent();
 
  679         if (content instanceof AbstractFile == 
false) {
 
  683         AbstractFile file = (AbstractFile) content;
 
  685         StringBuilder localFileLink = 
new StringBuilder();
 
  688                 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS
 
  689                 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS)) {
 
  690             localFileLink.append(
"<a href=\""); 
 
  692             String localFilePath = saveContent(file, contentTag.getName().getDisplayName());
 
  693             localFileLink.append(localFilePath);
 
  694             localFileLink.append(
"\" target=\"_top\">");
 
  697         StringBuilder builder = 
new StringBuilder();
 
  698         builder.append(
"\t<tr>\n"); 
 
  699         int positionCounter = 0;
 
  700         for (String cell : row) {
 
  702             switch (positionCounter) {
 
  705                     builder.append(
"\t\t<td class=\"left_align_cell\">").append(localFileLink.toString()).append(cell).append(
"</a></td>\n"); 
 
  709                     builder.append(
"\t\t<td class=\"right_align_cell\">").append(cell).append(
"</td>\n"); 
 
  713                     builder.append(
"\t\t<td>").append(cell).append(
"</td>\n"); 
 
  718         builder.append(
"\t</tr>\n"); 
 
  722             out.write(builder.toString());
 
  723         } 
catch (IOException ex) {
 
  724             logger.log(Level.SEVERE, 
"Failed to write row to out.", ex); 
 
  725         } 
catch (NullPointerException ex) {
 
  726             logger.log(Level.SEVERE, 
"Output writer is null. Page was not initialized before writing.", ex); 
 
  735     public void addThumbnailRows(Set<Content> images) {
 
  736         List<String> currentRow = 
new ArrayList<>();
 
  739         for (Content content : images) {
 
  740             if (currentRow.size() == THUMBNAIL_COLUMNS) {
 
  741                 addRow(currentRow, 
false);
 
  745             if (totalCount == MAX_THUMBS_PER_PAGE) {
 
  749                 rowCount = totalCount;
 
  754                 startDataType(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.addThumbRows.dataType.title", pages),
 
  755                         NbBundle.getMessage(
this.getClass(), 
"ReportHTML.addThumbRows.dataType.msg"));
 
  756                 List<String> emptyHeaders = 
new ArrayList<>();
 
  757                 for (
int i = 0; i < THUMBNAIL_COLUMNS; i++) {
 
  758                     emptyHeaders.add(
"");
 
  760                 startTable(emptyHeaders);
 
  763             if (failsContentCheck(content)) {
 
  767             AbstractFile file = (AbstractFile) content;
 
  770             String thumbnailPath = prepareThumbnail(file);
 
  771             if (thumbnailPath == null) {
 
  774             String contentPath = saveContent(file, 
"thumbs_fullsize"); 
 
  777                 nameInImage = file.getUniquePath();
 
  778             } 
catch (TskCoreException ex) {
 
  779                 nameInImage = file.getName();
 
  782             StringBuilder linkToThumbnail = 
new StringBuilder();
 
  783             linkToThumbnail.append(
"<div id='thumbnail_link'>");
 
  784             linkToThumbnail.append(
"<a href=\""); 
 
  785             linkToThumbnail.append(contentPath);
 
  786             linkToThumbnail.append(
"\" target=\"_top\">");
 
  787             linkToThumbnail.append(
"<img src=\"").append(thumbnailPath).append(
"\" title=\"").append(nameInImage).append(
"\"/>"); 
 
  788             linkToThumbnail.append(
"</a><br>"); 
 
  789             linkToThumbnail.append(file.getName()).append(
"<br>"); 
 
  791             Services services = currentCase.getServices();
 
  792             TagsManager tagsManager = services.getTagsManager();
 
  794                 List<ContentTag> tags = tagsManager.getContentTagsByContent(content);
 
  795                 if (tags.size() > 0) {
 
  796                     linkToThumbnail.append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.thumbLink.tags"));
 
  798                 for (
int i = 0; i < tags.size(); i++) {
 
  799                     ContentTag tag = tags.get(i);
 
  800                     String notableString = tag.getName().getKnownStatus() == TskData.FileKnown.BAD ? TagsManager.getNotableTagLabel() : 
"";
 
  801                     linkToThumbnail.append(tag.getName().getDisplayName()).append(notableString);
 
  802                     if (i != tags.size() - 1) {
 
  803                         linkToThumbnail.append(
", ");
 
  806             } 
catch (TskCoreException ex) {
 
  807                 logger.log(Level.WARNING, 
"Could not find get tags for file.", ex); 
 
  809             linkToThumbnail.append(
"</div>");
 
  810             currentRow.add(linkToThumbnail.toString());
 
  815         if (currentRow.isEmpty() == 
false) {
 
  816             int extraCells = THUMBNAIL_COLUMNS - currentRow.size();
 
  817             for (
int i = 0; i < extraCells; i++) {
 
  821             addRow(currentRow, 
false);
 
  825         rowCount = totalCount;
 
  828     private boolean failsContentCheck(Content c) {
 
  829         if (c instanceof AbstractFile == 
false) {
 
  832         AbstractFile file = (AbstractFile) c;
 
  834                 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS
 
  835                 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS;
 
  847     public String saveContent(AbstractFile file, String dirName) {
 
  852         StringBuilder localFilePath = 
new StringBuilder();  
 
  854         localFilePath.append(subPath);
 
  855         localFilePath.append(dirName2);
 
  856         File localFileFolder = 
new File(localFilePath.toString());
 
  857         if (!localFileFolder.exists()) {
 
  858             localFileFolder.mkdirs();
 
  869         String objectIdSuffix = 
"_" + file.getId();
 
  870         int lastDotIndex = fileName.lastIndexOf(
".");
 
  871         if (lastDotIndex != -1 && lastDotIndex != 0) {
 
  873             fileName = fileName.substring(0, lastDotIndex) + objectIdSuffix + fileName.substring(lastDotIndex, fileName.length());
 
  877             fileName += objectIdSuffix;
 
  879         localFilePath.append(File.separator);
 
  880         localFilePath.append(fileName);
 
  884         File localFile = 
new File(localFilePath.toString());
 
  885         if (!localFile.exists()) {
 
  886             ExtractFscContentVisitor.extract(file, localFile, null, null);
 
  890         return localFilePath.toString().substring(subPath.length());
 
  901     public String dateToString(
long date) {
 
  902         SimpleDateFormat sdf = 
new java.text.SimpleDateFormat(
"yyyy/MM/dd HH:mm:ss");
 
  903         return sdf.format(
new java.util.Date(date * 1000));
 
  907     public String getRelativeFilePath() {
 
  908         return "report.html"; 
 
  912     public String getName() {
 
  913         return NbBundle.getMessage(this.getClass(), 
"ReportHTML.getName.text");
 
  917     public String getDescription() {
 
  918         return NbBundle.getMessage(this.getClass(), 
"ReportHTML.getDesc.text");
 
  924     private void writeCss() {
 
  925         Writer cssOut = null;
 
  927             cssOut = 
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(subPath + 
"index.css"), 
"UTF-8")); 
 
  928             String css = 
"body {margin: 0px; padding: 0px; background: #FFFFFF; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353;}\n" 
  930                     "#content {padding: 30px;}\n" 
  932                     "#header {width:100%; padding: 10px; line-height: 25px; background: #07A; color: #FFF; font-size: 20px;}\n" 
  934                     "#pageHeaderFooter {width: 100%; padding: 10px; line-height: 25px; text-align: center; font-size: 20px;}\n" 
  936                     "h1 {font-size: 20px; font-weight: normal; color: #07A; padding: 0 0 7px 0; margin-top: 25px; border-bottom: 1px solid #D6D6D6;}\n" 
  938                     "h2 {font-size: 20px; font-weight: bolder; color: #07A;}\n" 
  940                     "h3 {font-size: 16px; color: #07A;}\n" 
  942                     "h4 {background: #07A; color: #FFF; font-size: 16px; margin: 0 0 0 25px; padding: 0; padding-left: 15px;}\n" 
  944                     "ul.nav {list-style-type: none; line-height: 35px; padding: 0px; margin-left: 15px;}\n" 
  946                     "ul li a {font-size: 14px; color: #444; text-decoration: none; padding-left: 25px;}\n" 
  948                     "ul li a:hover {text-decoration: underline;}\n" 
  950                     "p {margin: 0 0 20px 0;}\n" 
  952                     "table {white-space:nowrap; min-width: 700px; padding: 2; margin: 0; border-collapse: collapse; border-bottom: 2px solid #e5e5e5;}\n" 
  954                     ".keyword_list table {margin: 0 0 25px 25px; border-bottom: 2px solid #dedede;}\n" 
  956                     "table th {white-space:nowrap; display: table-cell; text-align: center; padding: 2px 4px; background: #e5e5e5; color: #777; font-size: 11px; text-shadow: #e9f9fd 0 1px 0; border-top: 1px solid #dedede; border-bottom: 2px solid #e5e5e5;}\n" 
  958                     "table .left_align_cell{display: table-cell; padding: 2px 4px; font: 13px/20px Arial, Helvetica, sans-serif; min-width: 125px; overflow: auto; text-align: left; }\n" 
  960                     "table .right_align_cell{display: table-cell; padding: 2px 4px; font: 13px/20px Arial, Helvetica, sans-serif; min-width: 125px; overflow: auto; text-align: right; }\n" 
  962                     "table td {white-space:nowrap; display: table-cell; padding: 2px 3px; font: 13px/20px Arial, Helvetica, sans-serif; min-width: 125px; overflow: auto; text-align:left; vertical-align: text-top;}\n" 
  964                     "table tr:nth-child(even) td {background: #f3f3f3;}\n" 
  966                     "div#thumbnail_link {max-width: 200px; white-space: pre-wrap; white-space: -moz-pre-wrap; white-space: -pre-wrap; white-space: -o-pre-wrap; word-wrap: break-word;}";
 
  968         } 
catch (FileNotFoundException ex) {
 
  969             logger.log(Level.SEVERE, 
"Could not find index.css file to write to.", ex); 
 
  970         } 
catch (UnsupportedEncodingException ex) {
 
  971             logger.log(Level.SEVERE, 
"Did not recognize encoding when writing index.css.", ex); 
 
  972         } 
catch (IOException ex) {
 
  973             logger.log(Level.SEVERE, 
"Error creating Writer for index.css.", ex); 
 
  976                 if (cssOut != null) {
 
  980             } 
catch (IOException ex) {
 
  988     private void writeIndex() {
 
  989         Writer indexOut = null;
 
  990         String indexFilePath = path + 
"report.html"; 
 
  993             openCase = Case.getCurrentCaseThrows();
 
  994         } 
catch (NoCurrentCaseException ex) {
 
  995             logger.log(Level.SEVERE, 
"Exception while getting open case.", ex); 
 
  999             indexOut = 
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(indexFilePath), 
"UTF-8")); 
 
 1000             StringBuilder index = 
new StringBuilder();
 
 1001             final String reportTitle = reportBranding.getReportTitle();
 
 1002             String iconPath = reportBranding.getAgencyLogoPath();
 
 1003             if (iconPath == null) {
 
 1005                 iconPath = HTML_SUBDIR + 
"favicon.ico";
 
 1007                 iconPath = Paths.get(reportBranding.getAgencyLogoPath()).getFileName().toString(); 
 
 1009             index.append(
"<head>\n<title>").append(reportTitle).append(
" ").append(
 
 1010                     NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeIndex.title", currentCase.getDisplayName())).append(
 
 1012             index.append(
"<link rel=\"icon\" type=\"image/ico\" href=\"")
 
 1013                     .append(iconPath).append(
"\" />\n"); 
 
 1014             index.append(
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"); 
 
 1015             index.append(
"</head>\n"); 
 
 1016             index.append(
"<frameset cols=\"350px,*\">\n"); 
 
 1017             index.append(
"<frame src=\"" + HTML_SUBDIR).append(File.separator).append(
"nav.html\" name=\"nav\">\n"); 
 
 1018             index.append(
"<frame src=\"" + HTML_SUBDIR).append(File.separator).append(
"summary.html\" name=\"content\">\n"); 
 
 1019             index.append(
"<noframes>").append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeIndex.noFrames.msg")).append(
"<br />\n"); 
 
 1020             index.append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeIndex.noFrames.seeNav")).append(
"<br />\n"); 
 
 1021             index.append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeIndex.seeSum")).append(
"</noframes>\n"); 
 
 1022             index.append(
"</frameset>\n"); 
 
 1023             index.append(
"</html>"); 
 
 1024             indexOut.write(index.toString());
 
 1025             openCase.addReport(indexFilePath, NbBundle.getMessage(
this.getClass(),
 
 1026                     "ReportHTML.writeIndex.srcModuleName.text"), 
"");
 
 1027         } 
catch (IOException ex) {
 
 1028             logger.log(Level.SEVERE, 
"Error creating Writer for report.html: {0}", ex); 
 
 1029         } 
catch (TskCoreException ex) {
 
 1030             String errorMessage = String.format(
"Error adding %s to case as a report", indexFilePath); 
 
 1031             logger.log(Level.SEVERE, errorMessage, ex);
 
 1034                 if (indexOut != null) {
 
 1038             } 
catch (IOException ex) {
 
 1046     private void writeNav() {
 
 1047         Writer navOut = null;
 
 1049             navOut = 
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(subPath + 
"nav.html"), 
"UTF-8")); 
 
 1050             StringBuilder nav = 
new StringBuilder();
 
 1051             nav.append(
"<html>\n<head>\n\t<title>").append( 
 
 1052                     NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeNav.title"))
 
 1053                     .append(
"</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"index.css\" />\n"); 
 
 1054             nav.append(
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n</head>\n<body>\n"); 
 
 1055             nav.append(
"<div id=\"content\">\n<h1>").append( 
 
 1056                     NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeNav.h1")).append(
"</h1>\n"); 
 
 1057             nav.append(
"<ul class=\"nav\">\n"); 
 
 1058             nav.append(
"<li style=\"background: url(summary.png) left center no-repeat;\"><a href=\"summary.html\" target=\"content\">") 
 
 1059                     .append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeNav.summary")).append(
"</a></li>\n"); 
 
 1061             for (String dataType : dataTypes.keySet()) {
 
 1062                 String dataTypeEsc = dataTypeToFileName(dataType);
 
 1063                 String iconFileName = useDataTypeIcon(dataType);
 
 1064                 nav.append(
"<li style=\"background: url('").append(iconFileName) 
 
 1065                         .append(
"') left center no-repeat;\"><a href=\"") 
 
 1066                         .append(dataTypeEsc).append(
".html\" target=\"content\">") 
 
 1067                         .append(dataType).append(
" (").append(dataTypes.get(dataType))
 
 1068                         .append(
")</a></li>\n"); 
 
 1070             nav.append(
"</ul>\n"); 
 
 1071             nav.append(
"</div>\n</body>\n</html>"); 
 
 1072             navOut.write(nav.toString());
 
 1073         } 
catch (IOException ex) {
 
 1074             logger.log(Level.SEVERE, 
"Failed to write end of report navigation menu: {0}", ex); 
 
 1076             if (navOut != null) {
 
 1080                 } 
catch (IOException ex) {
 
 1081                     logger.log(Level.WARNING, 
"Could not close navigation out writer."); 
 
 1086         InputStream in = null;
 
 1087         OutputStream output = null;
 
 1091             String generatorLogoPath = reportBranding.getGeneratorLogoPath();
 
 1092             if (generatorLogoPath != null && !generatorLogoPath.isEmpty()) {
 
 1093                 File from = 
new File(generatorLogoPath);
 
 1094                 File to = 
new File(subPath);
 
 1095                 FileUtil.copyFile(FileUtil.toFileObject(from), FileUtil.toFileObject(to), 
"generator_logo"); 
 
 1098             String agencyLogoPath = reportBranding.getAgencyLogoPath();
 
 1099             if (agencyLogoPath != null && !agencyLogoPath.isEmpty()) {
 
 1100                 Path destinationPath = Paths.get(subPath);
 
 1101                 Files.copy(Files.newInputStream(Paths.get(agencyLogoPath)), destinationPath.resolve(Paths.get(agencyLogoPath).getFileName())); 
 
 1104             in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/favicon.ico"); 
 
 1105             output = 
new FileOutputStream(
new File(subPath + 
"favicon.ico"));
 
 1106             FileUtil.copy(in, output);
 
 1110             in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/summary.png"); 
 
 1111             output = 
new FileOutputStream(
new File(subPath + 
"summary.png"));
 
 1112             FileUtil.copy(in, output);
 
 1116         } 
catch (IOException ex) {
 
 1117             logger.log(Level.SEVERE, 
"Failed to extract images for HTML report.", ex); 
 
 1119             if (output != null) {
 
 1123                 } 
catch (IOException ex) {
 
 1129                 } 
catch (IOException ex) {
 
 1138     private void writeSummary() {
 
 1139         Writer output = null;
 
 1141             output = 
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(subPath + 
"summary.html"), 
"UTF-8")); 
 
 1142             StringBuilder head = 
new StringBuilder();
 
 1143             head.append(
"<html>\n<head>\n<title>").append( 
 
 1144                     NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.title")).append(
"</title>\n"); 
 
 1145             head.append(
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"); 
 
 1146             head.append(
"<style type=\"text/css\">\n"); 
 
 1147             head.append(
"#pageHeaderFooter {width: 100%; padding: 10px; line-height: 25px; text-align: center; font-size: 20px;}\n"); 
 
 1148             head.append(
"body { padding: 0px; margin: 0px; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353; }\n"); 
 
 1149             head.append(
"#wrapper { width: 90%; margin: 0px auto; margin-top: 35px; }\n"); 
 
 1150             head.append(
"h1 { color: #07A; font-size: 36px; line-height: 42px; font-weight: normal; margin: 0px; border-bottom: 1px solid #81B9DB; }\n"); 
 
 1151             head.append(
"h1 span { color: #F00; display: block; font-size: 16px; font-weight: bold; line-height: 22px;}\n"); 
 
 1152             head.append(
"h2 { padding: 0 0 3px 0; margin: 0px; color: #07A; font-weight: normal; border-bottom: 1px dotted #81B9DB; }\n"); 
 
 1153             head.append(
"h3 { padding: 5 0 3px 0; margin: 0px; color: #07A; font-weight: normal; }\n");
 
 1154             head.append(
"table td { padding: 5px 25px 5px 0px; vertical-align:top;}\n"); 
 
 1155             head.append(
"p.subheadding { padding: 0px; margin: 0px; font-size: 11px; color: #B5B5B5; }\n"); 
 
 1156             head.append(
".title { width: 660px; margin-bottom: 50px; }\n"); 
 
 1157             head.append(
".left { float: left; width: 250px; margin-top: 20px; text-align: center; }\n"); 
 
 1158             head.append(
".left img { max-width: 250px; max-height: 250px; min-width: 200px; min-height: 200px; }\n"); 
 
 1159             head.append(
".right { float: right; width: 385px; margin-top: 25px; font-size: 14px; }\n"); 
 
 1160             head.append(
".clear { clear: both; }\n"); 
 
 1161             head.append(
".info { padding: 10px 0;}\n");
 
 1162             head.append(
".info p { padding: 3px 10px; background: #e5e5e5; color: #777; font-size: 12px; font-weight: bold; text-shadow: #e9f9fd 0 1px 0; border-top: 1px solid #dedede; border-bottom: 2px solid #dedede; }\n"); 
 
 1163             head.append(
".info table { margin: 10px 25px 10px 25px; }\n"); 
 
 1164             head.append(
"ul {padding: 0;margin: 0;list-style-type: none;}");
 
 1165             head.append(
"li {padding-bottom: 5px;}");
 
 1166             head.append(
"</style>\n"); 
 
 1167             head.append(
"</head>\n<body>\n"); 
 
 1168             output.write(head.toString());
 
 1170             DateFormat datetimeFormat = 
new SimpleDateFormat(
"yyyy/MM/dd HH:mm:ss");
 
 1171             Date date = 
new Date();
 
 1172             String datetime = datetimeFormat.format(date);
 
 1174             StringBuilder summary = 
new StringBuilder();
 
 1175             boolean running = 
false;
 
 1176             if (IngestManager.getInstance().isIngestRunning()) {
 
 1179             SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
 
 1180             List<IngestJobInfo> ingestJobs = skCase.getIngestJobs();
 
 1181             final String reportTitle = reportBranding.getReportTitle();
 
 1182             final String reportFooter = reportBranding.getReportFooter();
 
 1183             final boolean generatorLogoSet = reportBranding.getGeneratorLogoPath() != null && !reportBranding.getGeneratorLogoPath().isEmpty();
 
 1185             summary.append(
"<div id=\"wrapper\">\n"); 
 
 1186             summary.append(writePageHeader());
 
 1187             summary.append(
"<h1>").append(reportTitle) 
 
 1188                     .append(running ? NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.warningMsg") : 
"")
 
 1190             summary.append(
"<p class=\"subheadding\">").append( 
 
 1191                     NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.reportGenOn.text", datetime)).append(
"</p>\n"); 
 
 1192             summary.append(
"<div class=\"title\">\n"); 
 
 1193             summary.append(writeSummaryCaseDetails());
 
 1194             summary.append(writeSummaryImageInfo());
 
 1195             summary.append(writeSummarySoftwareInfo(skCase, ingestJobs));
 
 1196             summary.append(writeSummaryIngestHistoryInfo(skCase, ingestJobs));
 
 1197             if (generatorLogoSet) {
 
 1198                 summary.append(
"<div class=\"left\">\n"); 
 
 1199                 summary.append(
"<img src=\"generator_logo.png\" />\n"); 
 
 1200                 summary.append(
"</div>\n"); 
 
 1202             summary.append(
"<div class=\"clear\"></div>\n"); 
 
 1203             if (reportFooter != null) {
 
 1204                 summary.append(
"<p class=\"subheadding\">").append(reportFooter).append(
"</p>\n"); 
 
 1206             summary.append(
"</div>\n"); 
 
 1207             summary.append(writePageFooter());
 
 1208             summary.append(
"</body></html>"); 
 
 1209             output.write(summary.toString());
 
 1210         } 
catch (FileNotFoundException ex) {
 
 1211             logger.log(Level.SEVERE, 
"Could not find summary.html file to write to."); 
 
 1212         } 
catch (UnsupportedEncodingException ex) {
 
 1213             logger.log(Level.SEVERE, 
"Did not recognize encoding when writing summary.hmtl."); 
 
 1214         } 
catch (IOException ex) {
 
 1215             logger.log(Level.SEVERE, 
"Error creating Writer for summary.html."); 
 
 1216         } 
catch (NoCurrentCaseException | TskCoreException ex) {
 
 1217             logger.log(Level.WARNING, 
"Unable to get current sleuthkit Case for the HTML report.");
 
 1220                 if (output != null) {
 
 1224             } 
catch (IOException ex) {
 
 1234     private StringBuilder writeSummaryCaseDetails() {
 
 1235         StringBuilder summary = 
new StringBuilder();
 
 1236         String caseName = currentCase.getDisplayName();
 
 1237         String caseNumber = currentCase.getNumber();
 
 1238         String examiner = currentCase.getExaminer();
 
 1239         final boolean agencyLogoSet = reportBranding.getAgencyLogoPath() != null && !reportBranding.getAgencyLogoPath().isEmpty();
 
 1242             imagecount = currentCase.getDataSources().size();
 
 1243         } 
catch (TskCoreException ex) {
 
 1246         summary.append(
"<div class=\"title\">\n"); 
 
 1247         if (agencyLogoSet) {
 
 1248             summary.append(
"<div class=\"left\">\n"); 
 
 1249             summary.append(
"<img src=\"");
 
 1250             summary.append(Paths.get(reportBranding.getAgencyLogoPath()).getFileName().toString());
 
 1251             summary.append(
"\" />\n"); 
 
 1252             summary.append(
"</div>\n"); 
 
 1254         final String align = agencyLogoSet ? 
"right" : 
"left"; 
 
 1255         summary.append(
"<div class=\"").append(align).append(
"\">\n"); 
 
 1256         summary.append(
"<table>\n"); 
 
 1257         summary.append(
"<tr><td>").append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.caseName")) 
 
 1258                 .append(
"</td><td>").append(caseName).append(
"</td></tr>\n"); 
 
 1259         summary.append(
"<tr><td>").append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.caseNum")) 
 
 1260                 .append(
"</td><td>").append(!caseNumber.isEmpty() ? caseNumber : NbBundle 
 
 1261                 .getMessage(this.getClass(), 
"ReportHTML.writeSum.noCaseNum")).append(
"</td></tr>\n"); 
 
 1262         summary.append(
"<tr><td>").append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.examiner")).append(
"</td><td>") 
 
 1263                 .append(!examiner.isEmpty() ? examiner : NbBundle
 
 1264                         .getMessage(this.getClass(), 
"ReportHTML.writeSum.noExaminer"))
 
 1265                 .append(
"</td></tr>\n"); 
 
 1266         summary.append(
"<tr><td>").append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.numImages")) 
 
 1267                 .append(
"</td><td>").append(imagecount).append(
"</td></tr>\n"); 
 
 1268         summary.append(
"</table>\n"); 
 
 1269         summary.append(
"</div>\n"); 
 
 1270         summary.append(
"<div class=\"clear\"></div>\n"); 
 
 1271         summary.append(
"</div>\n"); 
 
 1280     private StringBuilder writeSummaryImageInfo() {
 
 1281         StringBuilder summary = 
new StringBuilder();
 
 1282         summary.append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.imageInfoHeading"));
 
 1283         summary.append(
"<div class=\"info\">\n"); 
 
 1285             for (Content c : currentCase.getDataSources()) {
 
 1286                 summary.append(
"<p>").append(c.getName()).append(
"</p>\n"); 
 
 1287                 if (c instanceof Image) {
 
 1288                     Image img = (Image) c;
 
 1290                     summary.append(
"<table>\n"); 
 
 1291                     summary.append(
"<tr><td>").append( 
 
 1292                             NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.timezone"))
 
 1293                             .append(
"</td><td>").append(img.getTimeZone()).append(
"</td></tr>\n"); 
 
 1294                     for (String imgPath : img.getPaths()) {
 
 1295                         summary.append(
"<tr><td>").append( 
 
 1296                                 NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.path"))
 
 1297                                 .append(
"</td><td>").append(imgPath).append(
"</td></tr>\n"); 
 
 1299                     summary.append(
"</table>\n"); 
 
 1302         } 
catch (TskCoreException ex) {
 
 1303             logger.log(Level.WARNING, 
"Unable to get image information for the HTML report."); 
 
 1305         summary.append(
"</div>\n"); 
 
 1314     private StringBuilder writeSummarySoftwareInfo(SleuthkitCase skCase, List<IngestJobInfo> ingestJobs) {
 
 1315         StringBuilder summary = 
new StringBuilder();
 
 1316         summary.append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.softwareInfoHeading"));
 
 1317         summary.append(
"<div class=\"info\">\n");
 
 1318         summary.append(
"<table>\n");
 
 1319         summary.append(
"<tr><td>").append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.autopsyVersion"))
 
 1320                 .append(
"</td><td>").append(Version.getVersion()).append(
"</td></tr>\n");
 
 1321         Map<Long, IngestModuleInfo> moduleInfoHashMap = 
new HashMap<>();
 
 1322         for (IngestJobInfo ingestJob : ingestJobs) {
 
 1323             List<IngestModuleInfo> ingestModules = ingestJob.getIngestModuleInfo();
 
 1324             for (IngestModuleInfo ingestModule : ingestModules) {
 
 1325                 if (!moduleInfoHashMap.containsKey(ingestModule.getIngestModuleId())) {
 
 1326                     moduleInfoHashMap.put(ingestModule.getIngestModuleId(), ingestModule);
 
 1330         TreeMap<String, String> modules = 
new TreeMap<>();
 
 1331         for (IngestModuleInfo moduleinfo : moduleInfoHashMap.values()) {
 
 1332             modules.put(moduleinfo.getDisplayName(), moduleinfo.getVersion());
 
 1334         for (Map.Entry<String, String> module : modules.entrySet()) {
 
 1335             summary.append(
"<tr><td>").append(module.getKey()).append(
" Module:")
 
 1336                     .append(
"</td><td>").append(module.getValue()).append(
"</td></tr>\n");
 
 1338         summary.append(
"</table>\n");
 
 1339         summary.append(
"</div>\n");
 
 1340         summary.append(
"<div class=\"clear\"></div>\n"); 
 
 1349     private StringBuilder writeSummaryIngestHistoryInfo(SleuthkitCase skCase, List<IngestJobInfo> ingestJobs) {
 
 1350         StringBuilder summary = 
new StringBuilder();
 
 1352             summary.append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.ingestHistoryHeading"));
 
 1353             summary.append(
"<div class=\"info\">\n");
 
 1356             for (IngestJobInfo ingestJob : ingestJobs) {
 
 1357                 summary.append(
"<h3>Job ").append(jobnumber).append(
":</h3>\n");
 
 1358                 summary.append(
"<table>\n");
 
 1359                 summary.append(
"<tr><td>").append(
"Data Source:")
 
 1360                         .append(
"</td><td>").append(skCase.getContentById(ingestJob.getObjectId()).getName()).append(
"</td></tr>\n");
 
 1361                 summary.append(
"<tr><td>").append(
"Status:")
 
 1362                         .append(
"</td><td>").append(ingestJob.getStatus()).append(
"</td></tr>\n");
 
 1363                 summary.append(
"<tr><td>").append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.modulesEnabledHeading"))
 
 1364                         .append(
"</td><td>");
 
 1365                 List<IngestModuleInfo> ingestModules = ingestJob.getIngestModuleInfo();
 
 1366                 summary.append(
"<ul>\n");
 
 1367                 for (IngestModuleInfo ingestModule : ingestModules) {
 
 1368                     summary.append(
"<li>").append(ingestModule.getDisplayName()).append(
"</li>");
 
 1370                 summary.append(
"</ul>\n");
 
 1372                 summary.append(
"</td></tr>\n");
 
 1373                 summary.append(
"</table>\n");
 
 1375             summary.append(
"</div>\n");
 
 1376         } 
catch (TskCoreException ex) {
 
 1377             logger.log(Level.WARNING, 
"Unable to get ingest jobs for the HTML report.");
 
 1390     private String prepareThumbnail(AbstractFile file) {
 
 1391         BufferedImage bufferedThumb = ImageUtils.getThumbnail(file, ImageUtils.ICON_SIZE_MEDIUM);
 
 1399         File thumbFile = Paths.get(thumbsPath, fileName + 
".png").toFile();
 
 1400         if (bufferedThumb == null) {
 
 1404             ImageIO.write(bufferedThumb, 
"png", thumbFile);
 
 1405         } 
catch (IOException ex) {
 
 1406             logger.log(Level.WARNING, 
"Failed to write thumb file to report directory.", ex); 
 
 1409         if (thumbFile.exists()
 
 1413         return THUMBS_REL_PATH
 
 1414                 + thumbFile.getName();
 
static String escapeFileName(String fileName)