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;
 
   54 import org.openide.util.NbBundle.Messages;
 
   72 import org.
sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
 
   81 import org.
sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM;
 
   83 class ReportHTML 
implements TableReportModule {
 
   85     private static final Logger logger = Logger.getLogger(ReportHTML.class.getName());
 
   86     private static final String THUMBS_REL_PATH = 
"thumbs" + File.separator; 
 
   87     private static ReportHTML instance;
 
   88     private static final int MAX_THUMBS_PER_PAGE = 1000;
 
   89     private static final String HTML_SUBDIR = 
"content";
 
   90     private Case currentCase;
 
   91     static Integer THUMBNAIL_COLUMNS = 5;
 
   93     private Map<String, Integer> dataTypes;
 
   95     private String thumbsPath;
 
   96     private String subPath;
 
   97     private String currentDataType; 
 
   98     private Integer rowCount;       
 
  101     private ReportHTMLConfigurationPanel configPanel;
 
  103     private final ReportBranding reportBranding;
 
  106     public static synchronized ReportHTML getDefault() {
 
  107         if (instance == null) {
 
  108             instance = 
new ReportHTML();
 
  114     private ReportHTML() {
 
  115         reportBranding = 
new ReportBranding();
 
  119     public JPanel getConfigurationPanel() {
 
  120         if (configPanel == null) {
 
  121             configPanel = 
new ReportHTMLConfigurationPanel();
 
  127     private void refresh() throws NoCurrentCaseException {
 
  128         currentCase = Case.getCurrentCaseThrows();
 
  130         dataTypes = 
new TreeMap<>();
 
  135         currentDataType = 
"";
 
  141             } 
catch (IOException ex) {
 
  153     private String dataTypeToFileName(String dataType) {
 
  157         fileName = fileName.replaceAll(
" ", 
"_");
 
  166     private String useDataTypeIcon(String dataType) {
 
  170         OutputStream output = null;
 
  172         logger.log(Level.INFO, 
"useDataTypeIcon: dataType = {0}", dataType); 
 
  175         BlackboardArtifact.ARTIFACT_TYPE artifactType = null;
 
  176         for (ARTIFACT_TYPE v : ARTIFACT_TYPE.values()) {
 
  177             if (v.getDisplayName().equals(dataType)) {
 
  182         if (null != artifactType) {
 
  184             iconFileName = dataTypeToFileName(artifactType.getDisplayName()) + 
".png"; 
 
  185             iconFilePath = subPath + File.separator + iconFileName;
 
  188             switch (artifactType) {
 
  189                 case TSK_WEB_BOOKMARK:
 
  190                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/bookmarks.png"); 
 
  193                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/cookies.png"); 
 
  195                 case TSK_WEB_HISTORY:
 
  196                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/history.png"); 
 
  198                 case TSK_WEB_DOWNLOAD:
 
  199                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/downloads.png"); 
 
  201                 case TSK_RECENT_OBJECT:
 
  202                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/recent.png"); 
 
  204                 case TSK_INSTALLED_PROG:
 
  205                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/installed.png"); 
 
  207                 case TSK_KEYWORD_HIT:
 
  208                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/keywords.png"); 
 
  210                 case TSK_HASHSET_HIT:
 
  211                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/hash.png"); 
 
  213                 case TSK_DEVICE_ATTACHED:
 
  214                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/devices.png"); 
 
  216                 case TSK_WEB_SEARCH_QUERY:
 
  217                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/search.png"); 
 
  219                 case TSK_METADATA_EXIF:
 
  220                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/exif.png"); 
 
  223                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/userbookmarks.png"); 
 
  225                 case TSK_TAG_ARTIFACT:
 
  226                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/userbookmarks.png"); 
 
  228                 case TSK_SERVICE_ACCOUNT:
 
  229                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/account-icon-16.png"); 
 
  232                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/contact.png"); 
 
  235                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/message.png"); 
 
  238                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/calllog.png"); 
 
  240                 case TSK_CALENDAR_ENTRY:
 
  241                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/calendar.png"); 
 
  243                 case TSK_SPEED_DIAL_ENTRY:
 
  244                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/speeddialentry.png"); 
 
  246                 case TSK_BLUETOOTH_PAIRING:
 
  247                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/bluetooth.png"); 
 
  249                 case TSK_GPS_BOOKMARK:
 
  250                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gpsfav.png"); 
 
  252                 case TSK_GPS_LAST_KNOWN_LOCATION:
 
  253                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gps-lastlocation.png"); 
 
  256                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gps-search.png"); 
 
  259                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/computer.png"); 
 
  261                 case TSK_GPS_TRACKPOINT:
 
  262                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gps_trackpoint.png"); 
 
  265                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gps_trackpoint.png"); 
 
  268                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/mail-icon-16.png"); 
 
  270                 case TSK_ENCRYPTION_SUSPECTED:
 
  271                 case TSK_ENCRYPTION_DETECTED:
 
  272                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/encrypted-file.png"); 
 
  274                 case TSK_EXT_MISMATCH_DETECTED:
 
  275                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/mismatch-16.png"); 
 
  277                 case TSK_INTERESTING_ARTIFACT_HIT:
 
  278                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/interesting_item.png"); 
 
  280                 case TSK_INTERESTING_FILE_HIT:
 
  281                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/interesting_item.png"); 
 
  284                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/installed.png"); 
 
  286                 case TSK_REMOTE_DRIVE:
 
  287                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/drive_network.png"); 
 
  290                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/accounts.png"); 
 
  292                 case TSK_WIFI_NETWORK:
 
  293                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/network-wifi.png"); 
 
  295                 case TSK_WIFI_NETWORK_ADAPTER:
 
  296                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/network-wifi.png"); 
 
  298                 case TSK_SIM_ATTACHED:
 
  299                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/sim_card.png"); 
 
  301                 case TSK_BLUETOOTH_ADAPTER:
 
  302                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/Bluetooth.png"); 
 
  304                 case TSK_DEVICE_INFO:
 
  305                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/devices.png"); 
 
  307                 case TSK_VERIFICATION_FAILED:
 
  308                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/validationFailed.png"); 
 
  311                     logger.log(Level.WARNING, 
"useDataTypeIcon: unhandled artifact type = {0}", dataType); 
 
  312                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/star.png"); 
 
  313                     iconFileName = 
"star.png"; 
 
  314                     iconFilePath = subPath + File.separator + iconFileName;
 
  317         } 
else if (dataType.startsWith(ARTIFACT_TYPE.TSK_ACCOUNT.getDisplayName())) {
 
  325             in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/accounts.png"); 
 
  326             iconFileName = 
"accounts.png"; 
 
  327             iconFilePath = subPath + File.separator + iconFileName;
 
  329             in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/star.png"); 
 
  330             iconFileName = 
"star.png"; 
 
  331             iconFilePath = subPath + File.separator + iconFileName;
 
  335             output = 
new FileOutputStream(iconFilePath);
 
  336             FileUtil.copy(in, output);
 
  339         } 
catch (IOException ex) {
 
  340             logger.log(Level.SEVERE, 
"Failed to extract images for HTML report.", ex); 
 
  342             if (output != null) {
 
  346                 } 
catch (IOException ex) {
 
  352                 } 
catch (IOException ex) {
 
  367     public void startReport(String baseReportDir) {
 
  369         ModuleSettings.setConfigSetting(
"HTMLReport", 
"header", configPanel.getHeader()); 
 
  370         ModuleSettings.setConfigSetting(
"HTMLReport", 
"footer", configPanel.getFooter()); 
 
  375         } 
catch (NoCurrentCaseException ex) {
 
  376             logger.log(Level.SEVERE, 
"Exception while getting open case."); 
 
  380         this.path = baseReportDir; 
 
  381         this.subPath = this.path + HTML_SUBDIR + File.separator;
 
  382         this.thumbsPath = this.subPath + THUMBS_REL_PATH; 
 
  384             FileUtil.createFolder(
new File(this.subPath));
 
  385             FileUtil.createFolder(
new File(this.thumbsPath));
 
  386         } 
catch (IOException ex) {
 
  387             logger.log(Level.SEVERE, 
"Unable to make HTML report folder."); 
 
  400     public void endReport() {
 
  405             } 
catch (IOException ex) {
 
  406                 logger.log(Level.WARNING, 
"Could not close the output writer when ending report.", ex); 
 
  420     public void startDataType(String name, String description) {
 
  421         String title = dataTypeToFileName(name);
 
  423             out = 
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(subPath + title + 
".html"), 
"UTF-8")); 
 
  424         } 
catch (FileNotFoundException ex) {
 
  425             logger.log(Level.SEVERE, 
"File not found: {0}", ex); 
 
  426         } 
catch (UnsupportedEncodingException ex) {
 
  427             logger.log(Level.SEVERE, 
"Unrecognized encoding"); 
 
  431             StringBuilder page = 
new StringBuilder();
 
  432             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") 
 
  433             .append(writePageHeader())
 
  434             .append(
"<div id=\"header\">").append(name).append(
"</div>\n")
 
  435             .append(
"<div id=\"content\">\n"); 
 
  436             if (!description.isEmpty()) {
 
  437                 page.append(
"<p><strong>"); 
 
  438                 page.append(description);
 
  439                 page.append(
"</strong></p>\n"); 
 
  441             out.write(page.toString());
 
  442             currentDataType = name;
 
  444         } 
catch (IOException ex) {
 
  445             logger.log(Level.SEVERE, 
"Failed to write page head: {0}", ex); 
 
  454     public void endDataType() {
 
  455         dataTypes.put(currentDataType, rowCount);
 
  457             StringBuilder builder = 
new StringBuilder();
 
  458             builder.append(writePageFooter());
 
  459             builder.append(
"</div>\n</body>\n</html>\n"); 
 
  460             out.write(builder.toString());
 
  461         } 
catch (IOException ex) {
 
  462             logger.log(Level.SEVERE, 
"Failed to write end of HTML report.", ex); 
 
  468                 } 
catch (IOException ex) {
 
  469                     logger.log(Level.WARNING, 
"Could not close the output writer when ending data type.", ex); 
 
  482     private String writePageHeader() {
 
  483         StringBuilder output = 
new StringBuilder();
 
  484         String pageHeader = configPanel.getHeader();
 
  485         if (pageHeader.isEmpty() == 
false) {
 
  486             output.append(
"<div id=\"pageHeaderFooter\">")
 
  487                     .append(StringEscapeUtils.escapeHtml4(pageHeader))
 
  490         return output.toString();
 
  499     private String writePageFooter() {
 
  500         StringBuilder output = 
new StringBuilder();
 
  501         String pageFooter = configPanel.getFooter();
 
  502         if (pageFooter.isEmpty() == 
false) {
 
  503             output.append(
"<br/><div id=\"pageHeaderFooter\">")
 
  504                     .append(StringEscapeUtils.escapeHtml4(pageFooter))
 
  507         return output.toString();
 
  516     public void startSet(String setName) {
 
  517         StringBuilder set = 
new StringBuilder();
 
  518         set.append(
"<h1><a name=\"").append(setName).append(
"\">").append(setName).append(
"</a></h1>\n"); 
 
  519         set.append(
"<div class=\"keyword_list\">\n"); 
 
  522             out.write(set.toString());
 
  523         } 
catch (IOException ex) {
 
  524             logger.log(Level.SEVERE, 
"Failed to write set: {0}", ex); 
 
  532     public void endSet() {
 
  534             out.write(
"</div>\n"); 
 
  535         } 
catch (IOException ex) {
 
  536             logger.log(Level.SEVERE, 
"Failed to write end of set: {0}", ex); 
 
  546     public void addSetIndex(List<String> sets) {
 
  547         StringBuilder index = 
new StringBuilder();
 
  548         index.append(
"<ul>\n"); 
 
  549         for (String set : sets) {
 
  550             index.append(
"\t<li><a href=\"#").append(set).append(
"\">").append(set).append(
"</a></li>\n"); 
 
  552         index.append(
"</ul>\n"); 
 
  554             out.write(index.toString());
 
  555         } 
catch (IOException ex) {
 
  556             logger.log(Level.SEVERE, 
"Failed to add set index: {0}", ex); 
 
  566     public void addSetElement(String elementName) {
 
  568             out.write(
"<h4>" + elementName + 
"</h4>\n"); 
 
  569         } 
catch (IOException ex) {
 
  570             logger.log(Level.SEVERE, 
"Failed to write set element: {0}", ex); 
 
  580     public void startTable(List<String> titles) {
 
  581         StringBuilder ele = 
new StringBuilder();
 
  582         ele.append(
"<table>\n<thead>\n\t<tr>\n"); 
 
  583         for (String title : titles) {
 
  584             ele.append(
"\t\t<th>").append(title).append(
"</th>\n"); 
 
  586         ele.append(
"\t</tr>\n</thead>\n"); 
 
  589             out.write(ele.toString());
 
  590         } 
catch (IOException ex) {
 
  591             logger.log(Level.SEVERE, 
"Failed to write table start: {0}", ex); 
 
  602     public void startContentTagsTable(List<String> columnHeaders) {
 
  603         StringBuilder htmlOutput = 
new StringBuilder();
 
  604         htmlOutput.append(
"<table>\n<thead>\n\t<tr>\n"); 
 
  607         for (String columnHeader : columnHeaders) {
 
  608             htmlOutput.append(
"\t\t<th>").append(columnHeader).append(
"</th>\n"); 
 
  612         htmlOutput.append(
"\t\t<th></th>\n"); 
 
  614         htmlOutput.append(
"\t</tr>\n</thead>\n"); 
 
  617             out.write(htmlOutput.toString());
 
  618         } 
catch (IOException ex) {
 
  619             logger.log(Level.SEVERE, 
"Failed to write table start: {0}", ex); 
 
  627     public void endTable() {
 
  629             out.write(
"</table>\n"); 
 
  630         } 
catch (IOException ex) {
 
  631             logger.log(Level.SEVERE, 
"Failed to write end of table: {0}", ex); 
 
  642     public void addRow(List<String> row) {
 
  653     private void addRow(List<String> row, 
boolean escapeText) {
 
  654         StringBuilder builder = 
new StringBuilder();
 
  655         builder.append(
"\t<tr>\n"); 
 
  656         for (String cell : row) {
 
  657             String cellText = escapeText ? EscapeUtil.escapeHtml(cell) : cell;
 
  658             builder.append(
"\t\t<td>").append(cellText).append(
"</td>\n"); 
 
  660         builder.append(
"\t</tr>\n"); 
 
  664             out.write(builder.toString());
 
  665         } 
catch (IOException ex) {
 
  666             logger.log(Level.SEVERE, 
"Failed to write row to out.", ex); 
 
  667         } 
catch (NullPointerException ex) {
 
  668             logger.log(Level.SEVERE, 
"Output writer is null. Page was not initialized before writing.", ex); 
 
  682     public void addRowWithTaggedContentHyperlink(List<String> row, ContentTag contentTag) {
 
  683         Content content = contentTag.getContent();
 
  684         if (content instanceof AbstractFile == 
false) {
 
  688         AbstractFile file = (AbstractFile) content;
 
  690         StringBuilder localFileLink = 
new StringBuilder();
 
  693                 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS
 
  694                 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS)) {
 
  695             localFileLink.append(
"<a href=\""); 
 
  697             String localFilePath = saveContent(file, contentTag.getName().getDisplayName());
 
  698             localFileLink.append(localFilePath);
 
  699             localFileLink.append(
"\" target=\"_top\">");
 
  702         StringBuilder builder = 
new StringBuilder();
 
  703         builder.append(
"\t<tr>\n"); 
 
  704         int positionCounter = 0;
 
  705         for (String cell : row) {
 
  707             switch (positionCounter) {
 
  710                     builder.append(
"\t\t<td class=\"left_align_cell\">").append(localFileLink.toString()).append(cell).append(
"</a></td>\n"); 
 
  714                     builder.append(
"\t\t<td class=\"right_align_cell\">").append(cell).append(
"</td>\n"); 
 
  718                     builder.append(
"\t\t<td>").append(cell).append(
"</td>\n"); 
 
  723         builder.append(
"\t</tr>\n"); 
 
  727             out.write(builder.toString());
 
  728         } 
catch (IOException ex) {
 
  729             logger.log(Level.SEVERE, 
"Failed to write row to out.", ex); 
 
  730         } 
catch (NullPointerException ex) {
 
  731             logger.log(Level.SEVERE, 
"Output writer is null. Page was not initialized before writing.", ex); 
 
  740     public void addThumbnailRows(Set<Content> images) {
 
  741         List<String> currentRow = 
new ArrayList<>();
 
  744         for (Content content : images) {
 
  745             if (currentRow.size() == THUMBNAIL_COLUMNS) {
 
  746                 addRow(currentRow, 
false);
 
  750             if (totalCount == MAX_THUMBS_PER_PAGE) {
 
  754                 rowCount = totalCount;
 
  759                 startDataType(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.addThumbRows.dataType.title", pages),
 
  760                         NbBundle.getMessage(
this.getClass(), 
"ReportHTML.addThumbRows.dataType.msg"));
 
  761                 List<String> emptyHeaders = 
new ArrayList<>();
 
  762                 for (
int i = 0; i < THUMBNAIL_COLUMNS; i++) {
 
  763                     emptyHeaders.add(
"");
 
  765                 startTable(emptyHeaders);
 
  768             if (failsContentCheck(content)) {
 
  772             AbstractFile file = (AbstractFile) content;
 
  775             String thumbnailPath = prepareThumbnail(file);
 
  776             if (thumbnailPath == null) {
 
  779             String contentPath = saveContent(file, 
"thumbs_fullsize"); 
 
  782                 nameInImage = file.getUniquePath();
 
  783             } 
catch (TskCoreException ex) {
 
  784                 nameInImage = file.getName();
 
  787             StringBuilder linkToThumbnail = 
new StringBuilder();
 
  788             linkToThumbnail.append(
"<div id='thumbnail_link'>");
 
  789             linkToThumbnail.append(
"<a href=\""); 
 
  790             linkToThumbnail.append(contentPath);
 
  791             linkToThumbnail.append(
"\" target=\"_top\">");
 
  792             linkToThumbnail.append(
"<img src=\"").append(thumbnailPath).append(
"\" title=\"").append(nameInImage).append(
"\"/>"); 
 
  793             linkToThumbnail.append(
"</a><br>"); 
 
  794             linkToThumbnail.append(file.getName()).append(
"<br>"); 
 
  796             Services services = currentCase.getServices();
 
  797             TagsManager tagsManager = services.getTagsManager();
 
  799                 List<ContentTag> tags = tagsManager.getContentTagsByContent(content);
 
  800                 if (tags.size() > 0) {
 
  801                     linkToThumbnail.append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.thumbLink.tags"));
 
  803                 for (
int i = 0; i < tags.size(); i++) {
 
  804                     ContentTag tag = tags.get(i);
 
  805                     String notableString = tag.getName().getKnownStatus() == TskData.FileKnown.BAD ? TagsManager.getNotableTagLabel() : 
"";
 
  806                     linkToThumbnail.append(tag.getName().getDisplayName()).append(notableString);
 
  807                     if (i != tags.size() - 1) {
 
  808                         linkToThumbnail.append(
", ");
 
  811             } 
catch (TskCoreException ex) {
 
  812                 logger.log(Level.WARNING, 
"Could not find get tags for file.", ex); 
 
  814             linkToThumbnail.append(
"</div>");
 
  815             currentRow.add(linkToThumbnail.toString());
 
  820         if (currentRow.isEmpty() == 
false) {
 
  821             int extraCells = THUMBNAIL_COLUMNS - currentRow.size();
 
  822             for (
int i = 0; i < extraCells; i++) {
 
  826             addRow(currentRow, 
false);
 
  830         rowCount = totalCount;
 
  833     private boolean failsContentCheck(Content c) {
 
  834         if (c instanceof AbstractFile == 
false) {
 
  837         AbstractFile file = (AbstractFile) c;
 
  839                 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS
 
  840                 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS;
 
  852     public String saveContent(AbstractFile file, String dirName) {
 
  857         StringBuilder localFilePath = 
new StringBuilder();  
 
  859         localFilePath.append(subPath);
 
  860         localFilePath.append(dirName2);
 
  861         File localFileFolder = 
new File(localFilePath.toString());
 
  862         if (!localFileFolder.exists()) {
 
  863             localFileFolder.mkdirs();
 
  874         String objectIdSuffix = 
"_" + file.getId();
 
  875         int lastDotIndex = fileName.lastIndexOf(
".");
 
  876         if (lastDotIndex != -1 && lastDotIndex != 0) {
 
  878             fileName = fileName.substring(0, lastDotIndex) + objectIdSuffix + fileName.substring(lastDotIndex, fileName.length());
 
  882             fileName += objectIdSuffix;
 
  884         localFilePath.append(File.separator);
 
  885         localFilePath.append(fileName);
 
  889         File localFile = 
new File(localFilePath.toString());
 
  890         if (!localFile.exists()) {
 
  891             ExtractFscContentVisitor.extract(file, localFile, null, null);
 
  895         return localFilePath.toString().substring(subPath.length());
 
  906     public String dateToString(
long date) {
 
  907         SimpleDateFormat sdf = 
new java.text.SimpleDateFormat(
"yyyy/MM/dd HH:mm:ss");
 
  908         return sdf.format(
new java.util.Date(date * 1000));
 
  912     public String getRelativeFilePath() {
 
  913         return "report.html"; 
 
  917     public String getName() {
 
  918         return NbBundle.getMessage(this.getClass(), 
"ReportHTML.getName.text");
 
  922     public String getDescription() {
 
  923         return NbBundle.getMessage(this.getClass(), 
"ReportHTML.getDesc.text");
 
  929     private void writeCss() {
 
  930         Writer cssOut = null;
 
  932             cssOut = 
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(subPath + 
"index.css"), 
"UTF-8")); 
 
  933             String css = 
"body {margin: 0px; padding: 0px; background: #FFFFFF; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353;}\n" 
  935                     "#content {padding: 30px;}\n" 
  937                     "#header {width:100%; padding: 10px; line-height: 25px; background: #07A; color: #FFF; font-size: 20px;}\n" 
  939                     "#pageHeaderFooter {width: 100%; padding: 10px; line-height: 25px; text-align: center; font-size: 20px;}\n" 
  941                     "h1 {font-size: 20px; font-weight: normal; color: #07A; padding: 0 0 7px 0; margin-top: 25px; border-bottom: 1px solid #D6D6D6;}\n" 
  943                     "h2 {font-size: 20px; font-weight: bolder; color: #07A;}\n" 
  945                     "h3 {font-size: 16px; color: #07A;}\n" 
  947                     "h4 {background: #07A; color: #FFF; font-size: 16px; margin: 0 0 0 25px; padding: 0; padding-left: 15px;}\n" 
  949                     "ul.nav {list-style-type: none; line-height: 35px; padding: 0px; margin-left: 15px;}\n" 
  951                     "ul li a {font-size: 14px; color: #444; text-decoration: none; padding-left: 25px;}\n" 
  953                     "ul li a:hover {text-decoration: underline;}\n" 
  955                     "p {margin: 0 0 20px 0;}\n" 
  957                     "table {white-space:nowrap; min-width: 700px; padding: 2; margin: 0; border-collapse: collapse; border-bottom: 2px solid #e5e5e5;}\n" 
  959                     ".keyword_list table {margin: 0 0 25px 25px; border-bottom: 2px solid #dedede;}\n" 
  961                     "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" 
  963                     "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" 
  965                     "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" 
  967                     "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" 
  969                     "table tr:nth-child(even) td {background: #f3f3f3;}\n" 
  971                     "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;}";
 
  973         } 
catch (FileNotFoundException ex) {
 
  974             logger.log(Level.SEVERE, 
"Could not find index.css file to write to.", ex); 
 
  975         } 
catch (UnsupportedEncodingException ex) {
 
  976             logger.log(Level.SEVERE, 
"Did not recognize encoding when writing index.css.", ex); 
 
  977         } 
catch (IOException ex) {
 
  978             logger.log(Level.SEVERE, 
"Error creating Writer for index.css.", ex); 
 
  981                 if (cssOut != null) {
 
  985             } 
catch (IOException ex) {
 
  993     private void writeIndex() {
 
  994         Writer indexOut = null;
 
  995         String indexFilePath = path + 
"report.html"; 
 
  998             openCase = Case.getCurrentCaseThrows();
 
  999         } 
catch (NoCurrentCaseException ex) {
 
 1000             logger.log(Level.SEVERE, 
"Exception while getting open case.", ex); 
 
 1004             indexOut = 
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(indexFilePath), 
"UTF-8")); 
 
 1005             StringBuilder index = 
new StringBuilder();
 
 1006             final String reportTitle = reportBranding.getReportTitle();
 
 1007             String iconPath = reportBranding.getAgencyLogoPath();
 
 1008             if (iconPath == null) {
 
 1010                 iconPath = HTML_SUBDIR + 
"favicon.ico";
 
 1012                 iconPath = Paths.get(reportBranding.getAgencyLogoPath()).getFileName().toString(); 
 
 1014             index.append(
"<head>\n<title>").append(reportTitle).append(
" ").append(
 
 1015                     NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeIndex.title", currentCase.getDisplayName())).append(
 
 1017             index.append(
"<link rel=\"icon\" type=\"image/ico\" href=\"")
 
 1018                     .append(iconPath).append(
"\" />\n"); 
 
 1019             index.append(
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"); 
 
 1020             index.append(
"</head>\n"); 
 
 1021             index.append(
"<frameset cols=\"350px,*\">\n"); 
 
 1022             index.append(
"<frame src=\"" + HTML_SUBDIR).append(File.separator).append(
"nav.html\" name=\"nav\">\n"); 
 
 1023             index.append(
"<frame src=\"" + HTML_SUBDIR).append(File.separator).append(
"summary.html\" name=\"content\">\n"); 
 
 1024             index.append(
"<noframes>").append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeIndex.noFrames.msg")).append(
"<br />\n"); 
 
 1025             index.append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeIndex.noFrames.seeNav")).append(
"<br />\n"); 
 
 1026             index.append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeIndex.seeSum")).append(
"</noframes>\n"); 
 
 1027             index.append(
"</frameset>\n"); 
 
 1028             index.append(
"</html>"); 
 
 1029             indexOut.write(index.toString());
 
 1030             openCase.addReport(indexFilePath, NbBundle.getMessage(
this.getClass(),
 
 1031                     "ReportHTML.writeIndex.srcModuleName.text"), 
"");
 
 1032         } 
catch (IOException ex) {
 
 1033             logger.log(Level.SEVERE, 
"Error creating Writer for report.html: {0}", ex); 
 
 1034         } 
catch (TskCoreException ex) {
 
 1035             String errorMessage = String.format(
"Error adding %s to case as a report", indexFilePath); 
 
 1036             logger.log(Level.SEVERE, errorMessage, ex);
 
 1039                 if (indexOut != null) {
 
 1043             } 
catch (IOException ex) {
 
 1051     private void writeNav() {
 
 1052         Writer navOut = null;
 
 1054             navOut = 
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(subPath + 
"nav.html"), 
"UTF-8")); 
 
 1055             StringBuilder nav = 
new StringBuilder();
 
 1056             nav.append(
"<html>\n<head>\n\t<title>").append( 
 
 1057                     NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeNav.title"))
 
 1058                     .append(
"</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"index.css\" />\n"); 
 
 1059             nav.append(
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n</head>\n<body>\n"); 
 
 1060             nav.append(
"<div id=\"content\">\n<h1>").append( 
 
 1061                     NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeNav.h1")).append(
"</h1>\n"); 
 
 1062             nav.append(
"<ul class=\"nav\">\n"); 
 
 1063             nav.append(
"<li style=\"background: url(summary.png) left center no-repeat;\"><a href=\"summary.html\" target=\"content\">") 
 
 1064                     .append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeNav.summary")).append(
"</a></li>\n"); 
 
 1066             for (String dataType : dataTypes.keySet()) {
 
 1067                 String dataTypeEsc = dataTypeToFileName(dataType);
 
 1068                 String iconFileName = useDataTypeIcon(dataType);
 
 1069                 nav.append(
"<li style=\"background: url('").append(iconFileName) 
 
 1070                         .append(
"') left center no-repeat;\"><a href=\"") 
 
 1071                         .append(dataTypeEsc).append(
".html\" target=\"content\">") 
 
 1072                         .append(dataType).append(
" (").append(dataTypes.get(dataType))
 
 1073                         .append(
")</a></li>\n"); 
 
 1075             nav.append(
"</ul>\n"); 
 
 1076             nav.append(
"</div>\n</body>\n</html>"); 
 
 1077             navOut.write(nav.toString());
 
 1078         } 
catch (IOException ex) {
 
 1079             logger.log(Level.SEVERE, 
"Failed to write end of report navigation menu: {0}", ex); 
 
 1081             if (navOut != null) {
 
 1085                 } 
catch (IOException ex) {
 
 1086                     logger.log(Level.WARNING, 
"Could not close navigation out writer."); 
 
 1091         InputStream in = null;
 
 1092         OutputStream output = null;
 
 1096             String generatorLogoPath = reportBranding.getGeneratorLogoPath();
 
 1097             if (generatorLogoPath != null && !generatorLogoPath.isEmpty()) {
 
 1098                 File from = 
new File(generatorLogoPath);
 
 1099                 File to = 
new File(subPath);
 
 1100                 FileUtil.copyFile(FileUtil.toFileObject(from), FileUtil.toFileObject(to), 
"generator_logo"); 
 
 1103             String agencyLogoPath = reportBranding.getAgencyLogoPath();
 
 1104             if (agencyLogoPath != null && !agencyLogoPath.isEmpty()) {
 
 1105                 Path destinationPath = Paths.get(subPath);
 
 1106                 Files.copy(Files.newInputStream(Paths.get(agencyLogoPath)), destinationPath.resolve(Paths.get(agencyLogoPath).getFileName())); 
 
 1109             in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/favicon.ico"); 
 
 1110             output = 
new FileOutputStream(
new File(subPath + 
"favicon.ico"));
 
 1111             FileUtil.copy(in, output);
 
 1115             in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/summary.png"); 
 
 1116             output = 
new FileOutputStream(
new File(subPath + 
"summary.png"));
 
 1117             FileUtil.copy(in, output);
 
 1121         } 
catch (IOException ex) {
 
 1122             logger.log(Level.SEVERE, 
"Failed to extract images for HTML report.", ex); 
 
 1124             if (output != null) {
 
 1128                 } 
catch (IOException ex) {
 
 1134                 } 
catch (IOException ex) {
 
 1143     private void writeSummary() {
 
 1144         Writer output = null;
 
 1146             output = 
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(subPath + 
"summary.html"), 
"UTF-8")); 
 
 1147             StringBuilder head = 
new StringBuilder();
 
 1148             head.append(
"<html>\n<head>\n<title>").append( 
 
 1149                     NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.title")).append(
"</title>\n"); 
 
 1150             head.append(
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"); 
 
 1151             head.append(
"<style type=\"text/css\">\n"); 
 
 1152             head.append(
"#pageHeaderFooter {width: 100%; padding: 10px; line-height: 25px; text-align: center; font-size: 20px;}\n"); 
 
 1153             head.append(
"body { padding: 0px; margin: 0px; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353; }\n"); 
 
 1154             head.append(
"#wrapper { width: 90%; margin: 0px auto; margin-top: 35px; }\n"); 
 
 1155             head.append(
"h1 { color: #07A; font-size: 36px; line-height: 42px; font-weight: normal; margin: 0px; border-bottom: 1px solid #81B9DB; }\n"); 
 
 1156             head.append(
"h1 span { color: #F00; display: block; font-size: 16px; font-weight: bold; line-height: 22px;}\n"); 
 
 1157             head.append(
"h2 { padding: 0 0 3px 0; margin: 0px; color: #07A; font-weight: normal; border-bottom: 1px dotted #81B9DB; }\n"); 
 
 1158             head.append(
"h3 { padding: 5 0 3px 0; margin: 0px; color: #07A; font-weight: normal; }\n");
 
 1159             head.append(
"table td { padding: 5px 25px 5px 0px; vertical-align:top;}\n"); 
 
 1160             head.append(
"p.subheadding { padding: 0px; margin: 0px; font-size: 11px; color: #B5B5B5; }\n"); 
 
 1161             head.append(
".title { width: 660px; margin-bottom: 50px; }\n"); 
 
 1162             head.append(
".left { float: left; width: 250px; margin-top: 20px; text-align: center; }\n"); 
 
 1163             head.append(
".left img { max-width: 250px; max-height: 250px; min-width: 200px; min-height: 200px; }\n"); 
 
 1164             head.append(
".right { float: right; width: 385px; margin-top: 25px; font-size: 14px; }\n"); 
 
 1165             head.append(
".clear { clear: both; }\n"); 
 
 1166             head.append(
".info { padding: 10px 0;}\n");
 
 1167             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"); 
 
 1168             head.append(
".info table { margin: 10px 25px 10px 25px; }\n"); 
 
 1169             head.append(
"ul {padding: 0;margin: 0;list-style-type: none;}");
 
 1170             head.append(
"li {padding-bottom: 5px;}");
 
 1171             head.append(
"</style>\n"); 
 
 1172             head.append(
"</head>\n<body>\n"); 
 
 1173             output.write(head.toString());
 
 1175             DateFormat datetimeFormat = 
new SimpleDateFormat(
"yyyy/MM/dd HH:mm:ss");
 
 1176             Date date = 
new Date();
 
 1177             String datetime = datetimeFormat.format(date);
 
 1179             StringBuilder summary = 
new StringBuilder();
 
 1180             boolean running = 
false;
 
 1181             if (IngestManager.getInstance().isIngestRunning()) {
 
 1184             SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
 
 1185             List<IngestJobInfo> ingestJobs = skCase.getIngestJobs();
 
 1186             final String reportTitle = reportBranding.getReportTitle();
 
 1187             final String reportFooter = reportBranding.getReportFooter();
 
 1188             final boolean generatorLogoSet = reportBranding.getGeneratorLogoPath() != null && !reportBranding.getGeneratorLogoPath().isEmpty();
 
 1190             summary.append(
"<div id=\"wrapper\">\n"); 
 
 1191             summary.append(writePageHeader());
 
 1192             summary.append(
"<h1>").append(reportTitle) 
 
 1193                     .append(running ? NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.warningMsg") : 
"")
 
 1195             summary.append(
"<p class=\"subheadding\">").append( 
 
 1196                     NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.reportGenOn.text", datetime)).append(
"</p>\n"); 
 
 1197             summary.append(
"<div class=\"title\">\n"); 
 
 1198             summary.append(writeSummaryCaseDetails());
 
 1199             summary.append(writeSummaryImageInfo());
 
 1200             summary.append(writeSummarySoftwareInfo(skCase, ingestJobs));
 
 1201             summary.append(writeSummaryIngestHistoryInfo(skCase, ingestJobs));
 
 1202             if (generatorLogoSet) {
 
 1203                 summary.append(
"<div class=\"left\">\n"); 
 
 1204                 summary.append(
"<img src=\"generator_logo.png\" />\n"); 
 
 1205                 summary.append(
"</div>\n"); 
 
 1207             summary.append(
"<div class=\"clear\"></div>\n"); 
 
 1208             if (reportFooter != null) {
 
 1209                 summary.append(
"<p class=\"subheadding\">").append(reportFooter).append(
"</p>\n"); 
 
 1211             summary.append(
"</div>\n"); 
 
 1212             summary.append(writePageFooter());
 
 1213             summary.append(
"</body></html>"); 
 
 1214             output.write(summary.toString());
 
 1215         } 
catch (FileNotFoundException ex) {
 
 1216             logger.log(Level.SEVERE, 
"Could not find summary.html file to write to."); 
 
 1217         } 
catch (UnsupportedEncodingException ex) {
 
 1218             logger.log(Level.SEVERE, 
"Did not recognize encoding when writing summary.hmtl."); 
 
 1219         } 
catch (IOException ex) {
 
 1220             logger.log(Level.SEVERE, 
"Error creating Writer for summary.html."); 
 
 1221         } 
catch (NoCurrentCaseException | TskCoreException ex) {
 
 1222             logger.log(Level.WARNING, 
"Unable to get current sleuthkit Case for the HTML report.");
 
 1225                 if (output != null) {
 
 1229             } 
catch (IOException ex) {
 
 1235         "ReportHTML.writeSum.case=Case:",
 
 1236         "ReportHTML.writeSum.caseNumber=Case Number:",
 
 1237         "ReportHTML.writeSum.caseNumImages=Number of Images:",
 
 1238         "ReportHTML.writeSum.caseNotes=Notes:",
 
 1239         "ReportHTML.writeSum.examiner=Examiner:" 
 1246     private StringBuilder writeSummaryCaseDetails() {
 
 1247         StringBuilder summary = 
new StringBuilder();
 
 1249         final boolean agencyLogoSet = reportBranding.getAgencyLogoPath() != null && !reportBranding.getAgencyLogoPath().isEmpty();
 
 1252         String caseName = currentCase.getDisplayName();
 
 1253         String caseNumber = currentCase.getNumber();
 
 1256             imagecount = currentCase.getDataSources().size();
 
 1257         } 
catch (TskCoreException ex) {
 
 1260         String caseNotes = currentCase.getCaseNotes();
 
 1263         String examinerName = currentCase.getExaminer();
 
 1266         summary.append(
"<div class=\"title\">\n"); 
 
 1267         if (agencyLogoSet) {
 
 1268             summary.append(
"<div class=\"left\">\n"); 
 
 1269             summary.append(
"<img src=\"");
 
 1270             summary.append(Paths.get(reportBranding.getAgencyLogoPath()).getFileName().toString());
 
 1271             summary.append(
"\" />\n"); 
 
 1272             summary.append(
"</div>\n"); 
 
 1274         final String align = agencyLogoSet ? 
"right" : 
"left"; 
 
 1275         summary.append(
"<div class=\"").append(align).append(
"\">\n"); 
 
 1276         summary.append(
"<table>\n"); 
 
 1279         summary.append(
"<tr><td>").append(Bundle.ReportHTML_writeSum_case()).append(
"</td><td>") 
 
 1280                 .append(formatHtmlString(caseName)).append(
"</td></tr>\n"); 
 
 1282         if (!caseNumber.isEmpty()) {
 
 1283             summary.append(
"<tr><td>").append(Bundle.ReportHTML_writeSum_caseNumber()).append(
"</td><td>") 
 
 1284                     .append(formatHtmlString(caseNumber)).append(
"</td></tr>\n"); 
 
 1287         summary.append(
"<tr><td>").append(Bundle.ReportHTML_writeSum_caseNumImages()).append(
"</td><td>") 
 
 1288                 .append(imagecount).append(
"</td></tr>\n"); 
 
 1290         if (!caseNotes.isEmpty()) {
 
 1291             summary.append(
"<tr><td>").append(Bundle.ReportHTML_writeSum_caseNotes()).append(
"</td><td>") 
 
 1292                     .append(formatHtmlString(caseNotes)).append(
"</td></tr>\n"); 
 
 1296         if (!examinerName.isEmpty()) {
 
 1297             summary.append(
"<tr><td>").append(Bundle.ReportHTML_writeSum_examiner()).append(
"</td><td>") 
 
 1298                     .append(formatHtmlString(examinerName)).append(
"</td></tr>\n"); 
 
 1302         summary.append(
"</table>\n"); 
 
 1303         summary.append(
"</div>\n"); 
 
 1304         summary.append(
"<div class=\"clear\"></div>\n"); 
 
 1305         summary.append(
"</div>\n"); 
 
 1314     private StringBuilder writeSummaryImageInfo() {
 
 1315         StringBuilder summary = 
new StringBuilder();
 
 1316         summary.append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.imageInfoHeading"));
 
 1317         summary.append(
"<div class=\"info\">\n"); 
 
 1319             for (Content c : currentCase.getDataSources()) {
 
 1320                 summary.append(
"<p>").append(c.getName()).append(
"</p>\n"); 
 
 1321                 if (c instanceof Image) {
 
 1322                     Image img = (Image) c;
 
 1324                     summary.append(
"<table>\n"); 
 
 1325                     summary.append(
"<tr><td>").append( 
 
 1326                             NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.timezone"))
 
 1327                             .append(
"</td><td>").append(img.getTimeZone()).append(
"</td></tr>\n"); 
 
 1328                     for (String imgPath : img.getPaths()) {
 
 1329                         summary.append(
"<tr><td>").append( 
 
 1330                                 NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.path"))
 
 1331                                 .append(
"</td><td>").append(imgPath).append(
"</td></tr>\n"); 
 
 1333                     summary.append(
"</table>\n"); 
 
 1336         } 
catch (TskCoreException ex) {
 
 1337             logger.log(Level.WARNING, 
"Unable to get image information for the HTML report."); 
 
 1339         summary.append(
"</div>\n"); 
 
 1348     private StringBuilder writeSummarySoftwareInfo(SleuthkitCase skCase, List<IngestJobInfo> ingestJobs) {
 
 1349         StringBuilder summary = 
new StringBuilder();
 
 1350         summary.append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.softwareInfoHeading"));
 
 1351         summary.append(
"<div class=\"info\">\n");
 
 1352         summary.append(
"<table>\n");
 
 1353         summary.append(
"<tr><td>").append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.autopsyVersion"))
 
 1354                 .append(
"</td><td>").append(Version.getVersion()).append(
"</td></tr>\n");
 
 1355         Map<Long, IngestModuleInfo> moduleInfoHashMap = 
new HashMap<>();
 
 1356         for (IngestJobInfo ingestJob : ingestJobs) {
 
 1357             List<IngestModuleInfo> ingestModules = ingestJob.getIngestModuleInfo();
 
 1358             for (IngestModuleInfo ingestModule : ingestModules) {
 
 1359                 if (!moduleInfoHashMap.containsKey(ingestModule.getIngestModuleId())) {
 
 1360                     moduleInfoHashMap.put(ingestModule.getIngestModuleId(), ingestModule);
 
 1364         TreeMap<String, String> modules = 
new TreeMap<>();
 
 1365         for (IngestModuleInfo moduleinfo : moduleInfoHashMap.values()) {
 
 1366             modules.put(moduleinfo.getDisplayName(), moduleinfo.getVersion());
 
 1368         for (Map.Entry<String, String> module : modules.entrySet()) {
 
 1369             summary.append(
"<tr><td>").append(module.getKey()).append(
" Module:")
 
 1370                     .append(
"</td><td>").append(module.getValue()).append(
"</td></tr>\n");
 
 1372         summary.append(
"</table>\n");
 
 1373         summary.append(
"</div>\n");
 
 1374         summary.append(
"<div class=\"clear\"></div>\n"); 
 
 1383     private StringBuilder writeSummaryIngestHistoryInfo(SleuthkitCase skCase, List<IngestJobInfo> ingestJobs) {
 
 1384         StringBuilder summary = 
new StringBuilder();
 
 1386             summary.append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.ingestHistoryHeading"));
 
 1387             summary.append(
"<div class=\"info\">\n");
 
 1390             for (IngestJobInfo ingestJob : ingestJobs) {
 
 1391                 summary.append(
"<h3>Job ").append(jobnumber).append(
":</h3>\n");
 
 1392                 summary.append(
"<table>\n");
 
 1393                 summary.append(
"<tr><td>").append(
"Data Source:")
 
 1394                         .append(
"</td><td>").append(skCase.getContentById(ingestJob.getObjectId()).getName()).append(
"</td></tr>\n");
 
 1395                 summary.append(
"<tr><td>").append(
"Status:")
 
 1396                         .append(
"</td><td>").append(ingestJob.getStatus()).append(
"</td></tr>\n");
 
 1397                 summary.append(
"<tr><td>").append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.modulesEnabledHeading"))
 
 1398                         .append(
"</td><td>");
 
 1399                 List<IngestModuleInfo> ingestModules = ingestJob.getIngestModuleInfo();
 
 1400                 summary.append(
"<ul>\n");
 
 1401                 for (IngestModuleInfo ingestModule : ingestModules) {
 
 1402                     summary.append(
"<li>").append(ingestModule.getDisplayName()).append(
"</li>");
 
 1404                 summary.append(
"</ul>\n");
 
 1406                 summary.append(
"</td></tr>\n");
 
 1407                 summary.append(
"</table>\n");
 
 1409             summary.append(
"</div>\n");
 
 1410         } 
catch (TskCoreException ex) {
 
 1411             logger.log(Level.WARNING, 
"Unable to get ingest jobs for the HTML report.");
 
 1424     private String prepareThumbnail(AbstractFile file) {
 
 1425         BufferedImage bufferedThumb = ImageUtils.getThumbnail(file, ImageUtils.ICON_SIZE_MEDIUM);
 
 1433         File thumbFile = Paths.get(thumbsPath, fileName + 
".png").toFile();
 
 1434         if (bufferedThumb == null) {
 
 1438             ImageIO.write(bufferedThumb, 
"png", thumbFile);
 
 1439         } 
catch (IOException ex) {
 
 1440             logger.log(Level.WARNING, 
"Failed to write thumb file to report directory.", ex); 
 
 1443         if (thumbFile.exists()
 
 1447         return THUMBS_REL_PATH
 
 1448                 + thumbFile.getName();
 
 1459     private String formatHtmlString(String text) {
 
 1460         String formattedString = StringEscapeUtils.escapeHtml4(text);
 
 1461         return formattedString.replaceAll(
"(\r\n|\r|\n|\n\r)", 
"<br>");
 
static String escapeFileName(String fileName)