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.util.Exceptions;
 
   45 import org.openide.util.NbBundle;
 
   47 import org.openide.filesystems.FileUtil;
 
   64  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;       
 
   81     private final ReportBranding reportBranding;
 
   84     public static synchronized ReportHTML getDefault() {
 
   85         if (instance == null) {
 
   86             instance = 
new ReportHTML();
 
   92     private ReportHTML() {
 
   93         reportBranding = 
new ReportBranding();
 
   97     private void refresh() {
 
   98         currentCase = Case.getCurrentCase();
 
   99         skCase = currentCase.getSleuthkitCase();
 
  101         dataTypes = 
new TreeMap<>();
 
  105         currentDataType = 
"";
 
  111             } 
catch (IOException ex) {
 
  122     private String dataTypeToFileName(String dataType) {
 
  126         fileName = fileName.replaceAll(
" ", 
"_");
 
  136     private String useDataTypeIcon(String dataType)
 
  141         OutputStream output = null;
 
  143         logger.log(Level.INFO, 
"useDataTypeIcon: dataType = {0}", dataType); 
 
  146         BlackboardArtifact.ARTIFACT_TYPE artifactType = null;
 
  147         for (ARTIFACT_TYPE v : ARTIFACT_TYPE.values()) {
 
  148             if (v.getDisplayName().equals(dataType)) {
 
  153         if (null != artifactType)
 
  156             iconFileName = dataTypeToFileName(artifactType.getDisplayName()) + 
".png"; 
 
  157             iconFilePath = path + File.separator + iconFileName;
 
  160              switch (artifactType) {
 
  161                 case TSK_WEB_BOOKMARK:
 
  162                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/bookmarks.png"); 
 
  165                      in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/cookies.png"); 
 
  167                 case TSK_WEB_HISTORY:
 
  168                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/history.png"); 
 
  170                 case TSK_WEB_DOWNLOAD:
 
  171                      in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/downloads.png"); 
 
  173                 case TSK_RECENT_OBJECT:
 
  174                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/recent.png"); 
 
  176                 case TSK_INSTALLED_PROG:
 
  177                      in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/installed.png"); 
 
  179                 case TSK_KEYWORD_HIT:
 
  180                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/keywords.png"); 
 
  182                 case TSK_HASHSET_HIT:
 
  183                      in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/hash.png"); 
 
  185                 case TSK_DEVICE_ATTACHED:
 
  186                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/devices.png"); 
 
  188                 case TSK_WEB_SEARCH_QUERY:
 
  189                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/search.png"); 
 
  191                 case TSK_METADATA_EXIF:
 
  192                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/exif.png"); 
 
  195                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/userbookmarks.png"); 
 
  197                 case TSK_TAG_ARTIFACT:
 
  198                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/userbookmarks.png"); 
 
  200                 case TSK_SERVICE_ACCOUNT:
 
  201                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/account-icon-16.png"); 
 
  204                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/contact.png"); 
 
  207                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/message.png"); 
 
  210                      in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/calllog.png"); 
 
  212                 case TSK_CALENDAR_ENTRY:
 
  213                      in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/calendar.png"); 
 
  215                 case TSK_SPEED_DIAL_ENTRY:
 
  216                      in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/speeddialentry.png"); 
 
  218                 case TSK_BLUETOOTH_PAIRING:
 
  219                      in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/bluetooth.png"); 
 
  221                 case TSK_GPS_BOOKMARK:
 
  222                      in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gpsfav.png"); 
 
  224                 case TSK_GPS_LAST_KNOWN_LOCATION:
 
  225                      in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gps-lastlocation.png"); 
 
  228                      in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gps-search.png"); 
 
  231                      in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/computer.png"); 
 
  233                 case TSK_GPS_TRACKPOINT:
 
  234                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gps_trackpoint.png"); 
 
  237                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gps_trackpoint.png"); 
 
  240                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/mail-icon-16.png"); 
 
  242                 case TSK_ENCRYPTION_DETECTED:
 
  243                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/encrypted-file.png"); 
 
  245                 case TSK_EXT_MISMATCH_DETECTED:
 
  246                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/mismatch-16.png"); 
 
  248                 case TSK_INTERESTING_ARTIFACT_HIT:
 
  249                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/interesting_item.png"); 
 
  251                 case TSK_INTERESTING_FILE_HIT:
 
  252                     in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/interesting_item.png"); 
 
  255                      in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/installed.png"); 
 
  257                 case TSK_REMOTE_DRIVE:
 
  258                      in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/drive_network.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;
 
  269             logger.log(Level.WARNING, 
"useDataTypeIcon: no artifact found for data type = " + dataType); 
 
  270             in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/star.png"); 
 
  271             iconFileName = 
"star.png"; 
 
  272             iconFilePath = path + File.separator +  iconFileName;   
 
  276              output = 
new FileOutputStream(iconFilePath);
 
  277              FileUtil.copy(in, output);
 
  280           } 
catch (IOException ex) {
 
  281              logger.log(Level.SEVERE, 
"Failed to extract images for HTML report.", ex); 
 
  283              if (output != null) {
 
  287                  } 
catch (IOException ex) {
 
  292                  } 
catch (IOException ex) {
 
  305     public void startReport(String baseReportDir) {
 
  309         this.path = baseReportDir + 
"HTML Report" + File.separator; 
 
  310         this.thumbsPath = this.path + 
"thumbs" + File.separator; 
 
  312             FileUtil.createFolder(
new File(this.path));
 
  313             FileUtil.createFolder(
new File(this.thumbsPath));
 
  314         } 
catch (IOException ex) {
 
  315             logger.log(Level.SEVERE, 
"Unable to make HTML report folder."); 
 
  328     public void endReport() {
 
  333             } 
catch (IOException ex) {
 
  334                 logger.log(Level.WARNING, 
"Could not close the output writer when ending report.", ex); 
 
  348     public void startDataType(String name, String description) {
 
  349         String title = dataTypeToFileName(name);
 
  351             out = 
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(path + title + 
".html"), 
"UTF-8")); 
 
  352         } 
catch (FileNotFoundException ex) {
 
  353             logger.log(Level.SEVERE, 
"File not found: {0}", ex); 
 
  354         } 
catch (UnsupportedEncodingException ex) {
 
  355             logger.log(Level.SEVERE, 
"Unrecognized encoding"); 
 
  359             StringBuilder page = 
new StringBuilder();
 
  360             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"); 
 
  361             page.append(
"<div id=\"header\">").append(name).append(
"</div>\n<div id=\"content\">\n"); 
 
  362             if (!description.isEmpty()) {
 
  363                 page.append(
"<p><strong>"); 
 
  364                 page.append(description);
 
  365                 page.append(
"</string></p>\n"); 
 
  367             out.write(page.toString());
 
  368             currentDataType = name;
 
  370         } 
catch (IOException ex) {
 
  371             logger.log(Level.SEVERE, 
"Failed to write page head: {0}", ex); 
 
  380     public void endDataType() {
 
  381         dataTypes.put(currentDataType, rowCount);
 
  383             out.write(
"</div>\n</body>\n</html>\n"); 
 
  384         } 
catch (IOException ex) {
 
  385             Exceptions.printStackTrace(ex);
 
  391                 } 
catch (IOException ex) {
 
  392                     logger.log(Level.WARNING, 
"Could not close the output writer when ending data type.", ex); 
 
  404     public void startSet(String setName) {   
 
  405         StringBuilder set = 
new StringBuilder();
 
  406         set.append(
"<h1><a name=\"").append(setName).append(
"\">").append(setName).append(
"</a></h1>\n"); 
 
  407         set.append(
"<div class=\"keyword_list\">\n"); 
 
  410             out.write(set.toString());
 
  411         } 
catch (IOException ex) {
 
  412             logger.log(Level.SEVERE, 
"Failed to write set: {0}", ex); 
 
  420     public void endSet() {
 
  422             out.write(
"</div>\n"); 
 
  423         } 
catch (IOException ex) {
 
  424             logger.log(Level.SEVERE, 
"Failed to write end of set: {0}", ex); 
 
  433     public void addSetIndex(List<String> sets) {
 
  434         StringBuilder index = 
new StringBuilder();
 
  435         index.append(
"<ul>\n"); 
 
  436         for (String set : sets) {
 
  437             index.append(
"\t<li><a href=\"#").append(set).append(
"\">").append(set).append(
"</a></li>\n"); 
 
  439         index.append(
"</ul>\n"); 
 
  441             out.write(index.toString());
 
  442         } 
catch (IOException ex) {
 
  443             logger.log(Level.SEVERE, 
"Failed to add set index: {0}", ex); 
 
  452     public void addSetElement(String elementName) {
 
  454             out.write(
"<h4>" + elementName + 
"</h4>\n"); 
 
  455         } 
catch (IOException ex) {
 
  456             logger.log(Level.SEVERE, 
"Failed to write set element: {0}", ex); 
 
  465     public void startTable(List<String> titles) {
 
  466         StringBuilder ele = 
new StringBuilder();        
 
  467         ele.append(
"<table>\n<thead>\n\t<tr>\n"); 
 
  468         for(String title : titles) {
 
  469             ele.append(
"\t\t<th>").append(title).append(
"</th>\n"); 
 
  471         ele.append(
"\t</tr>\n</thead>\n"); 
 
  474             out.write(ele.toString());
 
  475         } 
catch (IOException ex) {
 
  476             logger.log(Level.SEVERE, 
"Failed to write table start: {0}", ex); 
 
  487     public void startContentTagsTable(List<String> columnHeaders) {
 
  488         StringBuilder htmlOutput = 
new StringBuilder();        
 
  489         htmlOutput.append(
"<table>\n<thead>\n\t<tr>\n"); 
 
  492         for(String columnHeader : columnHeaders) {
 
  493             htmlOutput.append(
"\t\t<th>").append(columnHeader).append(
"</th>\n"); 
 
  497         htmlOutput.append(
"\t\t<th></th>\n"); 
 
  499         htmlOutput.append(
"\t</tr>\n</thead>\n"); 
 
  502             out.write(htmlOutput.toString());
 
  503         } 
catch (IOException ex) {
 
  504             logger.log(Level.SEVERE, 
"Failed to write table start: {0}", ex); 
 
  512     public void endTable() {
 
  514             out.write(
"</table>\n"); 
 
  515         } 
catch (IOException ex) {
 
  516             logger.log(Level.SEVERE, 
"Failed to write end of table: {0}", ex); 
 
  525     public void addRow(List<String> row) {
 
  526         StringBuilder builder = 
new StringBuilder();
 
  527         builder.append(
"\t<tr>\n"); 
 
  528         for (String cell : row) {
 
  529             builder.append(
"\t\t<td>").append(cell).append(
"</td>\n"); 
 
  531         builder.append(
"\t</tr>\n"); 
 
  535             out.write(builder.toString());
 
  536         } 
catch (IOException ex) {
 
  537             logger.log(Level.SEVERE, 
"Failed to write row to out.", ex); 
 
  538         } 
catch (NullPointerException ex) {
 
  539             logger.log(Level.SEVERE, 
"Output writer is null. Page was not initialized before writing.", ex); 
 
  552     public void addRowWithTaggedContentHyperlink(List<String> row, ContentTag contentTag) {
 
  553         Content content = contentTag.getContent();
 
  554         if (content instanceof AbstractFile == 
false) {
 
  558         AbstractFile file = (AbstractFile) content;
 
  561             file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS ||
 
  562             file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS) {
 
  568         row.add(file.getMtimeAsDate());
 
  569         row.add(file.getCtimeAsDate());
 
  570         row.add(file.getAtimeAsDate());
 
  571         row.add(file.getCrtimeAsDate());
 
  572         row.add(Long.toString(file.getSize()));
 
  573         row.add(file.getMd5Hash());
 
  576         String localFilePath = saveContent(file, contentTag.getName().getDisplayName());
 
  579         StringBuilder localFileLink = 
new StringBuilder();
 
  580         localFileLink.append(
"<a href=\""); 
 
  581         localFileLink.append(localFilePath);
 
  582         localFileLink.append(
"\">");
 
  584         StringBuilder builder = 
new StringBuilder();
 
  585         builder.append(
"\t<tr>\n"); 
 
  586         int positionCounter=0;
 
  587         for (String cell : row) {
 
  589             if(positionCounter==1) { 
 
  590                 builder.append(
"\t\t<td class=\"left_align_cell\">").append(localFileLink.toString()).append(cell).append(
"</a></td>\n"); 
 
  592             else if (positionCounter==7) { 
 
  593                 builder.append(
"\t\t<td class=\"right_align_cell\">").append(cell).append(
"</td>\n"); 
 
  596                 builder.append(
"\t\t<td>").append(cell).append(
"</td>\n"); 
 
  600         builder.append(
"\t</tr>\n"); 
 
  604             out.write(builder.toString());
 
  606         catch (IOException ex) {
 
  607             logger.log(Level.SEVERE, 
"Failed to write row to out.", ex); 
 
  609         catch (NullPointerException ex) {
 
  610             logger.log(Level.SEVERE, 
"Output writer is null. Page was not initialized before writing.", ex); 
 
  618     public void addThumbnailRows(List<Content> images) {
 
  619         List<String> currentRow = 
new ArrayList<>();
 
  622         for (Content content : images) {
 
  623             if (currentRow.size() == THUMBNAIL_COLUMNS) {
 
  628             if (totalCount == MAX_THUMBS_PER_PAGE) {
 
  632                 rowCount = totalCount;
 
  637                 startDataType(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.addThumbRows.dataType.title", pages),
 
  638                               NbBundle.getMessage(
this.getClass(), 
"ReportHTML.addThumbRows.dataType.msg"));
 
  639                 List<String> emptyHeaders = 
new ArrayList<>();
 
  640                 for (
int i = 0; i < THUMBNAIL_COLUMNS; i++) {
 
  641                     emptyHeaders.add(
"");
 
  643                 startTable(emptyHeaders);
 
  646             if (failsContentCheck(content)) {
 
  650             AbstractFile file = (AbstractFile) content; 
 
  653             String thumbnailPath = prepareThumbnail(file);
 
  654             if (thumbnailPath == null) {
 
  657             String contentPath = saveContent(file, 
"thumbs_fullsize"); 
 
  660                 nameInImage = file.getUniquePath();
 
  661             } 
catch (TskCoreException ex) {
 
  662                 nameInImage = file.getName();
 
  665             StringBuilder linkToThumbnail = 
new StringBuilder();
 
  666             linkToThumbnail.append(
"<a href=\""); 
 
  667             linkToThumbnail.append(contentPath);
 
  668             linkToThumbnail.append(
"\">");
 
  669             linkToThumbnail.append(
"<img src=\"").append(thumbnailPath).append(
"\" title=\"").append(nameInImage).append(
"\"/>"); 
 
  670             linkToThumbnail.append(
"</a><br>"); 
 
  671             linkToThumbnail.append(file.getName()).append(
"<br>"); 
 
  673             Services services = currentCase.getServices();
 
  674             TagsManager tagsManager = services.getTagsManager();
 
  676                 List<ContentTag> tags = tagsManager.getContentTagsByContent(content);
 
  677                 if (tags.size() > 0) {
 
  678                     linkToThumbnail.append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.thumbLink.tags") );
 
  680                 for (
int i = 0; i < tags.size(); i++) {
 
  681                     ContentTag tag = tags.get(i);
 
  682                     linkToThumbnail.append(tag.getName().getDisplayName());
 
  683                     if (i != tags.size() - 1) {
 
  684                         linkToThumbnail.append(
", ");
 
  687             } 
catch (TskCoreException ex) {
 
  688                 logger.log(Level.WARNING, 
"Could not find get tags for file.", ex); 
 
  691             currentRow.add(linkToThumbnail.toString());
 
  696         if (currentRow.isEmpty() == 
false) {
 
  697             int extraCells = THUMBNAIL_COLUMNS - currentRow.size();
 
  698             for (
int i = 0; i < extraCells; i++) {
 
  706         rowCount = totalCount;
 
  709     private boolean failsContentCheck(Content c) {
 
  710         if (c instanceof AbstractFile == 
false) {
 
  713         AbstractFile file = (AbstractFile) c;
 
  715             file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS ||
 
  716             file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS) {
 
  728     public String saveContent(AbstractFile file, String dirName) {
 
  730         String dirName2 = dirName.replace(
"/", 
"_");
 
  731         dirName2 = dirName2.replace(
"\\", 
"_");
 
  734         StringBuilder localFilePath = 
new StringBuilder();  
 
  736         localFilePath.append(path);
 
  737         localFilePath.append(dirName2);
 
  738         File localFileFolder = 
new File(localFilePath.toString());
 
  739         if (!localFileFolder.exists()) { 
 
  740             localFileFolder.mkdirs();
 
  744         String fileName = file.getName();
 
  745         String objectIdSuffix = 
"_" + file.getId();
 
  746         int lastDotIndex = fileName.lastIndexOf(
".");
 
  747         if (lastDotIndex != -1 && lastDotIndex != 0) {
 
  749             fileName = fileName.substring(0, lastDotIndex) + objectIdSuffix + fileName.substring(lastDotIndex, fileName.length());
 
  754             fileName += objectIdSuffix;
 
  756         localFilePath.append(File.separator);
 
  757         localFilePath.append(fileName);
 
  761         File localFile = 
new File(localFilePath.toString());
 
  762         if (!localFile.exists()) {
 
  763             ExtractFscContentVisitor.extract(file, localFile, null, null);
 
  767         return localFilePath.toString().substring(path.length());
 
  776     public String dateToString(
long date) {
 
  777         SimpleDateFormat sdf = 
new java.text.SimpleDateFormat(
"yyyy/MM/dd HH:mm:ss");
 
  778         return sdf.format(
new java.util.Date(date * 1000));
 
  783     public String getRelativeFilePath() {
 
  784         return "HTML Report" + File.separator + 
"index.html"; 
 
  789     public String getName() {
 
  790         return NbBundle.getMessage(this.getClass(), 
"ReportHTML.getName.text");
 
  794     public String getDescription() {
 
  795         return NbBundle.getMessage(this.getClass(), 
"ReportHTML.getDesc.text");
 
  801     private void writeCss() {
 
  802         Writer cssOut = null;
 
  804             cssOut = 
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(path + 
"index.css"), 
"UTF-8")); 
 
  805             String css = 
"body {margin: 0px; padding: 0px; background: #FFFFFF; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353;}\n" + 
 
  806                          "#content {padding: 30px;}\n" + 
 
  807                          "#header {width:100%; padding: 10px; line-height: 25px; background: #07A; color: #FFF; font-size: 20px;}\n" + 
 
  808                          "h1 {font-size: 20px; font-weight: normal; color: #07A; padding: 0 0 7px 0; margin-top: 25px; border-bottom: 1px solid #D6D6D6;}\n" + 
 
  809                          "h2 {font-size: 20px; font-weight: bolder; color: #07A;}\n" + 
 
  810                          "h3 {font-size: 16px; color: #07A;}\n" + 
 
  811                          "h4 {background: #07A; color: #FFF; font-size: 16px; margin: 0 0 0 25px; padding: 0; padding-left: 15px;}\n" +  
 
  812                          "ul.nav {list-style-type: none; line-height: 35px; padding: 0px; margin-left: 15px;}\n" + 
 
  813                          "ul li a {font-size: 14px; color: #444; text-decoration: none; padding-left: 25px;}\n" + 
 
  814                          "ul li a:hover {text-decoration: underline;}\n" + 
 
  815                          "p {margin: 0 0 20px 0;}\n" + 
 
  816                          "table {white-space:nowrap; min-width: 700px; padding: 2; margin: 0; border-collapse: collapse; border-bottom: 2px solid #e5e5e5;}\n" + 
 
  817                          ".keyword_list table {margin: 0 0 25px 25px; border-bottom: 2px solid #dedede;}\n" + 
 
  818                          "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" + 
 
  819                          "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" + 
 
  820                          "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" + 
 
  821                          "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" + 
 
  822                          "table tr:nth-child(even) td {background: #f3f3f3;}"; 
 
  824         } 
catch (FileNotFoundException ex) {
 
  825             logger.log(Level.SEVERE, 
"Could not find index.css file to write to.", ex); 
 
  826         } 
catch (UnsupportedEncodingException ex) {
 
  827             logger.log(Level.SEVERE, 
"Did not recognize encoding when writing index.css.", ex); 
 
  828         } 
catch (IOException ex) {
 
  829             logger.log(Level.SEVERE, 
"Error creating Writer for index.css.", ex); 
 
  836             } 
catch (IOException ex) {
 
  844     private void writeIndex() {
 
  845         Writer indexOut = null;
 
  846         String indexFilePath = path + 
"index.html"; 
 
  848             indexOut = 
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(indexFilePath), 
"UTF-8")); 
 
  849             StringBuilder index = 
new StringBuilder();
 
  850             index.append(
"<head>\n<title>").append( 
 
  851                     NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeIndex.title", currentCase.getName())).append(
 
  853             index.append(
"<link rel=\"icon\" type=\"image/ico\" href=\"favicon.ico\" />\n"); 
 
  854             index.append(
"</head>\n"); 
 
  855             index.append(
"<frameset cols=\"350px,*\">\n"); 
 
  856             index.append(
"<frame src=\"nav.html\" name=\"nav\">\n"); 
 
  857             index.append(
"<frame src=\"summary.html\" name=\"content\">\n"); 
 
  858             index.append(
"<noframes>").append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeIndex.noFrames.msg")).append(
"<br />\n"); 
 
  859             index.append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeIndex.noFrames.seeNav")).append(
"<br />\n"); 
 
  860             index.append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeIndex.seeSum")).append(
"</noframes>\n"); 
 
  861             index.append(
"</frameset>\n"); 
 
  862             index.append(
"</html>"); 
 
  863             indexOut.write(index.toString());
 
  864             Case.getCurrentCase().addReport(indexFilePath, NbBundle.getMessage(
this.getClass(),
 
  865                                                                                "ReportHTML.writeIndex.srcModuleName.text"), 
"");
 
  866         } 
catch (IOException ex) {
 
  867             logger.log(Level.SEVERE, 
"Error creating Writer for index.html: {0}", ex); 
 
  868         } 
catch (TskCoreException ex) {
 
  869             String errorMessage = String.format(
"Error adding %s to case as a report", indexFilePath); 
 
  870             logger.log(Level.SEVERE, errorMessage, ex);
 
  873                 if(indexOut != null) {
 
  877             } 
catch (IOException ex) {
 
  885     private void writeNav() {
 
  886         Writer navOut = null;
 
  888             navOut = 
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(path + 
"nav.html"), 
"UTF-8")); 
 
  889             StringBuilder nav = 
new StringBuilder();
 
  890             nav.append(
"<html>\n<head>\n\t<title>").append( 
 
  891                     NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeNav.title"))
 
  892                .append(
"</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"index.css\" />\n</head>\n<body>\n"); 
 
  893             nav.append(
"<div id=\"content\">\n<h1>").append( 
 
  894                     NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeNav.h1")).append(
"</h1>\n"); 
 
  895             nav.append(
"<ul class=\"nav\">\n"); 
 
  896             nav.append(
"<li style=\"background: url(summary.png) left center no-repeat;\"><a href=\"summary.html\" target=\"content\">") 
 
  897                .append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeNav.summary")).append(
"</a></li>\n"); 
 
  899             for (String dataType : dataTypes.keySet()) {
 
  900                 String dataTypeEsc = dataTypeToFileName(dataType);
 
  901                 String iconFileName = useDataTypeIcon(dataType);
 
  902                 nav.append(
"<li style=\"background: url('").append(iconFileName) 
 
  903                         .append(
"') left center no-repeat;\"><a href=\"") 
 
  904                         .append(dataTypeEsc).append(
".html\" target=\"content\">") 
 
  905                         .append(dataType).append(
" (").append(dataTypes.get(dataType))
 
  906                         .append(
")</a></li>\n"); 
 
  908             nav.append(
"</ul>\n"); 
 
  909             nav.append(
"</div>\n</body>\n</html>"); 
 
  910             navOut.write(nav.toString());
 
  911         } 
catch (IOException ex) {
 
  912             logger.log(Level.SEVERE, 
"Failed to write end of report navigation menu: {0}", ex); 
 
  914             if (navOut != null) {
 
  918                 } 
catch (IOException ex) {
 
  919                     logger.log(Level.WARNING, 
"Could not close navigation out writer."); 
 
  924         InputStream in = null;
 
  925         OutputStream output = null;
 
  929             String generatorLogoPath = reportBranding.getGeneratorLogoPath();
 
  930             if (generatorLogoPath != null && ! generatorLogoPath.isEmpty()) {
 
  931                 File from = 
new File(generatorLogoPath);
 
  932                 File to = 
new File(path);
 
  933                 FileUtil.copyFile(FileUtil.toFileObject(from), FileUtil.toFileObject(to), 
"generator_logo"); 
 
  936             String agencyLogoPath = reportBranding.getAgencyLogoPath();
 
  937             if (agencyLogoPath != null && ! agencyLogoPath.isEmpty() ) {
 
  938                 File from = 
new File(agencyLogoPath);
 
  939                 File to = 
new File(path);
 
  940                 FileUtil.copyFile(FileUtil.toFileObject(from), FileUtil.toFileObject(to), 
"agency_logo"); 
 
  943             in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/favicon.ico"); 
 
  944             output = 
new FileOutputStream(
new File(path + File.separator + 
"favicon.ico"));
 
  945             FileUtil.copy(in, output);
 
  949             in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/summary.png"); 
 
  950             output = 
new FileOutputStream(
new File(path + File.separator + 
"summary.png"));
 
  951             FileUtil.copy(in, output);
 
  956         } 
catch (IOException ex) {
 
  957             logger.log(Level.SEVERE, 
"Failed to extract images for HTML report.", ex); 
 
  959             if (output != null) {
 
  963                 } 
catch (IOException ex) {
 
  968                 } 
catch (IOException ex) {
 
  977     private void writeSummary() {
 
  980             out = 
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(path + 
"summary.html"), 
"UTF-8")); 
 
  981             StringBuilder head = 
new StringBuilder();
 
  982             head.append(
"<html>\n<head>\n<title>").append( 
 
  983                     NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.title")).append(
"</title>\n"); 
 
  984             head.append(
"<style type=\"text/css\">\n"); 
 
  985             head.append(
"body { padding: 0px; margin: 0px; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353; }\n"); 
 
  986             head.append(
"#wrapper { width: 90%; margin: 0px auto; margin-top: 35px; }\n"); 
 
  987             head.append(
"h1 { color: #07A; font-size: 36px; line-height: 42px; font-weight: normal; margin: 0px; border-bottom: 1px solid #81B9DB; }\n"); 
 
  988             head.append(
"h1 span { color: #F00; display: block; font-size: 16px; font-weight: bold; line-height: 22px;}\n"); 
 
  989             head.append(
"h2 { padding: 0 0 3px 0; margin: 0px; color: #07A; font-weight: normal; border-bottom: 1px dotted #81B9DB; }\n"); 
 
  990             head.append(
"table td { padding-right: 25px; }\n"); 
 
  991             head.append(
"p.subheadding { padding: 0px; margin: 0px; font-size: 11px; color: #B5B5B5; }\n"); 
 
  992             head.append(
".title { width: 660px; margin-bottom: 50px; }\n"); 
 
  993             head.append(
".left { float: left; width: 250px; margin-top: 20px; text-align: center; }\n"); 
 
  994             head.append(
".left img { max-width: 250px; max-height: 250px; min-width: 200px; min-height: 200px; }\n"); 
 
  995             head.append(
".right { float: right; width: 385px; margin-top: 25px; font-size: 14px; }\n"); 
 
  996             head.append(
".clear { clear: both; }\n"); 
 
  997             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"); 
 
  998             head.append(
".info table { margin: 0 25px 20px 25px; }\n"); 
 
  999             head.append(
"</style>\n"); 
 
 1000             head.append(
"</head>\n<body>\n"); 
 
 1001             out.write(head.toString());
 
 1003             DateFormat datetimeFormat = 
new SimpleDateFormat(
"yyyy/MM/dd HH:mm:ss");
 
 1004             Date date = 
new Date();
 
 1005             String datetime = datetimeFormat.format(date);
 
 1007             String caseName = currentCase.getName();
 
 1008             String caseNumber = currentCase.getNumber();
 
 1009             String examiner = currentCase.getExaminer();
 
 1012                 imagecount = currentCase.getDataSources().size();
 
 1013             } 
catch (TskCoreException ex) {
 
 1017             StringBuilder summary = 
new StringBuilder();
 
 1018             boolean running = 
false;
 
 1019             if (IngestManager.getInstance().isIngestRunning()) {
 
 1023             final String reportTitle = reportBranding.getReportTitle();
 
 1024             final String reportFooter = reportBranding.getReportFooter();
 
 1025             final boolean agencyLogoSet = reportBranding.getAgencyLogoPath() != null && !reportBranding.getAgencyLogoPath().isEmpty();
 
 1026             final boolean generatorLogoSet = reportBranding.getGeneratorLogoPath() != null && !reportBranding.getGeneratorLogoPath().isEmpty();
 
 1028             summary.append(
"<div id=\"wrapper\">\n"); 
 
 1029             summary.append(
"<h1>").append(reportTitle) 
 
 1030                    .append(running ? NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.warningMsg") : 
"")
 
 1032             summary.append(
"<p class=\"subheadding\">").append( 
 
 1033                     NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.reportGenOn.text", datetime)).append(
"</p>\n"); 
 
 1034             summary.append(
"<div class=\"title\">\n"); 
 
 1035             if (agencyLogoSet) {
 
 1036                 summary.append(
"<div class=\"left\">\n"); 
 
 1037                 summary.append(
"<img src=\"agency_logo.png\" />\n"); 
 
 1038                 summary.append(
"</div>\n"); 
 
 1040             final String align = agencyLogoSet?
"right":
"left"; 
 
 1041             summary.append(
"<div class=\"").append(align).append(
"\">\n"); 
 
 1042             summary.append(
"<table>\n"); 
 
 1043             summary.append(
"<tr><td>").append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.caseName")) 
 
 1044                    .append(
"</td><td>").append(caseName).append(
"</td></tr>\n"); 
 
 1045             summary.append(
"<tr><td>").append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.caseNum")) 
 
 1046                    .append(
"</td><td>").append(!caseNumber.isEmpty() ? caseNumber : NbBundle 
 
 1047                     .getMessage(this.getClass(), 
"ReportHTML.writeSum.noCaseNum")).append(
"</td></tr>\n"); 
 
 1048             summary.append(
"<tr><td>").append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.examiner")).append(
"</td><td>") 
 
 1049                    .append(!examiner.isEmpty() ? examiner : NbBundle
 
 1050                            .getMessage(this.getClass(), 
"ReportHTML.writeSum.noExaminer"))
 
 1051                    .append(
"</td></tr>\n"); 
 
 1052             summary.append(
"<tr><td>").append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.numImages")) 
 
 1053                    .append(
"</td><td>").append(imagecount).append(
"</td></tr>\n"); 
 
 1054             summary.append(
"</table>\n"); 
 
 1055             summary.append(
"</div>\n"); 
 
 1056             summary.append(
"<div class=\"clear\"></div>\n"); 
 
 1057             summary.append(
"</div>\n"); 
 
 1058             summary.append(NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.imageInfoHeading"));
 
 1059             summary.append(
"<div class=\"info\">\n"); 
 
 1061                 for (Content c : currentCase.getDataSources()) {
 
 1062                     summary.append(
"<p>").append(c.getName()).append(
"</p>\n"); 
 
 1063                     if (c instanceof Image) {
 
 1064                         Image img = (Image) c;
 
 1066                         summary.append(
"<table>\n"); 
 
 1067                         summary.append(
"<tr><td>").append( 
 
 1068                                 NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.timezone"))
 
 1069                                .append(
"</td><td>").append(img.getTimeZone()).append(
"</td></tr>\n"); 
 
 1070                         for(String imgPath : img.getPaths()) {
 
 1071                             summary.append(
"<tr><td>").append( 
 
 1072                                     NbBundle.getMessage(
this.getClass(), 
"ReportHTML.writeSum.path"))
 
 1073                                    .append(
"</td><td>").append(imgPath).append(
"</td></tr>\n"); 
 
 1075                         summary.append(
"</table>\n"); 
 
 1078             } 
catch (TskCoreException ex) {
 
 1079                 logger.log(Level.WARNING, 
"Unable to get image information for the HTML report."); 
 
 1081             summary.append(
"</div>\n"); 
 
 1082             if (generatorLogoSet) {
 
 1083                 summary.append(
"<div class=\"left\">\n"); 
 
 1084                 summary.append(
"<img src=\"generator_logo.png\" />\n"); 
 
 1085                 summary.append(
"</div>\n"); 
 
 1087             summary.append(
"<div class=\"clear\"></div>\n"); 
 
 1088             if (reportFooter != null) {
 
 1089                 summary.append(
"<p class=\"subheadding\">").append(reportFooter).append(
"</p>\n"); 
 
 1091             summary.append(
"</div>\n"); 
 
 1092             summary.append(
"</body></html>"); 
 
 1093             out.write(summary.toString());
 
 1094         } 
catch (FileNotFoundException ex) {
 
 1095             logger.log(Level.SEVERE, 
"Could not find summary.html file to write to."); 
 
 1096         } 
catch (UnsupportedEncodingException ex) {
 
 1097             logger.log(Level.SEVERE, 
"Did not recognize encoding when writing summary.hmtl."); 
 
 1098         } 
catch (IOException ex) {
 
 1099             logger.log(Level.SEVERE, 
"Error creating Writer for summary.html."); 
 
 1106             } 
catch (IOException ex) {
 
 1111     private String prepareThumbnail(AbstractFile file) {
 
 1112         File thumbFile = ImageUtils.getIconFile(file, ImageUtils.ICON_SIZE_MEDIUM);
 
 1113         if (thumbFile.exists() == 
false) {
 
 1117             File to = 
new File(thumbsPath);
 
 1118             FileObject from = FileUtil.toFileObject(thumbFile);
 
 1119             FileObject dest = FileUtil.toFileObject(to);
 
 1120             FileUtil.copyFile(from, dest, thumbFile.getName(), 
"");
 
 1121         } 
catch (IOException ex) {
 
 1122             logger.log(Level.SEVERE, 
"Failed to write thumb file to report directory.", ex); 
 
 1125         return THUMBS_REL_PATH + thumbFile.getName();
 
static String escapeFileName(String fileName)