Autopsy  4.10.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
ReportHTML.java
Go to the documentation of this file.
1 /*
2  *
3  * Autopsy Forensic Browser
4  *
5  * Copyright 2012-2018 Basis Technology Corp.
6  *
7  * Copyright 2012 42six Solutions.
8  * Contact: aebadirad <at> 42six <dot> com
9  * Project Contact/Architect: carrier <at> sleuthkit <dot> org
10  *
11  * Licensed under the Apache License, Version 2.0 (the "License");
12  * you may not use this file except in compliance with the License.
13  * You may obtain a copy of the License at
14  *
15  * http://www.apache.org/licenses/LICENSE-2.0
16  *
17  * Unless required by applicable law or agreed to in writing, software
18  * distributed under the License is distributed on an "AS IS" BASIS,
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20  * See the License for the specific language governing permissions and
21  * limitations under the License.
22  */
23 package org.sleuthkit.autopsy.report;
24 
25 import java.awt.image.BufferedImage;
26 import java.io.BufferedWriter;
27 import java.io.File;
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;
45 import java.util.Map;
46 import java.util.Set;
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;
70 import org.sleuthkit.datamodel.AbstractFile;
71 import org.sleuthkit.datamodel.BlackboardArtifact;
72 import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
73 import org.sleuthkit.datamodel.Content;
74 import org.sleuthkit.datamodel.ContentTag;
75 import org.sleuthkit.datamodel.Image;
76 import org.sleuthkit.datamodel.IngestJobInfo;
77 import org.sleuthkit.datamodel.IngestModuleInfo;
78 import org.sleuthkit.datamodel.SleuthkitCase;
79 import org.sleuthkit.datamodel.TskCoreException;
80 import org.sleuthkit.datamodel.TskData;
81 import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM;
82 
83 class ReportHTML implements TableReportModule {
84 
85  private static final Logger logger = Logger.getLogger(ReportHTML.class.getName());
86  private static final String THUMBS_REL_PATH = "thumbs" + File.separator; //NON-NLS
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;
92 
93  private Map<String, Integer> dataTypes;
94  private String path;
95  private String thumbsPath;
96  private String subPath;
97  private String currentDataType; // name of current data type
98  private Integer rowCount; // number of rows (aka artifacts or tags) for the current data type
99  private Writer out;
100 
101  private ReportHTMLConfigurationPanel configPanel;
102 
103  private final ReportBranding reportBranding;
104 
105  // Get the default instance of this report
106  public static synchronized ReportHTML getDefault() {
107  if (instance == null) {
108  instance = new ReportHTML();
109  }
110  return instance;
111  }
112 
113  // Hidden constructor
114  private ReportHTML() {
115  reportBranding = new ReportBranding();
116  }
117 
118  @Override
119  public JPanel getConfigurationPanel() {
120  if (configPanel == null) {
121  configPanel = new ReportHTMLConfigurationPanel();
122  }
123  return configPanel;
124  }
125 
126  // Refesh the member variables
127  private void refresh() throws NoCurrentCaseException {
128  currentCase = Case.getCurrentCaseThrows();
129 
130  dataTypes = new TreeMap<>();
131 
132  path = "";
133  thumbsPath = "";
134  subPath = "";
135  currentDataType = "";
136  rowCount = 0;
137 
138  if (out != null) {
139  try {
140  out.close();
141  } catch (IOException ex) {
142  }
143  }
144  out = null;
145  }
146 
153  private String dataTypeToFileName(String dataType) {
154 
155  String fileName = org.sleuthkit.autopsy.coreutils.FileUtil.escapeFileName(dataType);
156  // replace all ' ' with '_'
157  fileName = fileName.replaceAll(" ", "_");
158 
159  return fileName;
160  }
161 
166  private String useDataTypeIcon(String dataType) {
167  String iconFilePath;
168  String iconFileName;
169  InputStream in;
170  OutputStream output = null;
171 
172  logger.log(Level.INFO, "useDataTypeIcon: dataType = {0}", dataType); //NON-NLS
173 
174  // find the artifact with matching display name
175  BlackboardArtifact.ARTIFACT_TYPE artifactType = null;
176  for (ARTIFACT_TYPE v : ARTIFACT_TYPE.values()) {
177  if (v.getDisplayName().equals(dataType)) {
178  artifactType = v;
179  }
180  }
181 
182  if (null != artifactType) {
183  // set the icon file name
184  iconFileName = dataTypeToFileName(artifactType.getDisplayName()) + ".png"; //NON-NLS
185  iconFilePath = subPath + File.separator + iconFileName;
186 
187  // determine the source image to use
188  switch (artifactType) {
189  case TSK_WEB_BOOKMARK:
190  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/bookmarks.png"); //NON-NLS
191  break;
192  case TSK_WEB_COOKIE:
193  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/cookies.png"); //NON-NLS
194  break;
195  case TSK_WEB_HISTORY:
196  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/history.png"); //NON-NLS
197  break;
198  case TSK_WEB_DOWNLOAD:
199  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/downloads.png"); //NON-NLS
200  break;
201  case TSK_RECENT_OBJECT:
202  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/recent.png"); //NON-NLS
203  break;
204  case TSK_INSTALLED_PROG:
205  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/installed.png"); //NON-NLS
206  break;
207  case TSK_KEYWORD_HIT:
208  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/keywords.png"); //NON-NLS
209  break;
210  case TSK_HASHSET_HIT:
211  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/hash.png"); //NON-NLS
212  break;
213  case TSK_DEVICE_ATTACHED:
214  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/devices.png"); //NON-NLS
215  break;
216  case TSK_WEB_SEARCH_QUERY:
217  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/search.png"); //NON-NLS
218  break;
219  case TSK_METADATA_EXIF:
220  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/exif.png"); //NON-NLS
221  break;
222  case TSK_TAG_FILE:
223  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/userbookmarks.png"); //NON-NLS
224  break;
225  case TSK_TAG_ARTIFACT:
226  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/userbookmarks.png"); //NON-NLS
227  break;
228  case TSK_SERVICE_ACCOUNT:
229  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/account-icon-16.png"); //NON-NLS
230  break;
231  case TSK_CONTACT:
232  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/contact.png"); //NON-NLS
233  break;
234  case TSK_MESSAGE:
235  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/message.png"); //NON-NLS
236  break;
237  case TSK_CALLLOG:
238  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/calllog.png"); //NON-NLS
239  break;
240  case TSK_CALENDAR_ENTRY:
241  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/calendar.png"); //NON-NLS
242  break;
243  case TSK_SPEED_DIAL_ENTRY:
244  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/speeddialentry.png"); //NON-NLS
245  break;
246  case TSK_BLUETOOTH_PAIRING:
247  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/bluetooth.png"); //NON-NLS
248  break;
249  case TSK_GPS_BOOKMARK:
250  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/gpsfav.png"); //NON-NLS
251  break;
252  case TSK_GPS_LAST_KNOWN_LOCATION:
253  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/gps-lastlocation.png"); //NON-NLS
254  break;
255  case TSK_GPS_SEARCH:
256  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/gps-search.png"); //NON-NLS
257  break;
258  case TSK_OS_INFO:
259  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/computer.png"); //NON-NLS
260  break;
261  case TSK_GPS_TRACKPOINT:
262  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/gps_trackpoint.png"); //NON-NLS
263  break;
264  case TSK_GPS_ROUTE:
265  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/gps_trackpoint.png"); //NON-NLS
266  break;
267  case TSK_EMAIL_MSG:
268  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/mail-icon-16.png"); //NON-NLS
269  break;
270  case TSK_ENCRYPTION_SUSPECTED:
271  case TSK_ENCRYPTION_DETECTED:
272  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/encrypted-file.png"); //NON-NLS
273  break;
274  case TSK_EXT_MISMATCH_DETECTED:
275  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/mismatch-16.png"); //NON-NLS
276  break;
277  case TSK_INTERESTING_ARTIFACT_HIT:
278  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/interesting_item.png"); //NON-NLS
279  break;
280  case TSK_INTERESTING_FILE_HIT:
281  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/interesting_item.png"); //NON-NLS
282  break;
283  case TSK_PROG_RUN:
284  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/installed.png"); //NON-NLS
285  break;
286  case TSK_REMOTE_DRIVE:
287  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/drive_network.png"); //NON-NLS
288  break;
289  case TSK_ACCOUNT:
290  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/accounts.png"); //NON-NLS
291  break;
292  case TSK_WIFI_NETWORK:
293  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/network-wifi.png"); //NON-NLS
294  break;
295  case TSK_WIFI_NETWORK_ADAPTER:
296  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/network-wifi.png"); //NON-NLS
297  break;
298  case TSK_SIM_ATTACHED:
299  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/sim_card.png"); //NON-NLS
300  break;
301  case TSK_BLUETOOTH_ADAPTER:
302  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/Bluetooth.png"); //NON-NLS
303  break;
304  case TSK_DEVICE_INFO:
305  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/devices.png"); //NON-NLS
306  break;
307  case TSK_VERIFICATION_FAILED:
308  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/validationFailed.png"); //NON-NLS
309  break;
310  default:
311  logger.log(Level.WARNING, "useDataTypeIcon: unhandled artifact type = {0}", dataType); //NON-NLS
312  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/star.png"); //NON-NLS
313  iconFileName = "star.png"; //NON-NLS
314  iconFilePath = subPath + File.separator + iconFileName;
315  break;
316  }
317  } else if (dataType.startsWith(ARTIFACT_TYPE.TSK_ACCOUNT.getDisplayName())) {
318  /*
319  * TSK_ACCOUNT artifacts get separated by their TSK_ACCOUNT_TYPE
320  * attribute, with a synthetic compound dataType name, so they are
321  * not caught by the switch statement above. For now we just give
322  * them all the general account icon, but we could do something else
323  * in the future.
324  */
325  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/accounts.png"); //NON-NLS
326  iconFileName = "accounts.png"; //NON-NLS
327  iconFilePath = subPath + File.separator + iconFileName;
328  } else { // no defined artifact found for this dataType
329  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/star.png"); //NON-NLS
330  iconFileName = "star.png"; //NON-NLS
331  iconFilePath = subPath + File.separator + iconFileName;
332  }
333 
334  try {
335  output = new FileOutputStream(iconFilePath);
336  FileUtil.copy(in, output);
337  in.close();
338  output.close();
339  } catch (IOException ex) {
340  logger.log(Level.SEVERE, "Failed to extract images for HTML report.", ex); //NON-NLS
341  } finally {
342  if (output != null) {
343  try {
344  output.flush();
345  output.close();
346  } catch (IOException ex) {
347  }
348  }
349  if (in != null) {
350  try {
351  in.close();
352  } catch (IOException ex) {
353  }
354  }
355  }
356 
357  return iconFileName;
358  }
359 
366  @Override
367  public void startReport(String baseReportDir) {
368  // Save settings
369  ModuleSettings.setConfigSetting("HTMLReport", "header", configPanel.getHeader()); //NON-NLS
370  ModuleSettings.setConfigSetting("HTMLReport", "footer", configPanel.getFooter()); //NON-NLS
371 
372  // Refresh the HTML report
373  try {
374  refresh();
375  } catch (NoCurrentCaseException ex) {
376  logger.log(Level.SEVERE, "Exception while getting open case."); //NON-NLS
377  return;
378  }
379  // Setup the path for the HTML report
380  this.path = baseReportDir; //NON-NLS
381  this.subPath = this.path + HTML_SUBDIR + File.separator;
382  this.thumbsPath = this.subPath + THUMBS_REL_PATH; //NON-NLS
383  try {
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."); //NON-NLS
388  }
389  // Write the basic files
390  writeCss();
391  writeIndex();
392  writeSummary();
393  }
394 
399  @Override
400  public void endReport() {
401  writeNav();
402  if (out != null) {
403  try {
404  out.close();
405  } catch (IOException ex) {
406  logger.log(Level.WARNING, "Could not close the output writer when ending report.", ex); //NON-NLS
407  }
408  }
409  }
410 
419  @Override
420  public void startDataType(String name, String description) {
421  String title = dataTypeToFileName(name);
422  try {
423  out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(subPath + title + ".html"), "UTF-8")); //NON-NLS
424  } catch (FileNotFoundException ex) {
425  logger.log(Level.SEVERE, "File not found: {0}", ex); //NON-NLS
426  } catch (UnsupportedEncodingException ex) {
427  logger.log(Level.SEVERE, "Unrecognized encoding"); //NON-NLS
428  }
429 
430  try {
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") //NON-NLS
433  .append(writePageHeader())
434  .append("<div id=\"header\">").append(name).append("</div>\n")
435  .append("<div id=\"content\">\n"); //NON-NLS
436  if (!description.isEmpty()) {
437  page.append("<p><strong>"); //NON-NLS
438  page.append(description);
439  page.append("</strong></p>\n"); //NON-NLS
440  }
441  out.write(page.toString());
442  currentDataType = name;
443  rowCount = 0;
444  } catch (IOException ex) {
445  logger.log(Level.SEVERE, "Failed to write page head: {0}", ex); //NON-NLS
446  }
447  }
448 
453  @Override
454  public void endDataType() {
455  dataTypes.put(currentDataType, rowCount);
456  try {
457  StringBuilder builder = new StringBuilder();
458  builder.append(writePageFooter());
459  builder.append("</div>\n</body>\n</html>\n"); //NON-NLS
460  out.write(builder.toString());
461  } catch (IOException ex) {
462  logger.log(Level.SEVERE, "Failed to write end of HTML report.", ex); //NON-NLS
463  } finally {
464  if (out != null) {
465  try {
466  out.flush();
467  out.close();
468  } catch (IOException ex) {
469  logger.log(Level.WARNING, "Could not close the output writer when ending data type.", ex); //NON-NLS
470  }
471  out = null;
472  }
473  }
474  }
475 
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))
488  .append("</div>\n"); //NON-NLS
489  }
490  return output.toString();
491  }
492 
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))
505  .append("</div>"); //NON-NLS
506  }
507  return output.toString();
508  }
509 
515  @Override
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"); //NON-NLS
519  set.append("<div class=\"keyword_list\">\n"); //NON-NLS
520 
521  try {
522  out.write(set.toString());
523  } catch (IOException ex) {
524  logger.log(Level.SEVERE, "Failed to write set: {0}", ex); //NON-NLS
525  }
526  }
527 
531  @Override
532  public void endSet() {
533  try {
534  out.write("</div>\n"); //NON-NLS
535  } catch (IOException ex) {
536  logger.log(Level.SEVERE, "Failed to write end of set: {0}", ex); //NON-NLS
537  }
538  }
539 
545  @Override
546  public void addSetIndex(List<String> sets) {
547  StringBuilder index = new StringBuilder();
548  index.append("<ul>\n"); //NON-NLS
549  for (String set : sets) {
550  index.append("\t<li><a href=\"#").append(set).append("\">").append(set).append("</a></li>\n"); //NON-NLS
551  }
552  index.append("</ul>\n"); //NON-NLS
553  try {
554  out.write(index.toString());
555  } catch (IOException ex) {
556  logger.log(Level.SEVERE, "Failed to add set index: {0}", ex); //NON-NLS
557  }
558  }
559 
565  @Override
566  public void addSetElement(String elementName) {
567  try {
568  out.write("<h4>" + elementName + "</h4>\n"); //NON-NLS
569  } catch (IOException ex) {
570  logger.log(Level.SEVERE, "Failed to write set element: {0}", ex); //NON-NLS
571  }
572  }
573 
579  @Override
580  public void startTable(List<String> titles) {
581  StringBuilder ele = new StringBuilder();
582  ele.append("<table>\n<thead>\n\t<tr>\n"); //NON-NLS
583  for (String title : titles) {
584  ele.append("\t\t<th>").append(title).append("</th>\n"); //NON-NLS
585  }
586  ele.append("\t</tr>\n</thead>\n"); //NON-NLS
587 
588  try {
589  out.write(ele.toString());
590  } catch (IOException ex) {
591  logger.log(Level.SEVERE, "Failed to write table start: {0}", ex); //NON-NLS
592  }
593  }
594 
602  public void startContentTagsTable(List<String> columnHeaders) {
603  StringBuilder htmlOutput = new StringBuilder();
604  htmlOutput.append("<table>\n<thead>\n\t<tr>\n"); //NON-NLS
605 
606  // Add the specified columns.
607  for (String columnHeader : columnHeaders) {
608  htmlOutput.append("\t\t<th>").append(columnHeader).append("</th>\n"); //NON-NLS
609  }
610 
611  // Add a column for a hyperlink to a local copy of the tagged content.
612  htmlOutput.append("\t\t<th></th>\n"); //NON-NLS
613 
614  htmlOutput.append("\t</tr>\n</thead>\n"); //NON-NLS
615 
616  try {
617  out.write(htmlOutput.toString());
618  } catch (IOException ex) {
619  logger.log(Level.SEVERE, "Failed to write table start: {0}", ex); //NON-NLS
620  }
621  }
622 
626  @Override
627  public void endTable() {
628  try {
629  out.write("</table>\n"); //NON-NLS
630  } catch (IOException ex) {
631  logger.log(Level.SEVERE, "Failed to write end of table: {0}", ex); //NON-NLS
632  }
633  }
634 
641  @Override
642  public void addRow(List<String> row) {
643  addRow(row, true);
644  }
645 
653  private void addRow(List<String> row, boolean escapeText) {
654  StringBuilder builder = new StringBuilder();
655  builder.append("\t<tr>\n"); //NON-NLS
656  for (String cell : row) {
657  String cellText = escapeText ? EscapeUtil.escapeHtml(cell) : cell;
658  builder.append("\t\t<td>").append(cellText).append("</td>\n"); //NON-NLS
659  }
660  builder.append("\t</tr>\n"); //NON-NLS
661  rowCount++;
662 
663  try {
664  out.write(builder.toString());
665  } catch (IOException ex) {
666  logger.log(Level.SEVERE, "Failed to write row to out.", ex); //NON-NLS
667  } catch (NullPointerException ex) {
668  logger.log(Level.SEVERE, "Output writer is null. Page was not initialized before writing.", ex); //NON-NLS
669  }
670  }
671 
682  public void addRowWithTaggedContentHyperlink(List<String> row, ContentTag contentTag) {
683  Content content = contentTag.getContent();
684  if (content instanceof AbstractFile == false) {
685  addRow(row, true);
686  return;
687  }
688  AbstractFile file = (AbstractFile) content;
689  // Add the hyperlink to the row. A column header for it was created in startTable().
690  StringBuilder localFileLink = new StringBuilder();
691  // Don't make a local copy of the file if it is a directory or unallocated space.
692  if (!(file.isDir()
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=\""); //NON-NLS
696  // save it in a folder based on the tag name
697  String localFilePath = saveContent(file, contentTag.getName().getDisplayName());
698  localFileLink.append(localFilePath);
699  localFileLink.append("\" target=\"_top\">");
700  }
701 
702  StringBuilder builder = new StringBuilder();
703  builder.append("\t<tr>\n"); //NON-NLS
704  int positionCounter = 0;
705  for (String cell : row) {
706  // position-dependent code used to format this report. Not great, but understandable for formatting.
707  switch (positionCounter) {
708  case 1:
709  // Convert the file name to a hyperlink and left-align it
710  builder.append("\t\t<td class=\"left_align_cell\">").append(localFileLink.toString()).append(cell).append("</a></td>\n"); //NON-NLS
711  break;
712  case 7:
713  // Right-align the bytes column.
714  builder.append("\t\t<td class=\"right_align_cell\">").append(cell).append("</td>\n"); //NON-NLS
715  break;
716  default:
717  // Regular case, not a file name nor a byte count
718  builder.append("\t\t<td>").append(cell).append("</td>\n"); //NON-NLS
719  break;
720  }
721  ++positionCounter;
722  }
723  builder.append("\t</tr>\n"); //NON-NLS
724  rowCount++;
725 
726  try {
727  out.write(builder.toString());
728  } catch (IOException ex) {
729  logger.log(Level.SEVERE, "Failed to write row to out.", ex); //NON-NLS
730  } catch (NullPointerException ex) {
731  logger.log(Level.SEVERE, "Output writer is null. Page was not initialized before writing.", ex); //NON-NLS
732  }
733  }
734 
740  public void addThumbnailRows(Set<Content> images) {
741  List<String> currentRow = new ArrayList<>();
742  int totalCount = 0;
743  int pages = 1;
744  for (Content content : images) {
745  if (currentRow.size() == THUMBNAIL_COLUMNS) {
746  addRow(currentRow, false);
747  currentRow.clear();
748  }
749 
750  if (totalCount == MAX_THUMBS_PER_PAGE) {
751  // manually set the row count so the count of items shown in the
752  // navigation page reflects the number of thumbnails instead of
753  // the number of rows.
754  rowCount = totalCount;
755  totalCount = 0;
756  pages++;
757  endTable();
758  endDataType();
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("");
764  }
765  startTable(emptyHeaders);
766  }
767 
768  if (failsContentCheck(content)) {
769  continue;
770  }
771 
772  AbstractFile file = (AbstractFile) content;
773 
774  // save copies of the orginal image and thumbnail image
775  String thumbnailPath = prepareThumbnail(file);
776  if (thumbnailPath == null) {
777  continue;
778  }
779  String contentPath = saveContent(file, "thumbs_fullsize"); //NON-NLS
780  String nameInImage;
781  try {
782  nameInImage = file.getUniquePath();
783  } catch (TskCoreException ex) {
784  nameInImage = file.getName();
785  }
786 
787  StringBuilder linkToThumbnail = new StringBuilder();
788  linkToThumbnail.append("<div id='thumbnail_link'>");
789  linkToThumbnail.append("<a href=\""); //NON-NLS
790  linkToThumbnail.append(contentPath);
791  linkToThumbnail.append("\" target=\"_top\">");
792  linkToThumbnail.append("<img src=\"").append(thumbnailPath).append("\" title=\"").append(nameInImage).append("\"/>"); //NON-NLS
793  linkToThumbnail.append("</a><br>"); //NON-NLS
794  linkToThumbnail.append(file.getName()).append("<br>"); //NON-NLS
795 
796  Services services = currentCase.getServices();
797  TagsManager tagsManager = services.getTagsManager();
798  try {
799  List<ContentTag> tags = tagsManager.getContentTagsByContent(content);
800  if (tags.size() > 0) {
801  linkToThumbnail.append(NbBundle.getMessage(this.getClass(), "ReportHTML.thumbLink.tags"));
802  }
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(", ");
809  }
810  }
811  } catch (TskCoreException ex) {
812  logger.log(Level.WARNING, "Could not find get tags for file.", ex); //NON-NLS
813  }
814  linkToThumbnail.append("</div>");
815  currentRow.add(linkToThumbnail.toString());
816 
817  totalCount++;
818  }
819 
820  if (currentRow.isEmpty() == false) {
821  int extraCells = THUMBNAIL_COLUMNS - currentRow.size();
822  for (int i = 0; i < extraCells; i++) {
823  // Finish out the row.
824  currentRow.add("");
825  }
826  addRow(currentRow, false);
827  }
828 
829  // manually set rowCount to be the total number of images.
830  rowCount = totalCount;
831  }
832 
833  private boolean failsContentCheck(Content c) {
834  if (c instanceof AbstractFile == false) {
835  return true;
836  }
837  AbstractFile file = (AbstractFile) c;
838  return file.isDir()
839  || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS
840  || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS;
841  }
842 
852  public String saveContent(AbstractFile file, String dirName) {
853  // clean up the dir name passed in
854  String dirName2 = org.sleuthkit.autopsy.coreutils.FileUtil.escapeFileName(dirName);
855 
856  // Make a folder for the local file with the same tagName as the tag.
857  StringBuilder localFilePath = new StringBuilder(); // full path
858 
859  localFilePath.append(subPath);
860  localFilePath.append(dirName2);
861  File localFileFolder = new File(localFilePath.toString());
862  if (!localFileFolder.exists()) {
863  localFileFolder.mkdirs();
864  }
865 
866  /*
867  * Construct a file tagName for the local file that incorporates the
868  * file ID to ensure uniqueness.
869  *
870  * Note: File name is normalized to account for possible attribute name
871  * which will be separated by a ':' character.
872  */
873  String fileName = org.sleuthkit.autopsy.coreutils.FileUtil.escapeFileName(file.getName());
874  String objectIdSuffix = "_" + file.getId();
875  int lastDotIndex = fileName.lastIndexOf(".");
876  if (lastDotIndex != -1 && lastDotIndex != 0) {
877  // The file tagName has a conventional extension. Insert the object id before the '.' of the extension.
878  fileName = fileName.substring(0, lastDotIndex) + objectIdSuffix + fileName.substring(lastDotIndex, fileName.length());
879  } else {
880  // The file has no extension or the only '.' in the file is an initial '.', as in a hidden file.
881  // Add the object id to the end of the file tagName.
882  fileName += objectIdSuffix;
883  }
884  localFilePath.append(File.separator);
885  localFilePath.append(fileName);
886 
887  // If the local file doesn't already exist, create it now.
888  // The existence check is necessary because it is possible to apply multiple tags with the same tagName to a file.
889  File localFile = new File(localFilePath.toString());
890  if (!localFile.exists()) {
891  ExtractFscContentVisitor.extract(file, localFile, null, null);
892  }
893 
894  // get the relative path
895  return localFilePath.toString().substring(subPath.length());
896  }
897 
905  @Override
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));
909  }
910 
911  @Override
912  public String getRelativeFilePath() {
913  return "report.html"; //NON-NLS
914  }
915 
916  @Override
917  public String getName() {
918  return NbBundle.getMessage(this.getClass(), "ReportHTML.getName.text");
919  }
920 
921  @Override
922  public String getDescription() {
923  return NbBundle.getMessage(this.getClass(), "ReportHTML.getDesc.text");
924  }
925 
929  private void writeCss() {
930  Writer cssOut = null;
931  try {
932  cssOut = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(subPath + "index.css"), "UTF-8")); //NON-NLS NON-NLS
933  String css = "body {margin: 0px; padding: 0px; background: #FFFFFF; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353;}\n"
934  + //NON-NLS
935  "#content {padding: 30px;}\n"
936  + //NON-NLS
937  "#header {width:100%; padding: 10px; line-height: 25px; background: #07A; color: #FFF; font-size: 20px;}\n"
938  + //NON-NLS
939  "#pageHeaderFooter {width: 100%; padding: 10px; line-height: 25px; text-align: center; font-size: 20px;}\n"
940  + //NON-NLS
941  "h1 {font-size: 20px; font-weight: normal; color: #07A; padding: 0 0 7px 0; margin-top: 25px; border-bottom: 1px solid #D6D6D6;}\n"
942  + //NON-NLS
943  "h2 {font-size: 20px; font-weight: bolder; color: #07A;}\n"
944  + //NON-NLS
945  "h3 {font-size: 16px; color: #07A;}\n"
946  + //NON-NLS
947  "h4 {background: #07A; color: #FFF; font-size: 16px; margin: 0 0 0 25px; padding: 0; padding-left: 15px;}\n"
948  + //NON-NLS
949  "ul.nav {list-style-type: none; line-height: 35px; padding: 0px; margin-left: 15px;}\n"
950  + //NON-NLS
951  "ul li a {font-size: 14px; color: #444; text-decoration: none; padding-left: 25px;}\n"
952  + //NON-NLS
953  "ul li a:hover {text-decoration: underline;}\n"
954  + //NON-NLS
955  "p {margin: 0 0 20px 0;}\n"
956  + //NON-NLS
957  "table {white-space:nowrap; min-width: 700px; padding: 2; margin: 0; border-collapse: collapse; border-bottom: 2px solid #e5e5e5;}\n"
958  + //NON-NLS
959  ".keyword_list table {margin: 0 0 25px 25px; border-bottom: 2px solid #dedede;}\n"
960  + //NON-NLS
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"
962  + //NON-NLS
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"
964  + //NON-NLS
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"
966  + //NON-NLS
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"
968  + //NON-NLS
969  "table tr:nth-child(even) td {background: #f3f3f3;}\n"
970  + //NON-NLS
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;}";
972  cssOut.write(css);
973  } catch (FileNotFoundException ex) {
974  logger.log(Level.SEVERE, "Could not find index.css file to write to.", ex); //NON-NLS
975  } catch (UnsupportedEncodingException ex) {
976  logger.log(Level.SEVERE, "Did not recognize encoding when writing index.css.", ex); //NON-NLS
977  } catch (IOException ex) {
978  logger.log(Level.SEVERE, "Error creating Writer for index.css.", ex); //NON-NLS
979  } finally {
980  try {
981  if (cssOut != null) {
982  cssOut.flush();
983  cssOut.close();
984  }
985  } catch (IOException ex) {
986  }
987  }
988  }
989 
993  private void writeIndex() {
994  Writer indexOut = null;
995  String indexFilePath = path + "report.html"; //NON-NLS
996  Case openCase;
997  try {
998  openCase = Case.getCurrentCaseThrows();
999  } catch (NoCurrentCaseException ex) {
1000  logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS
1001  return;
1002  }
1003  try {
1004  indexOut = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(indexFilePath), "UTF-8")); //NON-NLS
1005  StringBuilder index = new StringBuilder();
1006  final String reportTitle = reportBranding.getReportTitle();
1007  String iconPath = reportBranding.getAgencyLogoPath();
1008  if (iconPath == null) {
1009  // use default Autopsy icon if custom icon is not set
1010  iconPath = HTML_SUBDIR + "favicon.ico";
1011  } else {
1012  iconPath = Paths.get(reportBranding.getAgencyLogoPath()).getFileName().toString(); //ref to writeNav() for agency_logo
1013  }
1014  index.append("<head>\n<title>").append(reportTitle).append(" ").append(
1015  NbBundle.getMessage(this.getClass(), "ReportHTML.writeIndex.title", currentCase.getDisplayName())).append(
1016  "</title>\n"); //NON-NLS
1017  index.append("<link rel=\"icon\" type=\"image/ico\" href=\"")
1018  .append(iconPath).append("\" />\n"); //NON-NLS
1019  index.append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"); //NON-NLS
1020  index.append("</head>\n"); //NON-NLS
1021  index.append("<frameset cols=\"350px,*\">\n"); //NON-NLS
1022  index.append("<frame src=\"" + HTML_SUBDIR).append(File.separator).append("nav.html\" name=\"nav\">\n"); //NON-NLS
1023  index.append("<frame src=\"" + HTML_SUBDIR).append(File.separator).append("summary.html\" name=\"content\">\n"); //NON-NLS
1024  index.append("<noframes>").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeIndex.noFrames.msg")).append("<br />\n"); //NON-NLS
1025  index.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeIndex.noFrames.seeNav")).append("<br />\n"); //NON-NLS
1026  index.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeIndex.seeSum")).append("</noframes>\n"); //NON-NLS
1027  index.append("</frameset>\n"); //NON-NLS
1028  index.append("</html>"); //NON-NLS
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); //NON-NLS
1034  } catch (TskCoreException ex) {
1035  String errorMessage = String.format("Error adding %s to case as a report", indexFilePath); //NON-NLS
1036  logger.log(Level.SEVERE, errorMessage, ex);
1037  } finally {
1038  try {
1039  if (indexOut != null) {
1040  indexOut.flush();
1041  indexOut.close();
1042  }
1043  } catch (IOException ex) {
1044  }
1045  }
1046  }
1047 
1051  private void writeNav() {
1052  Writer navOut = null;
1053  try {
1054  navOut = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(subPath + "nav.html"), "UTF-8")); //NON-NLS
1055  StringBuilder nav = new StringBuilder();
1056  nav.append("<html>\n<head>\n\t<title>").append( //NON-NLS
1057  NbBundle.getMessage(this.getClass(), "ReportHTML.writeNav.title"))
1058  .append("</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"index.css\" />\n"); //NON-NLS
1059  nav.append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n</head>\n<body>\n"); //NON-NLS
1060  nav.append("<div id=\"content\">\n<h1>").append( //NON-NLS
1061  NbBundle.getMessage(this.getClass(), "ReportHTML.writeNav.h1")).append("</h1>\n"); //NON-NLS
1062  nav.append("<ul class=\"nav\">\n"); //NON-NLS
1063  nav.append("<li style=\"background: url(summary.png) left center no-repeat;\"><a href=\"summary.html\" target=\"content\">") //NON-NLS
1064  .append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeNav.summary")).append("</a></li>\n"); //NON-NLS
1065 
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) //NON-NLS
1070  .append("') left center no-repeat;\"><a href=\"") //NON-NLS
1071  .append(dataTypeEsc).append(".html\" target=\"content\">") //NON-NLS
1072  .append(dataType).append(" (").append(dataTypes.get(dataType))
1073  .append(")</a></li>\n"); //NON-NLS
1074  }
1075  nav.append("</ul>\n"); //NON-NLS
1076  nav.append("</div>\n</body>\n</html>"); //NON-NLS
1077  navOut.write(nav.toString());
1078  } catch (IOException ex) {
1079  logger.log(Level.SEVERE, "Failed to write end of report navigation menu: {0}", ex); //NON-NLS
1080  } finally {
1081  if (navOut != null) {
1082  try {
1083  navOut.flush();
1084  navOut.close();
1085  } catch (IOException ex) {
1086  logger.log(Level.WARNING, "Could not close navigation out writer."); //NON-NLS
1087  }
1088  }
1089  }
1090 
1091  InputStream in = null;
1092  OutputStream output = null;
1093  try {
1094 
1095  //pull generator and agency logo from branding, and the remaining resources from the core jar
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"); //NON-NLS
1101  }
1102 
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())); //NON-NLS
1107  }
1108 
1109  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/favicon.ico"); //NON-NLS
1110  output = new FileOutputStream(new File(subPath + "favicon.ico"));
1111  FileUtil.copy(in, output);
1112  in.close();
1113  output.close();
1114 
1115  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/summary.png"); //NON-NLS
1116  output = new FileOutputStream(new File(subPath + "summary.png"));
1117  FileUtil.copy(in, output);
1118  in.close();
1119  output.close();
1120 
1121  } catch (IOException ex) {
1122  logger.log(Level.SEVERE, "Failed to extract images for HTML report.", ex); //NON-NLS
1123  } finally {
1124  if (output != null) {
1125  try {
1126  output.flush();
1127  output.close();
1128  } catch (IOException ex) {
1129  }
1130  }
1131  if (in != null) {
1132  try {
1133  in.close();
1134  } catch (IOException ex) {
1135  }
1136  }
1137  }
1138  }
1139 
1143  private void writeSummary() {
1144  Writer output = null;
1145  try {
1146  output = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(subPath + "summary.html"), "UTF-8")); //NON-NLS
1147  StringBuilder head = new StringBuilder();
1148  head.append("<html>\n<head>\n<title>").append( //NON-NLS
1149  NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.title")).append("</title>\n"); //NON-NLS
1150  head.append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"); //NON-NLS
1151  head.append("<style type=\"text/css\">\n"); //NON-NLS
1152  head.append("#pageHeaderFooter {width: 100%; padding: 10px; line-height: 25px; text-align: center; font-size: 20px;}\n"); //NON-NLS
1153  head.append("body { padding: 0px; margin: 0px; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353; }\n"); //NON-NLS
1154  head.append("#wrapper { width: 90%; margin: 0px auto; margin-top: 35px; }\n"); //NON-NLS
1155  head.append("h1 { color: #07A; font-size: 36px; line-height: 42px; font-weight: normal; margin: 0px; border-bottom: 1px solid #81B9DB; }\n"); //NON-NLS
1156  head.append("h1 span { color: #F00; display: block; font-size: 16px; font-weight: bold; line-height: 22px;}\n"); //NON-NLS
1157  head.append("h2 { padding: 0 0 3px 0; margin: 0px; color: #07A; font-weight: normal; border-bottom: 1px dotted #81B9DB; }\n"); //NON-NLS
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"); //NON-NLS
1160  head.append("p.subheadding { padding: 0px; margin: 0px; font-size: 11px; color: #B5B5B5; }\n"); //NON-NLS
1161  head.append(".title { width: 660px; margin-bottom: 50px; }\n"); //NON-NLS
1162  head.append(".left { float: left; width: 250px; margin-top: 20px; text-align: center; }\n"); //NON-NLS
1163  head.append(".left img { max-width: 250px; max-height: 250px; min-width: 200px; min-height: 200px; }\n"); //NON-NLS
1164  head.append(".right { float: right; width: 385px; margin-top: 25px; font-size: 14px; }\n"); //NON-NLS
1165  head.append(".clear { clear: both; }\n"); //NON-NLS
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"); //NON-NLS
1168  head.append(".info table { margin: 10px 25px 10px 25px; }\n"); //NON-NLS
1169  head.append("ul {padding: 0;margin: 0;list-style-type: none;}");
1170  head.append("li {padding-bottom: 5px;}");
1171  head.append("</style>\n"); //NON-NLS
1172  head.append("</head>\n<body>\n"); //NON-NLS
1173  output.write(head.toString());
1174 
1175  DateFormat datetimeFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
1176  Date date = new Date();
1177  String datetime = datetimeFormat.format(date);
1178 
1179  StringBuilder summary = new StringBuilder();
1180  boolean running = false;
1181  if (IngestManager.getInstance().isIngestRunning()) {
1182  running = true;
1183  }
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();
1189 
1190  summary.append("<div id=\"wrapper\">\n"); //NON-NLS
1191  summary.append(writePageHeader());
1192  summary.append("<h1>").append(reportTitle) //NON-NLS
1193  .append(running ? NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.warningMsg") : "")
1194  .append("</h1>\n"); //NON-NLS
1195  summary.append("<p class=\"subheadding\">").append( //NON-NLS
1196  NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.reportGenOn.text", datetime)).append("</p>\n"); //NON-NLS
1197  summary.append("<div class=\"title\">\n"); //NON-NLS
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"); //NON-NLS
1204  summary.append("<img src=\"generator_logo.png\" />\n"); //NON-NLS
1205  summary.append("</div>\n"); //NON-NLS
1206  }
1207  summary.append("<div class=\"clear\"></div>\n"); //NON-NLS
1208  if (reportFooter != null) {
1209  summary.append("<p class=\"subheadding\">").append(reportFooter).append("</p>\n"); //NON-NLS
1210  }
1211  summary.append("</div>\n"); //NON-NLS
1212  summary.append(writePageFooter());
1213  summary.append("</body></html>"); //NON-NLS
1214  output.write(summary.toString());
1215  } catch (FileNotFoundException ex) {
1216  logger.log(Level.SEVERE, "Could not find summary.html file to write to."); //NON-NLS
1217  } catch (UnsupportedEncodingException ex) {
1218  logger.log(Level.SEVERE, "Did not recognize encoding when writing summary.hmtl."); //NON-NLS
1219  } catch (IOException ex) {
1220  logger.log(Level.SEVERE, "Error creating Writer for summary.html."); //NON-NLS
1221  } catch (NoCurrentCaseException | TskCoreException ex) {
1222  logger.log(Level.WARNING, "Unable to get current sleuthkit Case for the HTML report.");
1223  } finally {
1224  try {
1225  if (output != null) {
1226  output.flush();
1227  output.close();
1228  }
1229  } catch (IOException ex) {
1230  }
1231  }
1232  }
1233 
1234  @Messages({
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:"
1240  })
1246  private StringBuilder writeSummaryCaseDetails() {
1247  StringBuilder summary = new StringBuilder();
1248 
1249  final boolean agencyLogoSet = reportBranding.getAgencyLogoPath() != null && !reportBranding.getAgencyLogoPath().isEmpty();
1250 
1251  // Case
1252  String caseName = currentCase.getDisplayName();
1253  String caseNumber = currentCase.getNumber();
1254  int imagecount;
1255  try {
1256  imagecount = currentCase.getDataSources().size();
1257  } catch (TskCoreException ex) {
1258  imagecount = 0;
1259  }
1260  String caseNotes = currentCase.getCaseNotes();
1261 
1262  // Examiner
1263  String examinerName = currentCase.getExaminer();
1264 
1265  // Start the layout.
1266  summary.append("<div class=\"title\">\n"); //NON-NLS
1267  if (agencyLogoSet) {
1268  summary.append("<div class=\"left\">\n"); //NON-NLS
1269  summary.append("<img src=\"");
1270  summary.append(Paths.get(reportBranding.getAgencyLogoPath()).getFileName().toString());
1271  summary.append("\" />\n"); //NON-NLS
1272  summary.append("</div>\n"); //NON-NLS
1273  }
1274  final String align = agencyLogoSet ? "right" : "left"; //NON-NLS NON-NLS
1275  summary.append("<div class=\"").append(align).append("\">\n"); //NON-NLS
1276  summary.append("<table>\n"); //NON-NLS
1277 
1278  // Case details
1279  summary.append("<tr><td>").append(Bundle.ReportHTML_writeSum_case()).append("</td><td>") //NON-NLS
1280  .append(formatHtmlString(caseName)).append("</td></tr>\n"); //NON-NLS
1281 
1282  if (!caseNumber.isEmpty()) {
1283  summary.append("<tr><td>").append(Bundle.ReportHTML_writeSum_caseNumber()).append("</td><td>") //NON-NLS
1284  .append(formatHtmlString(caseNumber)).append("</td></tr>\n"); //NON-NLS
1285  }
1286 
1287  summary.append("<tr><td>").append(Bundle.ReportHTML_writeSum_caseNumImages()).append("</td><td>") //NON-NLS
1288  .append(imagecount).append("</td></tr>\n"); //NON-NLS
1289 
1290  if (!caseNotes.isEmpty()) {
1291  summary.append("<tr><td>").append(Bundle.ReportHTML_writeSum_caseNotes()).append("</td><td>") //NON-NLS
1292  .append(formatHtmlString(caseNotes)).append("</td></tr>\n"); //NON-NLS
1293  }
1294 
1295  // Examiner details
1296  if (!examinerName.isEmpty()) {
1297  summary.append("<tr><td>").append(Bundle.ReportHTML_writeSum_examiner()).append("</td><td>") //NON-NLS
1298  .append(formatHtmlString(examinerName)).append("</td></tr>\n"); //NON-NLS
1299  }
1300 
1301  // End the layout.
1302  summary.append("</table>\n"); //NON-NLS
1303  summary.append("</div>\n"); //NON-NLS
1304  summary.append("<div class=\"clear\"></div>\n"); //NON-NLS
1305  summary.append("</div>\n"); //NON-NLS
1306  return summary;
1307  }
1308 
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"); //NON-NLS
1318  try {
1319  for (Content c : currentCase.getDataSources()) {
1320  summary.append("<p>").append(c.getName()).append("</p>\n"); //NON-NLS
1321  if (c instanceof Image) {
1322  Image img = (Image) c;
1323 
1324  summary.append("<table>\n"); //NON-NLS
1325  summary.append("<tr><td>").append( //NON-NLS
1326  NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.timezone"))
1327  .append("</td><td>").append(img.getTimeZone()).append("</td></tr>\n"); //NON-NLS
1328  for (String imgPath : img.getPaths()) {
1329  summary.append("<tr><td>").append( //NON-NLS
1330  NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.path"))
1331  .append("</td><td>").append(imgPath).append("</td></tr>\n"); //NON-NLS
1332  }
1333  summary.append("</table>\n"); //NON-NLS
1334  }
1335  }
1336  } catch (TskCoreException ex) {
1337  logger.log(Level.WARNING, "Unable to get image information for the HTML report."); //NON-NLS
1338  }
1339  summary.append("</div>\n"); //NON-NLS
1340  return summary;
1341  }
1342 
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);
1361  }
1362  }
1363  }
1364  TreeMap<String, String> modules = new TreeMap<>();
1365  for (IngestModuleInfo moduleinfo : moduleInfoHashMap.values()) {
1366  modules.put(moduleinfo.getDisplayName(), moduleinfo.getVersion());
1367  }
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");
1371  }
1372  summary.append("</table>\n");
1373  summary.append("</div>\n");
1374  summary.append("<div class=\"clear\"></div>\n"); //NON-NLS
1375  return summary;
1376  }
1377 
1383  private StringBuilder writeSummaryIngestHistoryInfo(SleuthkitCase skCase, List<IngestJobInfo> ingestJobs) {
1384  StringBuilder summary = new StringBuilder();
1385  try {
1386  summary.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.ingestHistoryHeading"));
1387  summary.append("<div class=\"info\">\n");
1388  int jobnumber = 1;
1389 
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>");
1403  }
1404  summary.append("</ul>\n");
1405  jobnumber++;
1406  summary.append("</td></tr>\n");
1407  summary.append("</table>\n");
1408  }
1409  summary.append("</div>\n");
1410  } catch (TskCoreException ex) {
1411  logger.log(Level.WARNING, "Unable to get ingest jobs for the HTML report.");
1412  }
1413  return summary;
1414  }
1415 
1424  private String prepareThumbnail(AbstractFile file) {
1425  BufferedImage bufferedThumb = ImageUtils.getThumbnail(file, ImageUtils.ICON_SIZE_MEDIUM);
1426 
1427  /*
1428  * File name is normalized to account for possible attribute name which
1429  * will be separated by a ':' character.
1430  */
1431  String fileName = org.sleuthkit.autopsy.coreutils.FileUtil.escapeFileName(file.getName());
1432 
1433  File thumbFile = Paths.get(thumbsPath, fileName + ".png").toFile();
1434  if (bufferedThumb == null) {
1435  return null;
1436  }
1437  try {
1438  ImageIO.write(bufferedThumb, "png", thumbFile);
1439  } catch (IOException ex) {
1440  logger.log(Level.WARNING, "Failed to write thumb file to report directory.", ex); //NON-NLS
1441  return null;
1442  }
1443  if (thumbFile.exists()
1444  == false) {
1445  return null;
1446  }
1447  return THUMBS_REL_PATH
1448  + thumbFile.getName();
1449  }
1450 
1459  private String formatHtmlString(String text) {
1460  String formattedString = StringEscapeUtils.escapeHtml4(text);
1461  return formattedString.replaceAll("(\r\n|\r|\n|\n\r)", "<br>");
1462  }
1463 
1464 }
static String escapeFileName(String fileName)
Definition: FileUtil.java:169

Copyright © 2012-2018 Basis Technology. Generated on: Fri Mar 22 2019
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.