Autopsy  4.9.1
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;
65 import org.sleuthkit.datamodel.AbstractFile;
66 import org.sleuthkit.datamodel.BlackboardArtifact;
67 import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
68 import org.sleuthkit.datamodel.Content;
69 import org.sleuthkit.datamodel.ContentTag;
70 import org.sleuthkit.datamodel.Image;
71 import org.sleuthkit.datamodel.IngestJobInfo;
72 import org.sleuthkit.datamodel.IngestModuleInfo;
73 import org.sleuthkit.datamodel.SleuthkitCase;
74 import org.sleuthkit.datamodel.TskCoreException;
75 import org.sleuthkit.datamodel.TskData;
76 import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM;
77 
78 class ReportHTML implements TableReportModule {
79 
80  private static final Logger logger = Logger.getLogger(ReportHTML.class.getName());
81  private static final String THUMBS_REL_PATH = "thumbs" + File.separator; //NON-NLS
82  private static ReportHTML instance;
83  private static final int MAX_THUMBS_PER_PAGE = 1000;
84  private static final String HTML_SUBDIR = "content";
85  private Case currentCase;
86  static Integer THUMBNAIL_COLUMNS = 5;
87 
88  private Map<String, Integer> dataTypes;
89  private String path;
90  private String thumbsPath;
91  private String subPath;
92  private String currentDataType; // name of current data type
93  private Integer rowCount; // number of rows (aka artifacts or tags) for the current data type
94  private Writer out;
95 
96  private ReportHTMLConfigurationPanel configPanel;
97 
98  private final ReportBranding reportBranding;
99 
100  // Get the default instance of this report
101  public static synchronized ReportHTML getDefault() {
102  if (instance == null) {
103  instance = new ReportHTML();
104  }
105  return instance;
106  }
107 
108  // Hidden constructor
109  private ReportHTML() {
110  reportBranding = new ReportBranding();
111  }
112 
113  @Override
114  public JPanel getConfigurationPanel() {
115  if (configPanel == null) {
116  configPanel = new ReportHTMLConfigurationPanel();
117  }
118  return configPanel;
119  }
120 
121  // Refesh the member variables
122  private void refresh() throws NoCurrentCaseException {
123  currentCase = Case.getCurrentCaseThrows();
124 
125  dataTypes = new TreeMap<>();
126 
127  path = "";
128  thumbsPath = "";
129  subPath = "";
130  currentDataType = "";
131  rowCount = 0;
132 
133  if (out != null) {
134  try {
135  out.close();
136  } catch (IOException ex) {
137  }
138  }
139  out = null;
140  }
141 
148  private String dataTypeToFileName(String dataType) {
149 
150  String fileName = org.sleuthkit.autopsy.coreutils.FileUtil.escapeFileName(dataType);
151  // replace all ' ' with '_'
152  fileName = fileName.replaceAll(" ", "_");
153 
154  return fileName;
155  }
156 
161  private String useDataTypeIcon(String dataType) {
162  String iconFilePath;
163  String iconFileName;
164  InputStream in;
165  OutputStream output = null;
166 
167  logger.log(Level.INFO, "useDataTypeIcon: dataType = {0}", dataType); //NON-NLS
168 
169  // find the artifact with matching display name
170  BlackboardArtifact.ARTIFACT_TYPE artifactType = null;
171  for (ARTIFACT_TYPE v : ARTIFACT_TYPE.values()) {
172  if (v.getDisplayName().equals(dataType)) {
173  artifactType = v;
174  }
175  }
176 
177  if (null != artifactType) {
178  // set the icon file name
179  iconFileName = dataTypeToFileName(artifactType.getDisplayName()) + ".png"; //NON-NLS
180  iconFilePath = subPath + File.separator + iconFileName;
181 
182  // determine the source image to use
183  switch (artifactType) {
184  case TSK_WEB_BOOKMARK:
185  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/bookmarks.png"); //NON-NLS
186  break;
187  case TSK_WEB_COOKIE:
188  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/cookies.png"); //NON-NLS
189  break;
190  case TSK_WEB_HISTORY:
191  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/history.png"); //NON-NLS
192  break;
193  case TSK_WEB_DOWNLOAD:
194  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/downloads.png"); //NON-NLS
195  break;
196  case TSK_RECENT_OBJECT:
197  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/recent.png"); //NON-NLS
198  break;
199  case TSK_INSTALLED_PROG:
200  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/installed.png"); //NON-NLS
201  break;
202  case TSK_KEYWORD_HIT:
203  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/keywords.png"); //NON-NLS
204  break;
205  case TSK_HASHSET_HIT:
206  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/hash.png"); //NON-NLS
207  break;
208  case TSK_DEVICE_ATTACHED:
209  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/devices.png"); //NON-NLS
210  break;
211  case TSK_WEB_SEARCH_QUERY:
212  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/search.png"); //NON-NLS
213  break;
214  case TSK_METADATA_EXIF:
215  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/exif.png"); //NON-NLS
216  break;
217  case TSK_TAG_FILE:
218  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/userbookmarks.png"); //NON-NLS
219  break;
220  case TSK_TAG_ARTIFACT:
221  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/userbookmarks.png"); //NON-NLS
222  break;
223  case TSK_SERVICE_ACCOUNT:
224  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/account-icon-16.png"); //NON-NLS
225  break;
226  case TSK_CONTACT:
227  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/contact.png"); //NON-NLS
228  break;
229  case TSK_MESSAGE:
230  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/message.png"); //NON-NLS
231  break;
232  case TSK_CALLLOG:
233  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/calllog.png"); //NON-NLS
234  break;
235  case TSK_CALENDAR_ENTRY:
236  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/calendar.png"); //NON-NLS
237  break;
238  case TSK_SPEED_DIAL_ENTRY:
239  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/speeddialentry.png"); //NON-NLS
240  break;
241  case TSK_BLUETOOTH_PAIRING:
242  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/bluetooth.png"); //NON-NLS
243  break;
244  case TSK_GPS_BOOKMARK:
245  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/gpsfav.png"); //NON-NLS
246  break;
247  case TSK_GPS_LAST_KNOWN_LOCATION:
248  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/gps-lastlocation.png"); //NON-NLS
249  break;
250  case TSK_GPS_SEARCH:
251  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/gps-search.png"); //NON-NLS
252  break;
253  case TSK_OS_INFO:
254  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/computer.png"); //NON-NLS
255  break;
256  case TSK_GPS_TRACKPOINT:
257  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/gps_trackpoint.png"); //NON-NLS
258  break;
259  case TSK_GPS_ROUTE:
260  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/gps_trackpoint.png"); //NON-NLS
261  break;
262  case TSK_EMAIL_MSG:
263  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/mail-icon-16.png"); //NON-NLS
264  break;
265  case TSK_ENCRYPTION_SUSPECTED:
266  case TSK_ENCRYPTION_DETECTED:
267  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/encrypted-file.png"); //NON-NLS
268  break;
269  case TSK_EXT_MISMATCH_DETECTED:
270  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/mismatch-16.png"); //NON-NLS
271  break;
272  case TSK_INTERESTING_ARTIFACT_HIT:
273  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/interesting_item.png"); //NON-NLS
274  break;
275  case TSK_INTERESTING_FILE_HIT:
276  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/interesting_item.png"); //NON-NLS
277  break;
278  case TSK_PROG_RUN:
279  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/installed.png"); //NON-NLS
280  break;
281  case TSK_REMOTE_DRIVE:
282  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/drive_network.png"); //NON-NLS
283  break;
284  case TSK_ACCOUNT:
285  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/accounts.png"); //NON-NLS
286  break;
287  case TSK_WIFI_NETWORK:
288  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/network-wifi.png"); //NON-NLS
289  break;
290  case TSK_WIFI_NETWORK_ADAPTER:
291  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/network-wifi.png"); //NON-NLS
292  break;
293  case TSK_SIM_ATTACHED:
294  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/sim_card.png"); //NON-NLS
295  break;
296  case TSK_BLUETOOTH_ADAPTER:
297  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/Bluetooth.png"); //NON-NLS
298  break;
299  case TSK_DEVICE_INFO:
300  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/devices.png"); //NON-NLS
301  break;
302  case TSK_VERIFICATION_FAILED:
303  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/validationFailed.png"); //NON-NLS
304  break;
305  default:
306  logger.log(Level.WARNING, "useDataTypeIcon: unhandled artifact type = {0}", dataType); //NON-NLS
307  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/star.png"); //NON-NLS
308  iconFileName = "star.png"; //NON-NLS
309  iconFilePath = subPath + File.separator + iconFileName;
310  break;
311  }
312  } else if (dataType.startsWith(ARTIFACT_TYPE.TSK_ACCOUNT.getDisplayName())) {
313  /*
314  * TSK_ACCOUNT artifacts get separated by their TSK_ACCOUNT_TYPE
315  * attribute, with a synthetic compound dataType name, so they are
316  * not caught by the switch statement above. For now we just give
317  * them all the general account icon, but we could do something else
318  * in the future.
319  */
320  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/accounts.png"); //NON-NLS
321  iconFileName = "accounts.png"; //NON-NLS
322  iconFilePath = subPath + File.separator + iconFileName;
323  } else { // no defined artifact found for this dataType
324  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/star.png"); //NON-NLS
325  iconFileName = "star.png"; //NON-NLS
326  iconFilePath = subPath + File.separator + iconFileName;
327  }
328 
329  try {
330  output = new FileOutputStream(iconFilePath);
331  FileUtil.copy(in, output);
332  in.close();
333  output.close();
334  } catch (IOException ex) {
335  logger.log(Level.SEVERE, "Failed to extract images for HTML report.", ex); //NON-NLS
336  } finally {
337  if (output != null) {
338  try {
339  output.flush();
340  output.close();
341  } catch (IOException ex) {
342  }
343  }
344  if (in != null) {
345  try {
346  in.close();
347  } catch (IOException ex) {
348  }
349  }
350  }
351 
352  return iconFileName;
353  }
354 
361  @Override
362  public void startReport(String baseReportDir) {
363  // Save settings
364  ModuleSettings.setConfigSetting("HTMLReport", "header", configPanel.getHeader()); //NON-NLS
365  ModuleSettings.setConfigSetting("HTMLReport", "footer", configPanel.getFooter()); //NON-NLS
366 
367  // Refresh the HTML report
368  try {
369  refresh();
370  } catch (NoCurrentCaseException ex) {
371  logger.log(Level.SEVERE, "Exception while getting open case."); //NON-NLS
372  return;
373  }
374  // Setup the path for the HTML report
375  this.path = baseReportDir; //NON-NLS
376  this.subPath = this.path + HTML_SUBDIR + File.separator;
377  this.thumbsPath = this.subPath + THUMBS_REL_PATH; //NON-NLS
378  try {
379  FileUtil.createFolder(new File(this.subPath));
380  FileUtil.createFolder(new File(this.thumbsPath));
381  } catch (IOException ex) {
382  logger.log(Level.SEVERE, "Unable to make HTML report folder."); //NON-NLS
383  }
384  // Write the basic files
385  writeCss();
386  writeIndex();
387  writeSummary();
388  }
389 
394  @Override
395  public void endReport() {
396  writeNav();
397  if (out != null) {
398  try {
399  out.close();
400  } catch (IOException ex) {
401  logger.log(Level.WARNING, "Could not close the output writer when ending report.", ex); //NON-NLS
402  }
403  }
404  }
405 
414  @Override
415  public void startDataType(String name, String description) {
416  String title = dataTypeToFileName(name);
417  try {
418  out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(subPath + title + ".html"), "UTF-8")); //NON-NLS
419  } catch (FileNotFoundException ex) {
420  logger.log(Level.SEVERE, "File not found: {0}", ex); //NON-NLS
421  } catch (UnsupportedEncodingException ex) {
422  logger.log(Level.SEVERE, "Unrecognized encoding"); //NON-NLS
423  }
424 
425  try {
426  StringBuilder page = new StringBuilder();
427  page.append("<html>\n<head>\n\t<title>").append(name).append("</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"index.css\" />\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n</head>\n<body>\n") //NON-NLS
428  .append(writePageHeader())
429  .append("<div id=\"header\">").append(name).append("</div>\n")
430  .append("<div id=\"content\">\n"); //NON-NLS
431  if (!description.isEmpty()) {
432  page.append("<p><strong>"); //NON-NLS
433  page.append(description);
434  page.append("</strong></p>\n"); //NON-NLS
435  }
436  out.write(page.toString());
437  currentDataType = name;
438  rowCount = 0;
439  } catch (IOException ex) {
440  logger.log(Level.SEVERE, "Failed to write page head: {0}", ex); //NON-NLS
441  }
442  }
443 
448  @Override
449  public void endDataType() {
450  dataTypes.put(currentDataType, rowCount);
451  try {
452  StringBuilder builder = new StringBuilder();
453  builder.append(writePageFooter());
454  builder.append("</div>\n</body>\n</html>\n"); //NON-NLS
455  out.write(builder.toString());
456  } catch (IOException ex) {
457  logger.log(Level.SEVERE, "Failed to write end of HTML report.", ex); //NON-NLS
458  } finally {
459  if (out != null) {
460  try {
461  out.flush();
462  out.close();
463  } catch (IOException ex) {
464  logger.log(Level.WARNING, "Could not close the output writer when ending data type.", ex); //NON-NLS
465  }
466  out = null;
467  }
468  }
469  }
470 
477  private String writePageHeader() {
478  StringBuilder output = new StringBuilder();
479  String pageHeader = configPanel.getHeader();
480  if (pageHeader.isEmpty() == false) {
481  output.append("<div id=\"pageHeaderFooter\">")
482  .append(StringEscapeUtils.escapeHtml4(pageHeader))
483  .append("</div>\n"); //NON-NLS
484  }
485  return output.toString();
486  }
487 
494  private String writePageFooter() {
495  StringBuilder output = new StringBuilder();
496  String pageFooter = configPanel.getFooter();
497  if (pageFooter.isEmpty() == false) {
498  output.append("<br/><div id=\"pageHeaderFooter\">")
499  .append(StringEscapeUtils.escapeHtml4(pageFooter))
500  .append("</div>"); //NON-NLS
501  }
502  return output.toString();
503  }
504 
510  @Override
511  public void startSet(String setName) {
512  StringBuilder set = new StringBuilder();
513  set.append("<h1><a name=\"").append(setName).append("\">").append(setName).append("</a></h1>\n"); //NON-NLS
514  set.append("<div class=\"keyword_list\">\n"); //NON-NLS
515 
516  try {
517  out.write(set.toString());
518  } catch (IOException ex) {
519  logger.log(Level.SEVERE, "Failed to write set: {0}", ex); //NON-NLS
520  }
521  }
522 
526  @Override
527  public void endSet() {
528  try {
529  out.write("</div>\n"); //NON-NLS
530  } catch (IOException ex) {
531  logger.log(Level.SEVERE, "Failed to write end of set: {0}", ex); //NON-NLS
532  }
533  }
534 
540  @Override
541  public void addSetIndex(List<String> sets) {
542  StringBuilder index = new StringBuilder();
543  index.append("<ul>\n"); //NON-NLS
544  for (String set : sets) {
545  index.append("\t<li><a href=\"#").append(set).append("\">").append(set).append("</a></li>\n"); //NON-NLS
546  }
547  index.append("</ul>\n"); //NON-NLS
548  try {
549  out.write(index.toString());
550  } catch (IOException ex) {
551  logger.log(Level.SEVERE, "Failed to add set index: {0}", ex); //NON-NLS
552  }
553  }
554 
560  @Override
561  public void addSetElement(String elementName) {
562  try {
563  out.write("<h4>" + elementName + "</h4>\n"); //NON-NLS
564  } catch (IOException ex) {
565  logger.log(Level.SEVERE, "Failed to write set element: {0}", ex); //NON-NLS
566  }
567  }
568 
574  @Override
575  public void startTable(List<String> titles) {
576  StringBuilder ele = new StringBuilder();
577  ele.append("<table>\n<thead>\n\t<tr>\n"); //NON-NLS
578  for (String title : titles) {
579  ele.append("\t\t<th>").append(title).append("</th>\n"); //NON-NLS
580  }
581  ele.append("\t</tr>\n</thead>\n"); //NON-NLS
582 
583  try {
584  out.write(ele.toString());
585  } catch (IOException ex) {
586  logger.log(Level.SEVERE, "Failed to write table start: {0}", ex); //NON-NLS
587  }
588  }
589 
597  public void startContentTagsTable(List<String> columnHeaders) {
598  StringBuilder htmlOutput = new StringBuilder();
599  htmlOutput.append("<table>\n<thead>\n\t<tr>\n"); //NON-NLS
600 
601  // Add the specified columns.
602  for (String columnHeader : columnHeaders) {
603  htmlOutput.append("\t\t<th>").append(columnHeader).append("</th>\n"); //NON-NLS
604  }
605 
606  // Add a column for a hyperlink to a local copy of the tagged content.
607  htmlOutput.append("\t\t<th></th>\n"); //NON-NLS
608 
609  htmlOutput.append("\t</tr>\n</thead>\n"); //NON-NLS
610 
611  try {
612  out.write(htmlOutput.toString());
613  } catch (IOException ex) {
614  logger.log(Level.SEVERE, "Failed to write table start: {0}", ex); //NON-NLS
615  }
616  }
617 
621  @Override
622  public void endTable() {
623  try {
624  out.write("</table>\n"); //NON-NLS
625  } catch (IOException ex) {
626  logger.log(Level.SEVERE, "Failed to write end of table: {0}", ex); //NON-NLS
627  }
628  }
629 
636  @Override
637  public void addRow(List<String> row) {
638  addRow(row, true);
639  }
640 
648  private void addRow(List<String> row, boolean escapeText) {
649  StringBuilder builder = new StringBuilder();
650  builder.append("\t<tr>\n"); //NON-NLS
651  for (String cell : row) {
652  String cellText = escapeText ? EscapeUtil.escapeHtml(cell) : cell;
653  builder.append("\t\t<td>").append(cellText).append("</td>\n"); //NON-NLS
654  }
655  builder.append("\t</tr>\n"); //NON-NLS
656  rowCount++;
657 
658  try {
659  out.write(builder.toString());
660  } catch (IOException ex) {
661  logger.log(Level.SEVERE, "Failed to write row to out.", ex); //NON-NLS
662  } catch (NullPointerException ex) {
663  logger.log(Level.SEVERE, "Output writer is null. Page was not initialized before writing.", ex); //NON-NLS
664  }
665  }
666 
677  public void addRowWithTaggedContentHyperlink(List<String> row, ContentTag contentTag) {
678  Content content = contentTag.getContent();
679  if (content instanceof AbstractFile == false) {
680  addRow(row, true);
681  return;
682  }
683  AbstractFile file = (AbstractFile) content;
684  // Add the hyperlink to the row. A column header for it was created in startTable().
685  StringBuilder localFileLink = new StringBuilder();
686  // Don't make a local copy of the file if it is a directory or unallocated space.
687  if (!(file.isDir()
688  || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS
689  || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS)) {
690  localFileLink.append("<a href=\""); //NON-NLS
691  // save it in a folder based on the tag name
692  String localFilePath = saveContent(file, contentTag.getName().getDisplayName());
693  localFileLink.append(localFilePath);
694  localFileLink.append("\" target=\"_top\">");
695  }
696 
697  StringBuilder builder = new StringBuilder();
698  builder.append("\t<tr>\n"); //NON-NLS
699  int positionCounter = 0;
700  for (String cell : row) {
701  // position-dependent code used to format this report. Not great, but understandable for formatting.
702  switch (positionCounter) {
703  case 1:
704  // Convert the file name to a hyperlink and left-align it
705  builder.append("\t\t<td class=\"left_align_cell\">").append(localFileLink.toString()).append(cell).append("</a></td>\n"); //NON-NLS
706  break;
707  case 7:
708  // Right-align the bytes column.
709  builder.append("\t\t<td class=\"right_align_cell\">").append(cell).append("</td>\n"); //NON-NLS
710  break;
711  default:
712  // Regular case, not a file name nor a byte count
713  builder.append("\t\t<td>").append(cell).append("</td>\n"); //NON-NLS
714  break;
715  }
716  ++positionCounter;
717  }
718  builder.append("\t</tr>\n"); //NON-NLS
719  rowCount++;
720 
721  try {
722  out.write(builder.toString());
723  } catch (IOException ex) {
724  logger.log(Level.SEVERE, "Failed to write row to out.", ex); //NON-NLS
725  } catch (NullPointerException ex) {
726  logger.log(Level.SEVERE, "Output writer is null. Page was not initialized before writing.", ex); //NON-NLS
727  }
728  }
729 
735  public void addThumbnailRows(Set<Content> images) {
736  List<String> currentRow = new ArrayList<>();
737  int totalCount = 0;
738  int pages = 1;
739  for (Content content : images) {
740  if (currentRow.size() == THUMBNAIL_COLUMNS) {
741  addRow(currentRow, false);
742  currentRow.clear();
743  }
744 
745  if (totalCount == MAX_THUMBS_PER_PAGE) {
746  // manually set the row count so the count of items shown in the
747  // navigation page reflects the number of thumbnails instead of
748  // the number of rows.
749  rowCount = totalCount;
750  totalCount = 0;
751  pages++;
752  endTable();
753  endDataType();
754  startDataType(NbBundle.getMessage(this.getClass(), "ReportHTML.addThumbRows.dataType.title", pages),
755  NbBundle.getMessage(this.getClass(), "ReportHTML.addThumbRows.dataType.msg"));
756  List<String> emptyHeaders = new ArrayList<>();
757  for (int i = 0; i < THUMBNAIL_COLUMNS; i++) {
758  emptyHeaders.add("");
759  }
760  startTable(emptyHeaders);
761  }
762 
763  if (failsContentCheck(content)) {
764  continue;
765  }
766 
767  AbstractFile file = (AbstractFile) content;
768 
769  // save copies of the orginal image and thumbnail image
770  String thumbnailPath = prepareThumbnail(file);
771  if (thumbnailPath == null) {
772  continue;
773  }
774  String contentPath = saveContent(file, "thumbs_fullsize"); //NON-NLS
775  String nameInImage;
776  try {
777  nameInImage = file.getUniquePath();
778  } catch (TskCoreException ex) {
779  nameInImage = file.getName();
780  }
781 
782  StringBuilder linkToThumbnail = new StringBuilder();
783  linkToThumbnail.append("<div id='thumbnail_link'>");
784  linkToThumbnail.append("<a href=\""); //NON-NLS
785  linkToThumbnail.append(contentPath);
786  linkToThumbnail.append("\" target=\"_top\">");
787  linkToThumbnail.append("<img src=\"").append(thumbnailPath).append("\" title=\"").append(nameInImage).append("\"/>"); //NON-NLS
788  linkToThumbnail.append("</a><br>"); //NON-NLS
789  linkToThumbnail.append(file.getName()).append("<br>"); //NON-NLS
790 
791  Services services = currentCase.getServices();
792  TagsManager tagsManager = services.getTagsManager();
793  try {
794  List<ContentTag> tags = tagsManager.getContentTagsByContent(content);
795  if (tags.size() > 0) {
796  linkToThumbnail.append(NbBundle.getMessage(this.getClass(), "ReportHTML.thumbLink.tags"));
797  }
798  for (int i = 0; i < tags.size(); i++) {
799  ContentTag tag = tags.get(i);
800  String notableString = tag.getName().getKnownStatus() == TskData.FileKnown.BAD ? TagsManager.getNotableTagLabel() : "";
801  linkToThumbnail.append(tag.getName().getDisplayName()).append(notableString);
802  if (i != tags.size() - 1) {
803  linkToThumbnail.append(", ");
804  }
805  }
806  } catch (TskCoreException ex) {
807  logger.log(Level.WARNING, "Could not find get tags for file.", ex); //NON-NLS
808  }
809  linkToThumbnail.append("</div>");
810  currentRow.add(linkToThumbnail.toString());
811 
812  totalCount++;
813  }
814 
815  if (currentRow.isEmpty() == false) {
816  int extraCells = THUMBNAIL_COLUMNS - currentRow.size();
817  for (int i = 0; i < extraCells; i++) {
818  // Finish out the row.
819  currentRow.add("");
820  }
821  addRow(currentRow, false);
822  }
823 
824  // manually set rowCount to be the total number of images.
825  rowCount = totalCount;
826  }
827 
828  private boolean failsContentCheck(Content c) {
829  if (c instanceof AbstractFile == false) {
830  return true;
831  }
832  AbstractFile file = (AbstractFile) c;
833  return file.isDir()
834  || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS
835  || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS;
836  }
837 
847  public String saveContent(AbstractFile file, String dirName) {
848  // clean up the dir name passed in
849  String dirName2 = org.sleuthkit.autopsy.coreutils.FileUtil.escapeFileName(dirName);
850 
851  // Make a folder for the local file with the same tagName as the tag.
852  StringBuilder localFilePath = new StringBuilder(); // full path
853 
854  localFilePath.append(subPath);
855  localFilePath.append(dirName2);
856  File localFileFolder = new File(localFilePath.toString());
857  if (!localFileFolder.exists()) {
858  localFileFolder.mkdirs();
859  }
860 
861  /*
862  * Construct a file tagName for the local file that incorporates the
863  * file ID to ensure uniqueness.
864  *
865  * Note: File name is normalized to account for possible attribute name
866  * which will be separated by a ':' character.
867  */
868  String fileName = org.sleuthkit.autopsy.coreutils.FileUtil.escapeFileName(file.getName());
869  String objectIdSuffix = "_" + file.getId();
870  int lastDotIndex = fileName.lastIndexOf(".");
871  if (lastDotIndex != -1 && lastDotIndex != 0) {
872  // The file tagName has a conventional extension. Insert the object id before the '.' of the extension.
873  fileName = fileName.substring(0, lastDotIndex) + objectIdSuffix + fileName.substring(lastDotIndex, fileName.length());
874  } else {
875  // The file has no extension or the only '.' in the file is an initial '.', as in a hidden file.
876  // Add the object id to the end of the file tagName.
877  fileName += objectIdSuffix;
878  }
879  localFilePath.append(File.separator);
880  localFilePath.append(fileName);
881 
882  // If the local file doesn't already exist, create it now.
883  // The existence check is necessary because it is possible to apply multiple tags with the same tagName to a file.
884  File localFile = new File(localFilePath.toString());
885  if (!localFile.exists()) {
886  ExtractFscContentVisitor.extract(file, localFile, null, null);
887  }
888 
889  // get the relative path
890  return localFilePath.toString().substring(subPath.length());
891  }
892 
900  @Override
901  public String dateToString(long date) {
902  SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
903  return sdf.format(new java.util.Date(date * 1000));
904  }
905 
906  @Override
907  public String getRelativeFilePath() {
908  return "report.html"; //NON-NLS
909  }
910 
911  @Override
912  public String getName() {
913  return NbBundle.getMessage(this.getClass(), "ReportHTML.getName.text");
914  }
915 
916  @Override
917  public String getDescription() {
918  return NbBundle.getMessage(this.getClass(), "ReportHTML.getDesc.text");
919  }
920 
924  private void writeCss() {
925  Writer cssOut = null;
926  try {
927  cssOut = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(subPath + "index.css"), "UTF-8")); //NON-NLS NON-NLS
928  String css = "body {margin: 0px; padding: 0px; background: #FFFFFF; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353;}\n"
929  + //NON-NLS
930  "#content {padding: 30px;}\n"
931  + //NON-NLS
932  "#header {width:100%; padding: 10px; line-height: 25px; background: #07A; color: #FFF; font-size: 20px;}\n"
933  + //NON-NLS
934  "#pageHeaderFooter {width: 100%; padding: 10px; line-height: 25px; text-align: center; font-size: 20px;}\n"
935  + //NON-NLS
936  "h1 {font-size: 20px; font-weight: normal; color: #07A; padding: 0 0 7px 0; margin-top: 25px; border-bottom: 1px solid #D6D6D6;}\n"
937  + //NON-NLS
938  "h2 {font-size: 20px; font-weight: bolder; color: #07A;}\n"
939  + //NON-NLS
940  "h3 {font-size: 16px; color: #07A;}\n"
941  + //NON-NLS
942  "h4 {background: #07A; color: #FFF; font-size: 16px; margin: 0 0 0 25px; padding: 0; padding-left: 15px;}\n"
943  + //NON-NLS
944  "ul.nav {list-style-type: none; line-height: 35px; padding: 0px; margin-left: 15px;}\n"
945  + //NON-NLS
946  "ul li a {font-size: 14px; color: #444; text-decoration: none; padding-left: 25px;}\n"
947  + //NON-NLS
948  "ul li a:hover {text-decoration: underline;}\n"
949  + //NON-NLS
950  "p {margin: 0 0 20px 0;}\n"
951  + //NON-NLS
952  "table {white-space:nowrap; min-width: 700px; padding: 2; margin: 0; border-collapse: collapse; border-bottom: 2px solid #e5e5e5;}\n"
953  + //NON-NLS
954  ".keyword_list table {margin: 0 0 25px 25px; border-bottom: 2px solid #dedede;}\n"
955  + //NON-NLS
956  "table th {white-space:nowrap; display: table-cell; text-align: center; padding: 2px 4px; background: #e5e5e5; color: #777; font-size: 11px; text-shadow: #e9f9fd 0 1px 0; border-top: 1px solid #dedede; border-bottom: 2px solid #e5e5e5;}\n"
957  + //NON-NLS
958  "table .left_align_cell{display: table-cell; padding: 2px 4px; font: 13px/20px Arial, Helvetica, sans-serif; min-width: 125px; overflow: auto; text-align: left; }\n"
959  + //NON-NLS
960  "table .right_align_cell{display: table-cell; padding: 2px 4px; font: 13px/20px Arial, Helvetica, sans-serif; min-width: 125px; overflow: auto; text-align: right; }\n"
961  + //NON-NLS
962  "table td {white-space:nowrap; display: table-cell; padding: 2px 3px; font: 13px/20px Arial, Helvetica, sans-serif; min-width: 125px; overflow: auto; text-align:left; vertical-align: text-top;}\n"
963  + //NON-NLS
964  "table tr:nth-child(even) td {background: #f3f3f3;}\n"
965  + //NON-NLS
966  "div#thumbnail_link {max-width: 200px; white-space: pre-wrap; white-space: -moz-pre-wrap; white-space: -pre-wrap; white-space: -o-pre-wrap; word-wrap: break-word;}";
967  cssOut.write(css);
968  } catch (FileNotFoundException ex) {
969  logger.log(Level.SEVERE, "Could not find index.css file to write to.", ex); //NON-NLS
970  } catch (UnsupportedEncodingException ex) {
971  logger.log(Level.SEVERE, "Did not recognize encoding when writing index.css.", ex); //NON-NLS
972  } catch (IOException ex) {
973  logger.log(Level.SEVERE, "Error creating Writer for index.css.", ex); //NON-NLS
974  } finally {
975  try {
976  if (cssOut != null) {
977  cssOut.flush();
978  cssOut.close();
979  }
980  } catch (IOException ex) {
981  }
982  }
983  }
984 
988  private void writeIndex() {
989  Writer indexOut = null;
990  String indexFilePath = path + "report.html"; //NON-NLS
991  Case openCase;
992  try {
993  openCase = Case.getCurrentCaseThrows();
994  } catch (NoCurrentCaseException ex) {
995  logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS
996  return;
997  }
998  try {
999  indexOut = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(indexFilePath), "UTF-8")); //NON-NLS
1000  StringBuilder index = new StringBuilder();
1001  final String reportTitle = reportBranding.getReportTitle();
1002  String iconPath = reportBranding.getAgencyLogoPath();
1003  if (iconPath == null) {
1004  // use default Autopsy icon if custom icon is not set
1005  iconPath = HTML_SUBDIR + "favicon.ico";
1006  } else {
1007  iconPath = Paths.get(reportBranding.getAgencyLogoPath()).getFileName().toString(); //ref to writeNav() for agency_logo
1008  }
1009  index.append("<head>\n<title>").append(reportTitle).append(" ").append(
1010  NbBundle.getMessage(this.getClass(), "ReportHTML.writeIndex.title", currentCase.getDisplayName())).append(
1011  "</title>\n"); //NON-NLS
1012  index.append("<link rel=\"icon\" type=\"image/ico\" href=\"")
1013  .append(iconPath).append("\" />\n"); //NON-NLS
1014  index.append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"); //NON-NLS
1015  index.append("</head>\n"); //NON-NLS
1016  index.append("<frameset cols=\"350px,*\">\n"); //NON-NLS
1017  index.append("<frame src=\"" + HTML_SUBDIR).append(File.separator).append("nav.html\" name=\"nav\">\n"); //NON-NLS
1018  index.append("<frame src=\"" + HTML_SUBDIR).append(File.separator).append("summary.html\" name=\"content\">\n"); //NON-NLS
1019  index.append("<noframes>").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeIndex.noFrames.msg")).append("<br />\n"); //NON-NLS
1020  index.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeIndex.noFrames.seeNav")).append("<br />\n"); //NON-NLS
1021  index.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeIndex.seeSum")).append("</noframes>\n"); //NON-NLS
1022  index.append("</frameset>\n"); //NON-NLS
1023  index.append("</html>"); //NON-NLS
1024  indexOut.write(index.toString());
1025  openCase.addReport(indexFilePath, NbBundle.getMessage(this.getClass(),
1026  "ReportHTML.writeIndex.srcModuleName.text"), "");
1027  } catch (IOException ex) {
1028  logger.log(Level.SEVERE, "Error creating Writer for report.html: {0}", ex); //NON-NLS
1029  } catch (TskCoreException ex) {
1030  String errorMessage = String.format("Error adding %s to case as a report", indexFilePath); //NON-NLS
1031  logger.log(Level.SEVERE, errorMessage, ex);
1032  } finally {
1033  try {
1034  if (indexOut != null) {
1035  indexOut.flush();
1036  indexOut.close();
1037  }
1038  } catch (IOException ex) {
1039  }
1040  }
1041  }
1042 
1046  private void writeNav() {
1047  Writer navOut = null;
1048  try {
1049  navOut = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(subPath + "nav.html"), "UTF-8")); //NON-NLS
1050  StringBuilder nav = new StringBuilder();
1051  nav.append("<html>\n<head>\n\t<title>").append( //NON-NLS
1052  NbBundle.getMessage(this.getClass(), "ReportHTML.writeNav.title"))
1053  .append("</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"index.css\" />\n"); //NON-NLS
1054  nav.append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n</head>\n<body>\n"); //NON-NLS
1055  nav.append("<div id=\"content\">\n<h1>").append( //NON-NLS
1056  NbBundle.getMessage(this.getClass(), "ReportHTML.writeNav.h1")).append("</h1>\n"); //NON-NLS
1057  nav.append("<ul class=\"nav\">\n"); //NON-NLS
1058  nav.append("<li style=\"background: url(summary.png) left center no-repeat;\"><a href=\"summary.html\" target=\"content\">") //NON-NLS
1059  .append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeNav.summary")).append("</a></li>\n"); //NON-NLS
1060 
1061  for (String dataType : dataTypes.keySet()) {
1062  String dataTypeEsc = dataTypeToFileName(dataType);
1063  String iconFileName = useDataTypeIcon(dataType);
1064  nav.append("<li style=\"background: url('").append(iconFileName) //NON-NLS
1065  .append("') left center no-repeat;\"><a href=\"") //NON-NLS
1066  .append(dataTypeEsc).append(".html\" target=\"content\">") //NON-NLS
1067  .append(dataType).append(" (").append(dataTypes.get(dataType))
1068  .append(")</a></li>\n"); //NON-NLS
1069  }
1070  nav.append("</ul>\n"); //NON-NLS
1071  nav.append("</div>\n</body>\n</html>"); //NON-NLS
1072  navOut.write(nav.toString());
1073  } catch (IOException ex) {
1074  logger.log(Level.SEVERE, "Failed to write end of report navigation menu: {0}", ex); //NON-NLS
1075  } finally {
1076  if (navOut != null) {
1077  try {
1078  navOut.flush();
1079  navOut.close();
1080  } catch (IOException ex) {
1081  logger.log(Level.WARNING, "Could not close navigation out writer."); //NON-NLS
1082  }
1083  }
1084  }
1085 
1086  InputStream in = null;
1087  OutputStream output = null;
1088  try {
1089 
1090  //pull generator and agency logo from branding, and the remaining resources from the core jar
1091  String generatorLogoPath = reportBranding.getGeneratorLogoPath();
1092  if (generatorLogoPath != null && !generatorLogoPath.isEmpty()) {
1093  File from = new File(generatorLogoPath);
1094  File to = new File(subPath);
1095  FileUtil.copyFile(FileUtil.toFileObject(from), FileUtil.toFileObject(to), "generator_logo"); //NON-NLS
1096  }
1097 
1098  String agencyLogoPath = reportBranding.getAgencyLogoPath();
1099  if (agencyLogoPath != null && !agencyLogoPath.isEmpty()) {
1100  Path destinationPath = Paths.get(subPath);
1101  Files.copy(Files.newInputStream(Paths.get(agencyLogoPath)), destinationPath.resolve(Paths.get(agencyLogoPath).getFileName())); //NON-NLS
1102  }
1103 
1104  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/favicon.ico"); //NON-NLS
1105  output = new FileOutputStream(new File(subPath + "favicon.ico"));
1106  FileUtil.copy(in, output);
1107  in.close();
1108  output.close();
1109 
1110  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/summary.png"); //NON-NLS
1111  output = new FileOutputStream(new File(subPath + "summary.png"));
1112  FileUtil.copy(in, output);
1113  in.close();
1114  output.close();
1115 
1116  } catch (IOException ex) {
1117  logger.log(Level.SEVERE, "Failed to extract images for HTML report.", ex); //NON-NLS
1118  } finally {
1119  if (output != null) {
1120  try {
1121  output.flush();
1122  output.close();
1123  } catch (IOException ex) {
1124  }
1125  }
1126  if (in != null) {
1127  try {
1128  in.close();
1129  } catch (IOException ex) {
1130  }
1131  }
1132  }
1133  }
1134 
1138  private void writeSummary() {
1139  Writer output = null;
1140  try {
1141  output = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(subPath + "summary.html"), "UTF-8")); //NON-NLS
1142  StringBuilder head = new StringBuilder();
1143  head.append("<html>\n<head>\n<title>").append( //NON-NLS
1144  NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.title")).append("</title>\n"); //NON-NLS
1145  head.append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"); //NON-NLS
1146  head.append("<style type=\"text/css\">\n"); //NON-NLS
1147  head.append("#pageHeaderFooter {width: 100%; padding: 10px; line-height: 25px; text-align: center; font-size: 20px;}\n"); //NON-NLS
1148  head.append("body { padding: 0px; margin: 0px; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353; }\n"); //NON-NLS
1149  head.append("#wrapper { width: 90%; margin: 0px auto; margin-top: 35px; }\n"); //NON-NLS
1150  head.append("h1 { color: #07A; font-size: 36px; line-height: 42px; font-weight: normal; margin: 0px; border-bottom: 1px solid #81B9DB; }\n"); //NON-NLS
1151  head.append("h1 span { color: #F00; display: block; font-size: 16px; font-weight: bold; line-height: 22px;}\n"); //NON-NLS
1152  head.append("h2 { padding: 0 0 3px 0; margin: 0px; color: #07A; font-weight: normal; border-bottom: 1px dotted #81B9DB; }\n"); //NON-NLS
1153  head.append("h3 { padding: 5 0 3px 0; margin: 0px; color: #07A; font-weight: normal; }\n");
1154  head.append("table td { padding: 5px 25px 5px 0px; vertical-align:top;}\n"); //NON-NLS
1155  head.append("p.subheadding { padding: 0px; margin: 0px; font-size: 11px; color: #B5B5B5; }\n"); //NON-NLS
1156  head.append(".title { width: 660px; margin-bottom: 50px; }\n"); //NON-NLS
1157  head.append(".left { float: left; width: 250px; margin-top: 20px; text-align: center; }\n"); //NON-NLS
1158  head.append(".left img { max-width: 250px; max-height: 250px; min-width: 200px; min-height: 200px; }\n"); //NON-NLS
1159  head.append(".right { float: right; width: 385px; margin-top: 25px; font-size: 14px; }\n"); //NON-NLS
1160  head.append(".clear { clear: both; }\n"); //NON-NLS
1161  head.append(".info { padding: 10px 0;}\n");
1162  head.append(".info p { padding: 3px 10px; background: #e5e5e5; color: #777; font-size: 12px; font-weight: bold; text-shadow: #e9f9fd 0 1px 0; border-top: 1px solid #dedede; border-bottom: 2px solid #dedede; }\n"); //NON-NLS
1163  head.append(".info table { margin: 10px 25px 10px 25px; }\n"); //NON-NLS
1164  head.append("ul {padding: 0;margin: 0;list-style-type: none;}");
1165  head.append("li {padding-bottom: 5px;}");
1166  head.append("</style>\n"); //NON-NLS
1167  head.append("</head>\n<body>\n"); //NON-NLS
1168  output.write(head.toString());
1169 
1170  DateFormat datetimeFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
1171  Date date = new Date();
1172  String datetime = datetimeFormat.format(date);
1173 
1174  StringBuilder summary = new StringBuilder();
1175  boolean running = false;
1176  if (IngestManager.getInstance().isIngestRunning()) {
1177  running = true;
1178  }
1179  SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
1180  List<IngestJobInfo> ingestJobs = skCase.getIngestJobs();
1181  final String reportTitle = reportBranding.getReportTitle();
1182  final String reportFooter = reportBranding.getReportFooter();
1183  final boolean generatorLogoSet = reportBranding.getGeneratorLogoPath() != null && !reportBranding.getGeneratorLogoPath().isEmpty();
1184 
1185  summary.append("<div id=\"wrapper\">\n"); //NON-NLS
1186  summary.append(writePageHeader());
1187  summary.append("<h1>").append(reportTitle) //NON-NLS
1188  .append(running ? NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.warningMsg") : "")
1189  .append("</h1>\n"); //NON-NLS
1190  summary.append("<p class=\"subheadding\">").append( //NON-NLS
1191  NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.reportGenOn.text", datetime)).append("</p>\n"); //NON-NLS
1192  summary.append("<div class=\"title\">\n"); //NON-NLS
1193  summary.append(writeSummaryCaseDetails());
1194  summary.append(writeSummaryImageInfo());
1195  summary.append(writeSummarySoftwareInfo(skCase, ingestJobs));
1196  summary.append(writeSummaryIngestHistoryInfo(skCase, ingestJobs));
1197  if (generatorLogoSet) {
1198  summary.append("<div class=\"left\">\n"); //NON-NLS
1199  summary.append("<img src=\"generator_logo.png\" />\n"); //NON-NLS
1200  summary.append("</div>\n"); //NON-NLS
1201  }
1202  summary.append("<div class=\"clear\"></div>\n"); //NON-NLS
1203  if (reportFooter != null) {
1204  summary.append("<p class=\"subheadding\">").append(reportFooter).append("</p>\n"); //NON-NLS
1205  }
1206  summary.append("</div>\n"); //NON-NLS
1207  summary.append(writePageFooter());
1208  summary.append("</body></html>"); //NON-NLS
1209  output.write(summary.toString());
1210  } catch (FileNotFoundException ex) {
1211  logger.log(Level.SEVERE, "Could not find summary.html file to write to."); //NON-NLS
1212  } catch (UnsupportedEncodingException ex) {
1213  logger.log(Level.SEVERE, "Did not recognize encoding when writing summary.hmtl."); //NON-NLS
1214  } catch (IOException ex) {
1215  logger.log(Level.SEVERE, "Error creating Writer for summary.html."); //NON-NLS
1216  } catch (NoCurrentCaseException | TskCoreException ex) {
1217  logger.log(Level.WARNING, "Unable to get current sleuthkit Case for the HTML report.");
1218  } finally {
1219  try {
1220  if (output != null) {
1221  output.flush();
1222  output.close();
1223  }
1224  } catch (IOException ex) {
1225  }
1226  }
1227  }
1228 
1234  private StringBuilder writeSummaryCaseDetails() {
1235  StringBuilder summary = new StringBuilder();
1236  String caseName = currentCase.getDisplayName();
1237  String caseNumber = currentCase.getNumber();
1238  String examiner = currentCase.getExaminer();
1239  final boolean agencyLogoSet = reportBranding.getAgencyLogoPath() != null && !reportBranding.getAgencyLogoPath().isEmpty();
1240  int imagecount;
1241  try {
1242  imagecount = currentCase.getDataSources().size();
1243  } catch (TskCoreException ex) {
1244  imagecount = 0;
1245  }
1246  summary.append("<div class=\"title\">\n"); //NON-NLS
1247  if (agencyLogoSet) {
1248  summary.append("<div class=\"left\">\n"); //NON-NLS
1249  summary.append("<img src=\"");
1250  summary.append(Paths.get(reportBranding.getAgencyLogoPath()).getFileName().toString());
1251  summary.append("\" />\n"); //NON-NLS
1252  summary.append("</div>\n"); //NON-NLS
1253  }
1254  final String align = agencyLogoSet ? "right" : "left"; //NON-NLS NON-NLS
1255  summary.append("<div class=\"").append(align).append("\">\n"); //NON-NLS
1256  summary.append("<table>\n"); //NON-NLS
1257  summary.append("<tr><td>").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.caseName")) //NON-NLS
1258  .append("</td><td>").append(caseName).append("</td></tr>\n"); //NON-NLS NON-NLS
1259  summary.append("<tr><td>").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.caseNum")) //NON-NLS
1260  .append("</td><td>").append(!caseNumber.isEmpty() ? caseNumber : NbBundle //NON-NLS
1261  .getMessage(this.getClass(), "ReportHTML.writeSum.noCaseNum")).append("</td></tr>\n"); //NON-NLS
1262  summary.append("<tr><td>").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.examiner")).append("</td><td>") //NON-NLS
1263  .append(!examiner.isEmpty() ? examiner : NbBundle
1264  .getMessage(this.getClass(), "ReportHTML.writeSum.noExaminer"))
1265  .append("</td></tr>\n"); //NON-NLS
1266  summary.append("<tr><td>").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.numImages")) //NON-NLS
1267  .append("</td><td>").append(imagecount).append("</td></tr>\n"); //NON-NLS
1268  summary.append("</table>\n"); //NON-NLS
1269  summary.append("</div>\n"); //NON-NLS
1270  summary.append("<div class=\"clear\"></div>\n"); //NON-NLS
1271  summary.append("</div>\n"); //NON-NLS
1272  return summary;
1273  }
1274 
1280  private StringBuilder writeSummaryImageInfo() {
1281  StringBuilder summary = new StringBuilder();
1282  summary.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.imageInfoHeading"));
1283  summary.append("<div class=\"info\">\n"); //NON-NLS
1284  try {
1285  for (Content c : currentCase.getDataSources()) {
1286  summary.append("<p>").append(c.getName()).append("</p>\n"); //NON-NLS
1287  if (c instanceof Image) {
1288  Image img = (Image) c;
1289 
1290  summary.append("<table>\n"); //NON-NLS
1291  summary.append("<tr><td>").append( //NON-NLS
1292  NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.timezone"))
1293  .append("</td><td>").append(img.getTimeZone()).append("</td></tr>\n"); //NON-NLS
1294  for (String imgPath : img.getPaths()) {
1295  summary.append("<tr><td>").append( //NON-NLS
1296  NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.path"))
1297  .append("</td><td>").append(imgPath).append("</td></tr>\n"); //NON-NLS
1298  }
1299  summary.append("</table>\n"); //NON-NLS
1300  }
1301  }
1302  } catch (TskCoreException ex) {
1303  logger.log(Level.WARNING, "Unable to get image information for the HTML report."); //NON-NLS
1304  }
1305  summary.append("</div>\n"); //NON-NLS
1306  return summary;
1307  }
1308 
1314  private StringBuilder writeSummarySoftwareInfo(SleuthkitCase skCase, List<IngestJobInfo> ingestJobs) {
1315  StringBuilder summary = new StringBuilder();
1316  summary.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.softwareInfoHeading"));
1317  summary.append("<div class=\"info\">\n");
1318  summary.append("<table>\n");
1319  summary.append("<tr><td>").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.autopsyVersion"))
1320  .append("</td><td>").append(Version.getVersion()).append("</td></tr>\n");
1321  Map<Long, IngestModuleInfo> moduleInfoHashMap = new HashMap<>();
1322  for (IngestJobInfo ingestJob : ingestJobs) {
1323  List<IngestModuleInfo> ingestModules = ingestJob.getIngestModuleInfo();
1324  for (IngestModuleInfo ingestModule : ingestModules) {
1325  if (!moduleInfoHashMap.containsKey(ingestModule.getIngestModuleId())) {
1326  moduleInfoHashMap.put(ingestModule.getIngestModuleId(), ingestModule);
1327  }
1328  }
1329  }
1330  TreeMap<String, String> modules = new TreeMap<>();
1331  for (IngestModuleInfo moduleinfo : moduleInfoHashMap.values()) {
1332  modules.put(moduleinfo.getDisplayName(), moduleinfo.getVersion());
1333  }
1334  for (Map.Entry<String, String> module : modules.entrySet()) {
1335  summary.append("<tr><td>").append(module.getKey()).append(" Module:")
1336  .append("</td><td>").append(module.getValue()).append("</td></tr>\n");
1337  }
1338  summary.append("</table>\n");
1339  summary.append("</div>\n");
1340  summary.append("<div class=\"clear\"></div>\n"); //NON-NLS
1341  return summary;
1342  }
1343 
1349  private StringBuilder writeSummaryIngestHistoryInfo(SleuthkitCase skCase, List<IngestJobInfo> ingestJobs) {
1350  StringBuilder summary = new StringBuilder();
1351  try {
1352  summary.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.ingestHistoryHeading"));
1353  summary.append("<div class=\"info\">\n");
1354  int jobnumber = 1;
1355 
1356  for (IngestJobInfo ingestJob : ingestJobs) {
1357  summary.append("<h3>Job ").append(jobnumber).append(":</h3>\n");
1358  summary.append("<table>\n");
1359  summary.append("<tr><td>").append("Data Source:")
1360  .append("</td><td>").append(skCase.getContentById(ingestJob.getObjectId()).getName()).append("</td></tr>\n");
1361  summary.append("<tr><td>").append("Status:")
1362  .append("</td><td>").append(ingestJob.getStatus()).append("</td></tr>\n");
1363  summary.append("<tr><td>").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.modulesEnabledHeading"))
1364  .append("</td><td>");
1365  List<IngestModuleInfo> ingestModules = ingestJob.getIngestModuleInfo();
1366  summary.append("<ul>\n");
1367  for (IngestModuleInfo ingestModule : ingestModules) {
1368  summary.append("<li>").append(ingestModule.getDisplayName()).append("</li>");
1369  }
1370  summary.append("</ul>\n");
1371  jobnumber++;
1372  summary.append("</td></tr>\n");
1373  summary.append("</table>\n");
1374  }
1375  summary.append("</div>\n");
1376  } catch (TskCoreException ex) {
1377  logger.log(Level.WARNING, "Unable to get ingest jobs for the HTML report.");
1378  }
1379  return summary;
1380  }
1381 
1390  private String prepareThumbnail(AbstractFile file) {
1391  BufferedImage bufferedThumb = ImageUtils.getThumbnail(file, ImageUtils.ICON_SIZE_MEDIUM);
1392 
1393  /*
1394  * File name is normalized to account for possible attribute name which
1395  * will be separated by a ':' character.
1396  */
1397  String fileName = org.sleuthkit.autopsy.coreutils.FileUtil.escapeFileName(file.getName());
1398 
1399  File thumbFile = Paths.get(thumbsPath, fileName + ".png").toFile();
1400  if (bufferedThumb == null) {
1401  return null;
1402  }
1403  try {
1404  ImageIO.write(bufferedThumb, "png", thumbFile);
1405  } catch (IOException ex) {
1406  logger.log(Level.WARNING, "Failed to write thumb file to report directory.", ex); //NON-NLS
1407  return null;
1408  }
1409  if (thumbFile.exists()
1410  == false) {
1411  return null;
1412  }
1413  return THUMBS_REL_PATH
1414  + thumbFile.getName();
1415  }
1416 
1417 }
static String escapeFileName(String fileName)
Definition: FileUtil.java:169

Copyright © 2012-2018 Basis Technology. Generated on: Tue Dec 18 2018
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.