23 package org.sleuthkit.autopsy.report;
 
   25 import java.io.BufferedWriter;
 
   27 import java.io.FileNotFoundException;
 
   28 import java.io.FileOutputStream;
 
   29 import java.io.IOException;
 
   30 import java.io.InputStream;
 
   31 import java.io.OutputStream;
 
   32 import java.io.OutputStreamWriter;
 
   33 import java.io.UnsupportedEncodingException;
 
   34 import java.io.Writer;
 
   35 import java.text.DateFormat;
 
   36 import java.text.SimpleDateFormat;
 
   37 import java.util.ArrayList;
 
   38 import java.util.Date;
 
   39 import java.util.List;
 
   41 import java.util.TreeMap;
 
   42 import java.util.logging.Level;
 
   43 import org.openide.filesystems.FileObject;
 
   44 import org.openide.filesystems.FileUtil;
 
   45 import org.openide.util.NbBundle;
 
   63 class ReportHTML 
implements TableReportModule {
 
   65     private static final Logger logger = Logger.getLogger(ReportHTML.class.getName());
 
   66     private static final String THUMBS_REL_PATH = 
"thumbs" + File.separator; 
 
   67     private static ReportHTML instance;
 
   68     private static final int MAX_THUMBS_PER_PAGE = 1000;
 
   69     private Case currentCase;
 
   70     private SleuthkitCase skCase;
 
   71     static Integer THUMBNAIL_COLUMNS = 5;
 
   73     private Map<String, Integer> dataTypes;
 
   75     private String thumbsPath;
 
   76     private String currentDataType; 
 
   77     private Integer rowCount;       
 
   80     private final ReportBranding reportBranding;
 
   83     public static synchronized ReportHTML getDefault() {
 
   84         if (instance == null) {
 
   85             instance = 
new ReportHTML();
 
   91     private ReportHTML() {
 
   92         reportBranding = 
new ReportBranding();
 
   96     private void refresh() {
 
   97         currentCase = Case.getCurrentCase();
 
   98         skCase = currentCase.getSleuthkitCase();
 
  100         dataTypes = 
new TreeMap<>();
 
  104         currentDataType = 
"";
 
  110             } 
catch (IOException ex) {
 
  122     private String dataTypeToFileName(String dataType) {
 
  126         fileName = fileName.replaceAll(
" ", 
"_");
 
  135     private String useDataTypeIcon(String dataType) {
 
  139         OutputStream output = null;
 
  141         logger.log(Level.INFO, 
"useDataTypeIcon: dataType = {0}", dataType); 
 
  144         BlackboardArtifact.ARTIFACT_TYPE artifactType = null;
 
  145         for (ARTIFACT_TYPE v : ARTIFACT_TYPE.values()) {
 
  146             if (v.getDisplayName().equals(dataType)) {
 
  151         if (null != artifactType) {
 
  153             iconFileName = dataTypeToFileName(artifactType.getDisplayName()) + 
".png"; 
 
  154             iconFilePath = path + File.separator + iconFileName;
 
  157             switch (artifactType) {
 
  158                 case TSK_WEB_BOOKMARK:
 
  159                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/bookmarks.png"); 
 
  162                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/cookies.png"); 
 
  164                 case TSK_WEB_HISTORY:
 
  165                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/history.png"); 
 
  167                 case TSK_WEB_DOWNLOAD:
 
  168                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/downloads.png"); 
 
  170                 case TSK_RECENT_OBJECT:
 
  171                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/recent.png"); 
 
  173                 case TSK_INSTALLED_PROG:
 
  174                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/installed.png"); 
 
  176                 case TSK_KEYWORD_HIT:
 
  177                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/keywords.png"); 
 
  179                 case TSK_HASHSET_HIT:
 
  180                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/hash.png"); 
 
  182                 case TSK_DEVICE_ATTACHED:
 
  183                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/devices.png"); 
 
  185                 case TSK_WEB_SEARCH_QUERY:
 
  186                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/search.png"); 
 
  188                 case TSK_METADATA_EXIF:
 
  189                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/exif.png"); 
 
  192                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/userbookmarks.png"); 
 
  194                 case TSK_TAG_ARTIFACT:
 
  195                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/userbookmarks.png"); 
 
  197                 case TSK_SERVICE_ACCOUNT:
 
  198                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/account-icon-16.png"); 
 
  201                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/contact.png"); 
 
  204                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/message.png"); 
 
  207                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/calllog.png"); 
 
  209                 case TSK_CALENDAR_ENTRY:
 
  210                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/calendar.png"); 
 
  212                 case TSK_SPEED_DIAL_ENTRY:
 
  213                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/speeddialentry.png"); 
 
  215                 case TSK_BLUETOOTH_PAIRING:
 
  216                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/bluetooth.png"); 
 
  218                 case TSK_GPS_BOOKMARK:
 
  219                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gpsfav.png"); 
 
  221                 case TSK_GPS_LAST_KNOWN_LOCATION:
 
  222                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gps-lastlocation.png"); 
 
  225                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gps-search.png"); 
 
  228                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/computer.png"); 
 
  230                 case TSK_GPS_TRACKPOINT:
 
  231                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gps_trackpoint.png"); 
 
  234                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gps_trackpoint.png"); 
 
  237                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/mail-icon-16.png"); 
 
  239                 case TSK_ENCRYPTION_DETECTED:
 
  240                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/encrypted-file.png"); 
 
  242                 case TSK_EXT_MISMATCH_DETECTED:
 
  243                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/mismatch-16.png"); 
 
  245                 case TSK_INTERESTING_ARTIFACT_HIT:
 
  246                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/interesting_item.png"); 
 
  248                 case TSK_INTERESTING_FILE_HIT:
 
  249                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/interesting_item.png"); 
 
  252                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/installed.png"); 
 
  254                 case TSK_REMOTE_DRIVE:
 
  255                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/drive_network.png"); 
 
  258                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/accounts.png"); 
 
  261                     logger.log(Level.WARNING, 
"useDataTypeIcon: unhandled artifact type = " + dataType); 
 
  262                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/star.png"); 
 
  263                     iconFileName = 
"star.png"; 
 
  264                     iconFilePath = path + File.separator + iconFileName;
 
  267         } 
else if (dataType.startsWith(ARTIFACT_TYPE.TSK_ACCOUNT.getDisplayName())) {
 
  274             in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/accounts.png"); 
 
  275             iconFileName = 
"accounts.png"; 
 
  276             iconFilePath = path + File.separator + iconFileName;
 
  278             in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/star.png"); 
 
  279             iconFileName = 
"star.png"; 
 
  280             iconFilePath = path + File.separator + iconFileName;
 
  284             output = 
new FileOutputStream(iconFilePath);
 
  285             FileUtil.copy(in, output);
 
  288         } 
catch (IOException ex) {
 
  289             logger.log(Level.SEVERE, 
"Failed to extract images for HTML report.", ex); 
 
  291             if (output != null) {
 
  295                 } 
catch (IOException ex) {
 
  301                 } 
catch (IOException ex) {
 
  316     public void startReport(String baseReportDir) {
 
  320         this.path = baseReportDir + 
"HTML Report" + File.separator; 
 
  321         this.thumbsPath = this.path + 
"thumbs" + File.separator; 
 
  323             FileUtil.createFolder(
new File(this.path));
 
  324             FileUtil.createFolder(
new File(this.thumbsPath));
 
  325         } 
catch (IOException ex) {
 
  326             logger.log(Level.SEVERE, 
"Unable to make HTML report folder."); 
 
  339     public void endReport() {
 
  344             } 
catch (IOException ex) {
 
  345                 logger.log(Level.WARNING, 
"Could not close the output writer when ending report.", ex); 
 
  359     public void startDataType(String name, String description) {
 
  360         String title = dataTypeToFileName(name);
 
  362             out = 
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(path + title + 
".html"), 
"UTF-8")); 
 
  363         } 
catch (FileNotFoundException ex) {
 
  364             logger.log(Level.SEVERE, 
"File not found: {0}", ex); 
 
  365         } 
catch (UnsupportedEncodingException ex) {
 
  366             logger.log(Level.SEVERE, 
"Unrecognized encoding"); 
 
  370             StringBuilder page = 
new StringBuilder();
 
  371             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"); 
 
  372             page.append(
"<div id=\"header\">").append(name).append(
"</div>\n<div id=\"content\">\n"); 
 
  373             if (!description.isEmpty()) {
 
  374                 page.append(
"<p><strong>"); 
 
  375                 page.append(description);
 
  376                 page.append(
"</string></p>\n"); 
 
  378             out.write(page.toString());
 
  379             currentDataType = name;
 
  381         } 
catch (IOException ex) {
 
  382             logger.log(Level.SEVERE, 
"Failed to write page head: {0}", ex); 
 
  391     public void endDataType() {
 
  392         dataTypes.put(currentDataType, rowCount);
 
  394             out.write(
"</div>\n</body>\n</html>\n"); 
 
  395         } 
catch (IOException ex) {
 
  396             logger.log(Level.SEVERE, 
"Failed to write end of HTML report.", ex); 
 
  402                 } 
catch (IOException ex) {
 
  403                     logger.log(Level.WARNING, 
"Could not close the output writer when ending data type.", ex); 
 
  416     public void startSet(String setName) {
 
  417         StringBuilder set = 
new StringBuilder();
 
  418         set.append(
"<h1><a name=\"").append(setName).append(
"\">").append(setName).append(
"</a></h1>\n"); 
 
  419         set.append(
"<div class=\"keyword_list\">\n"); 
 
  422             out.write(set.toString());
 
  423         } 
catch (IOException ex) {
 
  424             logger.log(Level.SEVERE, 
"Failed to write set: {0}", ex); 
 
  432     public void endSet() {
 
  434             out.write(
"</div>\n"); 
 
  435         } 
catch (IOException ex) {
 
  436             logger.log(Level.SEVERE, 
"Failed to write end of set: {0}", ex); 
 
  446     public void addSetIndex(List<String> sets) {
 
  447         StringBuilder index = 
new StringBuilder();
 
  448         index.append(
"<ul>\n"); 
 
  449         for (String set : sets) {
 
  450             index.append(
"\t<li><a href=\"#").append(set).append(
"\">").append(set).append(
"</a></li>\n"); 
 
  452         index.append(
"</ul>\n"); 
 
  454             out.write(index.toString());
 
  455         } 
catch (IOException ex) {
 
  456             logger.log(Level.SEVERE, 
"Failed to add set index: {0}", ex); 
 
  466     public void addSetElement(String elementName) {
 
  468             out.write(
"<h4>" + elementName + 
"</h4>\n"); 
 
  469         } 
catch (IOException ex) {
 
  470             logger.log(Level.SEVERE, 
"Failed to write set element: {0}", ex); 
 
  480     public void startTable(List<String> titles) {
 
  481         StringBuilder ele = 
new StringBuilder();
 
  482         ele.append(
"<table>\n<thead>\n\t<tr>\n"); 
 
  483         for (String title : titles) {
 
  484             ele.append(
"\t\t<th>").append(title).append(
"</th>\n"); 
 
  486         ele.append(
"\t</tr>\n</thead>\n"); 
 
  489             out.write(ele.toString());
 
  490         } 
catch (IOException ex) {
 
  491             logger.log(Level.SEVERE, 
"Failed to write table start: {0}", ex); 
 
  502     public void startContentTagsTable(List<String> columnHeaders) {
 
  503         StringBuilder htmlOutput = 
new StringBuilder();
 
  504         htmlOutput.append(
"<table>\n<thead>\n\t<tr>\n"); 
 
  507         for (String columnHeader : columnHeaders) {
 
  508             htmlOutput.append(
"\t\t<th>").append(columnHeader).append(
"</th>\n"); 
 
  512         htmlOutput.append(
"\t\t<th></th>\n"); 
 
  514         htmlOutput.append(
"\t</tr>\n</thead>\n"); 
 
  517             out.write(htmlOutput.toString());
 
  518         } 
catch (IOException ex) {
 
  519             logger.log(Level.SEVERE, 
"Failed to write table start: {0}", ex); 
 
  527     public void endTable() {
 
  529             out.write(
"</table>\n"); 
 
  530         } 
catch (IOException ex) {
 
  531             logger.log(Level.SEVERE, 
"Failed to write end of table: {0}", ex); 
 
  541     public void addRow(List<String> row) {
 
  542         StringBuilder builder = 
new StringBuilder();
 
  543         builder.append(
"\t<tr>\n"); 
 
  544         for (String cell : row) {
 
  545             builder.append(
"\t\t<td>").append(cell).append(
"</td>\n"); 
 
  547         builder.append(
"\t</tr>\n"); 
 
  551             out.write(builder.toString());
 
  552         } 
catch (IOException ex) {
 
  553             logger.log(Level.SEVERE, 
"Failed to write row to out.", ex); 
 
  554         } 
catch (NullPointerException ex) {
 
  555             logger.log(Level.SEVERE, 
"Output writer is null. Page was not initialized before writing.", ex); 
 
  569     public void addRowWithTaggedContentHyperlink(List<String> row, ContentTag contentTag) {
 
  570         Content content = contentTag.getContent();
 
  571         if (content instanceof AbstractFile == 
false) {
 
  575         AbstractFile file = (AbstractFile) content;
 
  577         StringBuilder localFileLink = 
new StringBuilder();
 
  580                 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS
 
  581                 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS)) {
 
  582             localFileLink.append(
"<a href=\""); 
 
  584             String localFilePath = saveContent(file, contentTag.getName().getDisplayName());
 
  585             localFileLink.append(localFilePath);
 
  586             localFileLink.append(
"\">");
 
  589         StringBuilder builder = 
new StringBuilder();
 
  590         builder.append(
"\t<tr>\n"); 
 
  591         int positionCounter = 0;
 
  592         for (String cell : row) {
 
  594             if (positionCounter == 1) { 
 
  595                 builder.append(
"\t\t<td class=\"left_align_cell\">").append(localFileLink.toString()).append(cell).append(
"</a></td>\n"); 
 
  596             } 
else if (positionCounter == 7) { 
 
  597                 builder.append(
"\t\t<td class=\"right_align_cell\">").append(cell).append(
"</td>\n"); 
 
  599                 builder.append(
"\t\t<td>").append(cell).append(
"</td>\n"); 
 
  603         builder.append(
"\t</tr>\n"); 
 
  607             out.write(builder.toString());
 
  608         } 
catch (IOException ex) {
 
  609             logger.log(Level.SEVERE, 
"Failed to write row to out.", ex); 
 
  610         } 
catch (NullPointerException ex) {
 
  611             logger.log(Level.SEVERE, 
"Output writer is null. Page was not initialized before writing.", ex); 
 
  620     public void addThumbnailRows(List<Content> images) {
 
  621         List<String> currentRow = 
new ArrayList<>();
 
  624         for (Content content : images) {
 
  625             if (currentRow.size() == THUMBNAIL_COLUMNS) {
 
  630             if (totalCount == MAX_THUMBS_PER_PAGE) {
 
  634                 rowCount = totalCount;
 
  639                 startDataType(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.addThumbRows.dataType.title", pages),
 
  640                         NbBundle.getMessage(
this.getClass(), 
"ReportHTML.addThumbRows.dataType.msg"));
 
  641                 List<String> emptyHeaders = 
new ArrayList<>();
 
  642                 for (
int i = 0; i < THUMBNAIL_COLUMNS; i++) {
 
  643                     emptyHeaders.add(
"");
 
  645                 startTable(emptyHeaders);
 
  648             if (failsContentCheck(content)) {
 
  652             AbstractFile file = (AbstractFile) content;
 
  655             String thumbnailPath = prepareThumbnail(file);
 
  656             if (thumbnailPath == null) {
 
  659             String contentPath = saveContent(file, 
"thumbs_fullsize"); 
 
  662                 nameInImage = file.getUniquePath();
 
  663             } 
catch (TskCoreException ex) {
 
  664                 nameInImage = file.getName();
 
  667             StringBuilder linkToThumbnail = 
new StringBuilder();
 
  668             linkToThumbnail.append(
"<a href=\""); 
 
  669             linkToThumbnail.append(contentPath);
 
  670             linkToThumbnail.append(
"\">");
 
  671             linkToThumbnail.append(
"<img src=\"").append(thumbnailPath).append(
"\" title=\"").append(nameInImage).append(
"\"/>"); 
 
  672             linkToThumbnail.append(
"</a><br>"); 
 
  673             linkToThumbnail.append(file.getName()).append(
"<br>"); 
 
  675             Services services = currentCase.getServices();
 
  676             TagsManager tagsManager = services.getTagsManager();
 
  678                 List<ContentTag> tags = tagsManager.getContentTagsByContent(content);
 
  679                 if (tags.size() > 0) {
 
  680                     linkToThumbnail.append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.thumbLink.tags"));
 
  682                 for (
int i = 0; i < tags.size(); i++) {
 
  683                     ContentTag tag = tags.get(i);
 
  684                     linkToThumbnail.append(tag.getName().getDisplayName());
 
  685                     if (i != tags.size() - 1) {
 
  686                         linkToThumbnail.append(
", ");
 
  689             } 
catch (TskCoreException ex) {
 
  690                 logger.log(Level.WARNING, 
"Could not find get tags for file.", ex); 
 
  693             currentRow.add(linkToThumbnail.toString());
 
  698         if (currentRow.isEmpty() == 
false) {
 
  699             int extraCells = THUMBNAIL_COLUMNS - currentRow.size();
 
  700             for (
int i = 0; i < extraCells; i++) {
 
  708         rowCount = totalCount;
 
  711     private boolean failsContentCheck(Content c) {
 
  712         if (c instanceof AbstractFile == 
false) {
 
  715         AbstractFile file = (AbstractFile) c;
 
  717                 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS
 
  718                 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS) {
 
  733     public String saveContent(AbstractFile file, String dirName) {
 
  738         StringBuilder localFilePath = 
new StringBuilder();  
 
  740         localFilePath.append(path);
 
  741         localFilePath.append(dirName2);
 
  742         File localFileFolder = 
new File(localFilePath.toString());
 
  743         if (!localFileFolder.exists()) {
 
  744             localFileFolder.mkdirs();
 
  748         String fileName = file.getName();
 
  749         String objectIdSuffix = 
"_" + file.getId();
 
  750         int lastDotIndex = fileName.lastIndexOf(
".");
 
  751         if (lastDotIndex != -1 && lastDotIndex != 0) {
 
  753             fileName = fileName.substring(0, lastDotIndex) + objectIdSuffix + fileName.substring(lastDotIndex, fileName.length());
 
  757             fileName += objectIdSuffix;
 
  759         localFilePath.append(File.separator);
 
  760         localFilePath.append(fileName);
 
  764         File localFile = 
new File(localFilePath.toString());
 
  765         if (!localFile.exists()) {
 
  766             ExtractFscContentVisitor.extract(file, localFile, null, null);
 
  770         return localFilePath.toString().substring(path.length());
 
  781     public String dateToString(
long date) {
 
  782         SimpleDateFormat sdf = 
new java.text.SimpleDateFormat(
"yyyy/MM/dd HH:mm:ss");
 
  783         return sdf.format(
new java.util.Date(date * 1000));
 
  787     public String getRelativeFilePath() {
 
  788         return "HTML Report" + File.separator + 
"index.html"; 
 
  792     public String getName() {
 
  793         return NbBundle.getMessage(this.getClass(), 
"ReportHTML.getName.text");
 
  797     public String getDescription() {
 
  798         return NbBundle.getMessage(this.getClass(), 
"ReportHTML.getDesc.text");
 
  804     private void writeCss() {
 
  805         Writer cssOut = null;
 
  807             cssOut = 
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(path + 
"index.css"), 
"UTF-8")); 
 
  808             String css = 
"body {margin: 0px; padding: 0px; background: #FFFFFF; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353;}\n" + 
 
  809                     "#content {padding: 30px;}\n" + 
 
  810                     "#header {width:100%; padding: 10px; line-height: 25px; background: #07A; color: #FFF; font-size: 20px;}\n" + 
 
  811                     "h1 {font-size: 20px; font-weight: normal; color: #07A; padding: 0 0 7px 0; margin-top: 25px; border-bottom: 1px solid #D6D6D6;}\n" + 
 
  812                     "h2 {font-size: 20px; font-weight: bolder; color: #07A;}\n" + 
 
  813                     "h3 {font-size: 16px; color: #07A;}\n" + 
 
  814                     "h4 {background: #07A; color: #FFF; font-size: 16px; margin: 0 0 0 25px; padding: 0; padding-left: 15px;}\n" + 
 
  815                     "ul.nav {list-style-type: none; line-height: 35px; padding: 0px; margin-left: 15px;}\n" + 
 
  816                     "ul li a {font-size: 14px; color: #444; text-decoration: none; padding-left: 25px;}\n" + 
 
  817                     "ul li a:hover {text-decoration: underline;}\n" + 
 
  818                     "p {margin: 0 0 20px 0;}\n" + 
 
  819                     "table {white-space:nowrap; min-width: 700px; padding: 2; margin: 0; border-collapse: collapse; border-bottom: 2px solid #e5e5e5;}\n" + 
 
  820                     ".keyword_list table {margin: 0 0 25px 25px; border-bottom: 2px solid #dedede;}\n" + 
 
  821                     "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" + 
 
  822                     "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" + 
 
  823                     "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" + 
 
  824                     "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; }\n" + 
 
  825                     "table tr:nth-child(even) td {background: #f3f3f3;}"; 
 
  827         } 
catch (FileNotFoundException ex) {
 
  828             logger.log(Level.SEVERE, 
"Could not find index.css file to write to.", ex); 
 
  829         } 
catch (UnsupportedEncodingException ex) {
 
  830             logger.log(Level.SEVERE, 
"Did not recognize encoding when writing index.css.", ex); 
 
  831         } 
catch (IOException ex) {
 
  832             logger.log(Level.SEVERE, 
"Error creating Writer for index.css.", ex); 
 
  835                 if (cssOut != null) {
 
  839             } 
catch (IOException ex) {
 
  847     private void writeIndex() {
 
  848         Writer indexOut = null;
 
  849         String indexFilePath = path + 
"index.html"; 
 
  851             indexOut = 
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(indexFilePath), 
"UTF-8")); 
 
  852             StringBuilder index = 
new StringBuilder();
 
  853             final String reportTitle = reportBranding.getReportTitle();
 
  854             String iconPath = reportBranding.getAgencyLogoPath();
 
  855             if (iconPath == null) {
 
  857                 iconPath = 
"favicon.ico";
 
  859                 iconPath = 
"agency_logo"; 
 
  861             index.append(
"<head>\n<title>").append(reportTitle).append(
" ").append(
 
  862                     NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeIndex.title", currentCase.getDisplayName())).append(
 
  864             index.append(
"<link rel=\"icon\" type=\"image/ico\" href=\"")
 
  865                     .append(iconPath).append(
"\" />\n"); 
 
  866             index.append(
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"); 
 
  867             index.append(
"</head>\n"); 
 
  868             index.append(
"<frameset cols=\"350px,*\">\n"); 
 
  869             index.append(
"<frame src=\"nav.html\" name=\"nav\">\n"); 
 
  870             index.append(
"<frame src=\"summary.html\" name=\"content\">\n"); 
 
  871             index.append(
"<noframes>").append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeIndex.noFrames.msg")).append(
"<br />\n"); 
 
  872             index.append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeIndex.noFrames.seeNav")).append(
"<br />\n"); 
 
  873             index.append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeIndex.seeSum")).append(
"</noframes>\n"); 
 
  874             index.append(
"</frameset>\n"); 
 
  875             index.append(
"</html>"); 
 
  876             indexOut.write(index.toString());
 
  877             Case.getCurrentCase().addReport(indexFilePath, NbBundle.getMessage(
this.getClass(),
 
  878                     "ReportHTML.writeIndex.srcModuleName.text"), 
"");
 
  879         } 
catch (IOException ex) {
 
  880             logger.log(Level.SEVERE, 
"Error creating Writer for index.html: {0}", ex); 
 
  881         } 
catch (TskCoreException ex) {
 
  882             String errorMessage = String.format(
"Error adding %s to case as a report", indexFilePath); 
 
  883             logger.log(Level.SEVERE, errorMessage, ex);
 
  886                 if (indexOut != null) {
 
  890             } 
catch (IOException ex) {
 
  898     private void writeNav() {
 
  899         Writer navOut = null;
 
  901             navOut = 
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(path + 
"nav.html"), 
"UTF-8")); 
 
  902             StringBuilder nav = 
new StringBuilder();
 
  903             nav.append(
"<html>\n<head>\n\t<title>").append( 
 
  904                     NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeNav.title"))
 
  905                     .append(
"</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"index.css\" />\n"); 
 
  906             nav.append(
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n</head>\n<body>\n"); 
 
  907             nav.append(
"<div id=\"content\">\n<h1>").append( 
 
  908                     NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeNav.h1")).append(
"</h1>\n"); 
 
  909             nav.append(
"<ul class=\"nav\">\n"); 
 
  910             nav.append(
"<li style=\"background: url(summary.png) left center no-repeat;\"><a href=\"summary.html\" target=\"content\">") 
 
  911                     .append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeNav.summary")).append(
"</a></li>\n"); 
 
  913             for (String dataType : dataTypes.keySet()) {
 
  914                 String dataTypeEsc = dataTypeToFileName(dataType);
 
  915                 String iconFileName = useDataTypeIcon(dataType);
 
  916                 nav.append(
"<li style=\"background: url('").append(iconFileName) 
 
  917                         .append(
"') left center no-repeat;\"><a href=\"") 
 
  918                         .append(dataTypeEsc).append(
".html\" target=\"content\">") 
 
  919                         .append(dataType).append(
" (").append(dataTypes.get(dataType))
 
  920                         .append(
")</a></li>\n"); 
 
  922             nav.append(
"</ul>\n"); 
 
  923             nav.append(
"</div>\n</body>\n</html>"); 
 
  924             navOut.write(nav.toString());
 
  925         } 
catch (IOException ex) {
 
  926             logger.log(Level.SEVERE, 
"Failed to write end of report navigation menu: {0}", ex); 
 
  928             if (navOut != null) {
 
  932                 } 
catch (IOException ex) {
 
  933                     logger.log(Level.WARNING, 
"Could not close navigation out writer."); 
 
  938         InputStream in = null;
 
  939         OutputStream output = null;
 
  943             String generatorLogoPath = reportBranding.getGeneratorLogoPath();
 
  944             if (generatorLogoPath != null && !generatorLogoPath.isEmpty()) {
 
  945                 File from = 
new File(generatorLogoPath);
 
  946                 File to = 
new File(path);
 
  947                 FileUtil.copyFile(FileUtil.toFileObject(from), FileUtil.toFileObject(to), 
"generator_logo"); 
 
  950             String agencyLogoPath = reportBranding.getAgencyLogoPath();
 
  951             if (agencyLogoPath != null && !agencyLogoPath.isEmpty()) {
 
  952                 File from = 
new File(agencyLogoPath);
 
  953                 File to = 
new File(path);
 
  954                 FileUtil.copyFile(FileUtil.toFileObject(from), FileUtil.toFileObject(to), 
"agency_logo"); 
 
  957             in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/favicon.ico"); 
 
  958             output = 
new FileOutputStream(
new File(path + File.separator + 
"favicon.ico"));
 
  959             FileUtil.copy(in, output);
 
  963             in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/summary.png"); 
 
  964             output = 
new FileOutputStream(
new File(path + File.separator + 
"summary.png"));
 
  965             FileUtil.copy(in, output);
 
  969         } 
catch (IOException ex) {
 
  970             logger.log(Level.SEVERE, 
"Failed to extract images for HTML report.", ex); 
 
  972             if (output != null) {
 
  976                 } 
catch (IOException ex) {
 
  982                 } 
catch (IOException ex) {
 
  991     private void writeSummary() {
 
  994             out = 
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(path + 
"summary.html"), 
"UTF-8")); 
 
  995             StringBuilder head = 
new StringBuilder();
 
  996             head.append(
"<html>\n<head>\n<title>").append( 
 
  997                     NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.title")).append(
"</title>\n"); 
 
  998             head.append(
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"); 
 
  999             head.append(
"<style type=\"text/css\">\n"); 
 
 1000             head.append(
"body { padding: 0px; margin: 0px; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353; }\n"); 
 
 1001             head.append(
"#wrapper { width: 90%; margin: 0px auto; margin-top: 35px; }\n"); 
 
 1002             head.append(
"h1 { color: #07A; font-size: 36px; line-height: 42px; font-weight: normal; margin: 0px; border-bottom: 1px solid #81B9DB; }\n"); 
 
 1003             head.append(
"h1 span { color: #F00; display: block; font-size: 16px; font-weight: bold; line-height: 22px;}\n"); 
 
 1004             head.append(
"h2 { padding: 0 0 3px 0; margin: 0px; color: #07A; font-weight: normal; border-bottom: 1px dotted #81B9DB; }\n"); 
 
 1005             head.append(
"table td { padding-right: 25px; }\n"); 
 
 1006             head.append(
"p.subheadding { padding: 0px; margin: 0px; font-size: 11px; color: #B5B5B5; }\n"); 
 
 1007             head.append(
".title { width: 660px; margin-bottom: 50px; }\n"); 
 
 1008             head.append(
".left { float: left; width: 250px; margin-top: 20px; text-align: center; }\n"); 
 
 1009             head.append(
".left img { max-width: 250px; max-height: 250px; min-width: 200px; min-height: 200px; }\n"); 
 
 1010             head.append(
".right { float: right; width: 385px; margin-top: 25px; font-size: 14px; }\n"); 
 
 1011             head.append(
".clear { clear: both; }\n"); 
 
 1012             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"); 
 
 1013             head.append(
".info table { margin: 0 25px 20px 25px; }\n"); 
 
 1014             head.append(
"</style>\n"); 
 
 1015             head.append(
"</head>\n<body>\n"); 
 
 1016             out.write(head.toString());
 
 1018             DateFormat datetimeFormat = 
new SimpleDateFormat(
"yyyy/MM/dd HH:mm:ss");
 
 1019             Date date = 
new Date();
 
 1020             String datetime = datetimeFormat.format(date);
 
 1022             String caseName = currentCase.getDisplayName();
 
 1023             String caseNumber = currentCase.getNumber();
 
 1024             String examiner = currentCase.getExaminer();
 
 1027                 imagecount = currentCase.getDataSources().size();
 
 1028             } 
catch (TskCoreException ex) {
 
 1032             StringBuilder summary = 
new StringBuilder();
 
 1033             boolean running = 
false;
 
 1034             if (IngestManager.getInstance().isIngestRunning()) {
 
 1038             final String reportTitle = reportBranding.getReportTitle();
 
 1039             final String reportFooter = reportBranding.getReportFooter();
 
 1040             final boolean agencyLogoSet = reportBranding.getAgencyLogoPath() != null && !reportBranding.getAgencyLogoPath().isEmpty();
 
 1041             final boolean generatorLogoSet = reportBranding.getGeneratorLogoPath() != null && !reportBranding.getGeneratorLogoPath().isEmpty();
 
 1043             summary.append(
"<div id=\"wrapper\">\n"); 
 
 1044             summary.append(
"<h1>").append(reportTitle) 
 
 1045                     .append(running ? NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.warningMsg") : 
"")
 
 1047             summary.append(
"<p class=\"subheadding\">").append( 
 
 1048                     NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.reportGenOn.text", datetime)).append(
"</p>\n"); 
 
 1049             summary.append(
"<div class=\"title\">\n"); 
 
 1050             if (agencyLogoSet) {
 
 1051                 summary.append(
"<div class=\"left\">\n"); 
 
 1052                 summary.append(
"<img src=\"agency_logo.png\" />\n"); 
 
 1053                 summary.append(
"</div>\n"); 
 
 1055             final String align = agencyLogoSet ? 
"right" : 
"left"; 
 
 1056             summary.append(
"<div class=\"").append(align).append(
"\">\n"); 
 
 1057             summary.append(
"<table>\n"); 
 
 1058             summary.append(
"<tr><td>").append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.caseName")) 
 
 1059                     .append(
"</td><td>").append(caseName).append(
"</td></tr>\n"); 
 
 1060             summary.append(
"<tr><td>").append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.caseNum")) 
 
 1061                     .append(
"</td><td>").append(!caseNumber.isEmpty() ? caseNumber : NbBundle 
 
 1062                     .getMessage(this.getClass(), 
"ReportHTML.writeSum.noCaseNum")).append(
"</td></tr>\n"); 
 
 1063             summary.append(
"<tr><td>").append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.examiner")).append(
"</td><td>") 
 
 1064                     .append(!examiner.isEmpty() ? examiner : NbBundle
 
 1065                             .getMessage(this.getClass(), 
"ReportHTML.writeSum.noExaminer"))
 
 1066                     .append(
"</td></tr>\n"); 
 
 1067             summary.append(
"<tr><td>").append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.numImages")) 
 
 1068                     .append(
"</td><td>").append(imagecount).append(
"</td></tr>\n"); 
 
 1069             summary.append(
"</table>\n"); 
 
 1070             summary.append(
"</div>\n"); 
 
 1071             summary.append(
"<div class=\"clear\"></div>\n"); 
 
 1072             summary.append(
"</div>\n"); 
 
 1073             summary.append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.imageInfoHeading"));
 
 1074             summary.append(
"<div class=\"info\">\n"); 
 
 1076                 for (Content c : currentCase.getDataSources()) {
 
 1077                     summary.append(
"<p>").append(c.getName()).append(
"</p>\n"); 
 
 1078                     if (c instanceof Image) {
 
 1079                         Image img = (Image) c;
 
 1081                         summary.append(
"<table>\n"); 
 
 1082                         summary.append(
"<tr><td>").append( 
 
 1083                                 NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.timezone"))
 
 1084                                 .append(
"</td><td>").append(img.getTimeZone()).append(
"</td></tr>\n"); 
 
 1085                         for (String imgPath : img.getPaths()) {
 
 1086                             summary.append(
"<tr><td>").append( 
 
 1087                                     NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.path"))
 
 1088                                     .append(
"</td><td>").append(imgPath).append(
"</td></tr>\n"); 
 
 1090                         summary.append(
"</table>\n"); 
 
 1093             } 
catch (TskCoreException ex) {
 
 1094                 logger.log(Level.WARNING, 
"Unable to get image information for the HTML report."); 
 
 1096             summary.append(
"</div>\n"); 
 
 1097             if (generatorLogoSet) {
 
 1098                 summary.append(
"<div class=\"left\">\n"); 
 
 1099                 summary.append(
"<img src=\"generator_logo.png\" />\n"); 
 
 1100                 summary.append(
"</div>\n"); 
 
 1102             summary.append(
"<div class=\"clear\"></div>\n"); 
 
 1103             if (reportFooter != null) {
 
 1104                 summary.append(
"<p class=\"subheadding\">").append(reportFooter).append(
"</p>\n"); 
 
 1106             summary.append(
"</div>\n"); 
 
 1107             summary.append(
"</body></html>"); 
 
 1108             out.write(summary.toString());
 
 1109         } 
catch (FileNotFoundException ex) {
 
 1110             logger.log(Level.SEVERE, 
"Could not find summary.html file to write to."); 
 
 1111         } 
catch (UnsupportedEncodingException ex) {
 
 1112             logger.log(Level.SEVERE, 
"Did not recognize encoding when writing summary.hmtl."); 
 
 1113         } 
catch (IOException ex) {
 
 1114             logger.log(Level.SEVERE, 
"Error creating Writer for summary.html."); 
 
 1121             } 
catch (IOException ex) {
 
 1126     private String prepareThumbnail(AbstractFile file) {
 
 1127         File thumbFile = ImageUtils.getCachedThumbnailFile(file, ImageUtils.ICON_SIZE_MEDIUM);
 
 1128         if (thumbFile.exists() == 
false) {
 
 1131         File to = 
new File(thumbsPath);
 
 1132         FileObject from = FileUtil.toFileObject(thumbFile);
 
 1133         FileObject dest = FileUtil.toFileObject(to);
 
 1135             FileUtil.copyFile(from, dest, thumbFile.getName(), 
"");
 
 1136         } 
catch (IOException ex) {
 
 1137             logger.log(Level.SEVERE, 
"Failed to write thumb file to report directory.", ex); 
 
 1138         } 
catch (NullPointerException ex) {
 
 1139             logger.log(Level.SEVERE, 
"NPE generated from FileUtil.copyFile, probably because FileUtil.toFileObject returned null. \n" +
 
 1140                     "The File argument for toFileObject was " + thumbFile + 
" with toString: " + thumbFile.toString() + 
"\n" +
 
 1141                     "The FileObject returned by toFileObject, passed into FileUtil.copyFile, was " + from, ex);
 
 1144         return THUMBS_REL_PATH + thumbFile.getName();
 
static String escapeFileName(String fileName)