Autopsy  4.20.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
HTMLReport.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.modules.html;
24 
27 import java.awt.image.BufferedImage;
28 import java.io.BufferedWriter;
29 import java.io.File;
30 import java.io.FileNotFoundException;
31 import java.io.FileOutputStream;
32 import java.io.IOException;
33 import java.io.InputStream;
34 import java.io.OutputStream;
35 import java.io.OutputStreamWriter;
36 import java.io.UnsupportedEncodingException;
37 import java.io.Writer;
38 import java.nio.file.Files;
39 import java.nio.file.Path;
40 import java.nio.file.Paths;
41 import java.text.DateFormat;
42 import java.text.SimpleDateFormat;
43 import java.util.ArrayList;
44 import java.util.Date;
45 import java.util.HashMap;
46 import java.util.List;
47 import java.util.Map;
48 import java.util.Set;
49 import java.util.TreeMap;
50 import java.util.concurrent.ExecutionException;
51 import java.util.logging.Level;
52 import javax.imageio.ImageIO;
53 import javax.swing.JPanel;
54 import org.apache.commons.io.FilenameUtils;
55 import org.apache.commons.text.StringEscapeUtils;
56 import org.openide.filesystems.FileUtil;
57 import org.openide.util.NbBundle;
58 import org.openide.util.NbBundle.Messages;
74 import org.sleuthkit.datamodel.AbstractFile;
75 import org.sleuthkit.datamodel.BlackboardArtifact;
76 import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
77 import org.sleuthkit.datamodel.Content;
78 import org.sleuthkit.datamodel.ContentTag;
79 import org.sleuthkit.datamodel.Image;
80 import org.sleuthkit.datamodel.IngestJobInfo;
81 import org.sleuthkit.datamodel.IngestModuleInfo;
82 import org.sleuthkit.datamodel.SleuthkitCase;
83 import org.sleuthkit.datamodel.TskCoreException;
84 import org.sleuthkit.datamodel.TskData;
85 import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM;
86 
87 public class HTMLReport implements TableReportModule {
88 
89  private static final Logger logger = Logger.getLogger(HTMLReport.class.getName());
90  private static final String THUMBS_REL_PATH = "thumbs" + File.separator; //NON-NLS
91  private static HTMLReport instance;
92  private static final int MAX_THUMBS_PER_PAGE = 1000;
93  private static final String HTML_SUBDIR = "content";
94  private Case currentCase;
95  public static Integer THUMBNAIL_COLUMNS = 5;
96 
97  private Map<String, Integer> dataTypes;
98  private String path;
99  private String thumbsPath;
100  private String subPath;
101  private String currentDataType; // name of current data type
102  private Integer rowCount; // number of rows (aka artifacts or tags) for the current data type
103  private Writer out;
104 
105  private HTMLReportConfigurationPanel configPanel;
106 
108 
109  // Get the default instance of this report
110  public static synchronized HTMLReport getDefault() {
111  if (instance == null) {
112  instance = new HTMLReport();
113  }
114  return instance;
115  }
116 
117  // Hidden constructor
118  private HTMLReport() {
119  reportBranding = new ReportBranding();
120  }
121 
122  @Override
123  public JPanel getConfigurationPanel() {
124  initializePanel();
125  return configPanel;
126  }
127 
128  private void initializePanel() {
129  if (configPanel == null) {
130  configPanel = new HTMLReportConfigurationPanel();
131  }
132  }
133 
139  @Override
141  return new HTMLReportModuleSettings();
142  }
143 
149  @Override
151  initializePanel();
152  return configPanel.getConfiguration();
153  }
154 
160  @Override
161  public void setConfiguration(ReportModuleSettings settings) {
162  initializePanel();
163  if (settings == null || settings instanceof NoReportModuleSettings) {
164  configPanel.setConfiguration((HTMLReportModuleSettings) getDefaultConfiguration());
165  return;
166  }
167 
168  if (settings instanceof HTMLReportModuleSettings) {
169  configPanel.setConfiguration((HTMLReportModuleSettings) settings);
170  return;
171  }
172 
173  throw new IllegalArgumentException("Expected settings argument to be an instance of HTMLReportModuleSettings");
174  }
175 
176  // Refesh the member variables
177  private void refresh() throws NoCurrentCaseException {
178  currentCase = Case.getCurrentCaseThrows();
179 
180  dataTypes = new TreeMap<>();
181 
182  path = "";
183  thumbsPath = "";
184  subPath = "";
185  currentDataType = "";
186  rowCount = 0;
187 
188  if (out != null) {
189  try {
190  out.close();
191  } catch (IOException ex) {
192  }
193  }
194  out = null;
195  }
196 
203  private String dataTypeToFileName(String dataType) {
204 
205  String fileName = org.sleuthkit.autopsy.coreutils.FileUtil.escapeFileName(dataType);
206  // replace all ' ' with '_'
207  fileName = fileName.replaceAll(" ", "_");
208 
209  return fileName;
210  }
211 
216  @SuppressWarnings("deprecation")
217  private String useDataTypeIcon(String dataType) {
218  String iconFilePath;
219  String iconFileName;
220  InputStream in;
221  OutputStream output = null;
222 
223  logger.log(Level.INFO, "useDataTypeIcon: dataType = {0}", dataType); //NON-NLS
224 
225  // find the artifact with matching display name
226  BlackboardArtifact.ARTIFACT_TYPE artifactType = null;
227  for (ARTIFACT_TYPE v : ARTIFACT_TYPE.values()) {
228  if (v.getDisplayName().equals(dataType)) {
229  artifactType = v;
230  }
231  }
232 
233  if (null != artifactType) {
234  // set the icon file name
235  iconFileName = dataTypeToFileName(artifactType.getDisplayName()) + ".png"; //NON-NLS
236  iconFilePath = subPath + File.separator + iconFileName;
237 
238  // determine the source image to use
239  switch (artifactType) {
240  case TSK_WEB_BOOKMARK:
241  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/bookmarks.png"); //NON-NLS
242  break;
243  case TSK_WEB_COOKIE:
244  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/cookies.png"); //NON-NLS
245  break;
246  case TSK_WEB_HISTORY:
247  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/history.png"); //NON-NLS
248  break;
249  case TSK_WEB_DOWNLOAD:
250  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/downloads.png"); //NON-NLS
251  break;
252  case TSK_RECENT_OBJECT:
253  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/recent.png"); //NON-NLS
254  break;
255  case TSK_INSTALLED_PROG:
256  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/installed.png"); //NON-NLS
257  break;
258  case TSK_KEYWORD_HIT:
259  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/keywords.png"); //NON-NLS
260  break;
261  case TSK_HASHSET_HIT:
262  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/hash.png"); //NON-NLS
263  break;
264  case TSK_DEVICE_ATTACHED:
265  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/devices.png"); //NON-NLS
266  break;
267  case TSK_WEB_SEARCH_QUERY:
268  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/search.png"); //NON-NLS
269  break;
270  case TSK_METADATA_EXIF:
271  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/exif.png"); //NON-NLS
272  break;
273  case TSK_TAG_FILE:
274  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/userbookmarks.png"); //NON-NLS
275  break;
276  case TSK_TAG_ARTIFACT:
277  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/userbookmarks.png"); //NON-NLS
278  break;
279  case TSK_SERVICE_ACCOUNT:
280  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/account-icon-16.png"); //NON-NLS
281  break;
282  case TSK_CONTACT:
283  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/contact.png"); //NON-NLS
284  break;
285  case TSK_MESSAGE:
286  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/message.png"); //NON-NLS
287  break;
288  case TSK_CALLLOG:
289  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/calllog.png"); //NON-NLS
290  break;
291  case TSK_CALENDAR_ENTRY:
292  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/calendar.png"); //NON-NLS
293  break;
294  case TSK_SPEED_DIAL_ENTRY:
295  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/speeddialentry.png"); //NON-NLS
296  break;
297  case TSK_BLUETOOTH_PAIRING:
298  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/bluetooth.png"); //NON-NLS
299  break;
300  case TSK_GPS_BOOKMARK:
301  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/gpsfav.png"); //NON-NLS
302  break;
303  case TSK_GPS_LAST_KNOWN_LOCATION:
304  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/gps-lastlocation.png"); //NON-NLS
305  break;
306  case TSK_GPS_SEARCH:
307  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/gps-search.png"); //NON-NLS
308  break;
309  case TSK_OS_INFO:
310  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/computer.png"); //NON-NLS
311  break;
312  case TSK_GPS_TRACKPOINT:
313  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/gps_trackpoint.png"); //NON-NLS
314  break;
315  case TSK_GPS_ROUTE:
316  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/gps_trackpoint.png"); //NON-NLS
317  break;
318  case TSK_EMAIL_MSG:
319  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/mail-icon-16.png"); //NON-NLS
320  break;
321  case TSK_ENCRYPTION_SUSPECTED:
322  case TSK_ENCRYPTION_DETECTED:
323  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/encrypted-file.png"); //NON-NLS
324  break;
325  case TSK_EXT_MISMATCH_DETECTED:
326  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/mismatch-16.png"); //NON-NLS
327  break;
328  case TSK_INTERESTING_ARTIFACT_HIT:
329  //fall through deprecated type to TSK_INTERESTING_ITEM
330  case TSK_INTERESTING_FILE_HIT:
331  //fall through deprecated type to TSK_INTERESTING_ITEM
332  case TSK_INTERESTING_ITEM:
333  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/interesting_item.png"); //NON-NLS
334  break;
335  case TSK_PROG_RUN:
336  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/installed.png"); //NON-NLS
337  break;
338  case TSK_REMOTE_DRIVE:
339  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/drive_network.png"); //NON-NLS
340  break;
341  case TSK_OS_ACCOUNT:
342  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/os-account.png"); //NON-NLS
343  break;
344  case TSK_OBJECT_DETECTED:
345  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/objects.png"); //NON-NLS
346  break;
347  case TSK_WEB_FORM_AUTOFILL:
348  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/web-form.png"); //NON-NLS
349  break;
350  case TSK_WEB_CACHE:
351  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/cache.png"); //NON-NLS
352  break;
353  case TSK_USER_CONTENT_SUSPECTED:
354  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/user-content.png"); //NON-NLS
355  break;
356  case TSK_METADATA:
357  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/metadata.png"); //NON-NLS
358  break;
359  case TSK_CLIPBOARD_CONTENT:
360  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/clipboard.png"); //NON-NLS
361  break;
362  case TSK_ACCOUNT:
363  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/accounts.png"); //NON-NLS
364  break;
365  case TSK_WIFI_NETWORK:
366  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/network-wifi.png"); //NON-NLS
367  break;
368  case TSK_WIFI_NETWORK_ADAPTER:
369  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/network-wifi.png"); //NON-NLS
370  break;
371  case TSK_SIM_ATTACHED:
372  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/sim_card.png"); //NON-NLS
373  break;
374  case TSK_BLUETOOTH_ADAPTER:
375  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/Bluetooth.png"); //NON-NLS
376  break;
377  case TSK_DEVICE_INFO:
378  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/devices.png"); //NON-NLS
379  break;
380  case TSK_VERIFICATION_FAILED:
381  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/validationFailed.png"); //NON-NLS
382  break;
383  case TSK_WEB_ACCOUNT_TYPE:
384  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/web-account-type.png"); //NON-NLS
385  break;
386  case TSK_WEB_FORM_ADDRESS:
387  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/web-form-address.png"); //NON-NLS
388  break;
389  case TSK_GPS_AREA:
390  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/gps-area.png"); //NON-NLS
391  break;
392  case TSK_WEB_CATEGORIZATION:
393  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/domain-16.png"); //NON-NLS
394  break;
395  case TSK_YARA_HIT:
396  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/yara_16.png"); //NON-NLS
397  break;
398  case TSK_PREVIOUSLY_SEEN:
399  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/previously-seen.png"); //NON-NLS
400  break;
401  case TSK_PREVIOUSLY_UNSEEN:
402  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/previously-unseen.png"); //NON-NLS
403  break;
404  case TSK_PREVIOUSLY_NOTABLE:
405  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/red-circle-exclamation.png"); //NON-NLS
406  break;
407  default:
408  logger.log(Level.WARNING, "useDataTypeIcon: unhandled artifact type = {0}", dataType); //NON-NLS
409  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/star.png"); //NON-NLS
410  iconFileName = "star.png"; //NON-NLS
411  iconFilePath = subPath + File.separator + iconFileName;
412  break;
413  }
414  } else if (dataType.startsWith(ARTIFACT_TYPE.TSK_ACCOUNT.getDisplayName())) {
415  /*
416  * TSK_ACCOUNT artifacts get separated by their TSK_ACCOUNT_TYPE
417  * attribute, with a synthetic compound dataType name, so they are
418  * not caught by the switch statement above. For now we just give
419  * them all the general account icon, but we could do something else
420  * in the future.
421  */
422  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/accounts.png"); //NON-NLS
423  iconFileName = "accounts.png"; //NON-NLS
424  iconFilePath = subPath + File.separator + iconFileName;
425  } else { // no defined artifact found for this dataType
426  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/star.png"); //NON-NLS
427  iconFileName = "star.png"; //NON-NLS
428  iconFilePath = subPath + File.separator + iconFileName;
429  }
430 
431  try {
432  output = new FileOutputStream(iconFilePath);
433  FileUtil.copy(in, output);
434  in.close();
435  output.close();
436  } catch (IOException ex) {
437  logger.log(Level.SEVERE, "Failed to extract images for HTML report.", ex); //NON-NLS
438  } finally {
439  if (output != null) {
440  try {
441  output.flush();
442  output.close();
443  } catch (IOException ex) {
444  }
445  }
446  if (in != null) {
447  try {
448  in.close();
449  } catch (IOException ex) {
450  }
451  }
452  }
453 
454  return iconFileName;
455  }
456 
463  @Override
464  public void startReport(String baseReportDir) {
465 
466  // Refresh the HTML report
467  try {
468  refresh();
469  } catch (NoCurrentCaseException ex) {
470  logger.log(Level.SEVERE, "Exception while getting open case."); //NON-NLS
471  return;
472  }
473  // Setup the path for the HTML report
474  this.path = baseReportDir; //NON-NLS
475  this.subPath = this.path + HTML_SUBDIR + File.separator;
476  this.thumbsPath = this.subPath + THUMBS_REL_PATH; //NON-NLS
477  try {
478  FileUtil.createFolder(new File(this.subPath));
479  FileUtil.createFolder(new File(this.thumbsPath));
480  } catch (IOException ex) {
481  logger.log(Level.SEVERE, "Unable to make HTML report folder."); //NON-NLS
482  }
483  // Write the basic files
484  writeCss();
485  writeIndex();
486  writeSummary();
487  }
488 
493  @Override
494  public void endReport() {
495  writeNav();
496  if (out != null) {
497  try {
498  out.close();
499  } catch (IOException ex) {
500  logger.log(Level.WARNING, "Could not close the output writer when ending report.", ex); //NON-NLS
501  }
502  }
503  }
504 
513  @Override
514  public void startDataType(String name, String description) {
515  String title = dataTypeToFileName(name);
516  try {
517  out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(subPath + title + ".html"), "UTF-8")); //NON-NLS
518  } catch (FileNotFoundException ex) {
519  logger.log(Level.SEVERE, "File not found: {0}", ex); //NON-NLS
520  } catch (UnsupportedEncodingException ex) {
521  logger.log(Level.SEVERE, "Unrecognized encoding"); //NON-NLS
522  }
523 
524  try {
525  StringBuilder page = new StringBuilder();
526  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
527  .append(writePageHeader())
528  .append("<div id=\"header\">").append(name).append("</div>\n")
529  .append("<div id=\"content\">\n"); //NON-NLS
530  if (!description.isEmpty()) {
531  page.append("<p><strong>"); //NON-NLS
532  page.append(description);
533  page.append("</strong></p>\n"); //NON-NLS
534  }
535  out.write(page.toString());
536  currentDataType = name;
537  rowCount = 0;
538  } catch (IOException ex) {
539  logger.log(Level.SEVERE, "Failed to write page head: {0}", ex); //NON-NLS
540  }
541  }
542 
547  @Override
548  public void endDataType() {
549  dataTypes.put(currentDataType, rowCount);
550  try {
551  StringBuilder builder = new StringBuilder();
552  builder.append(writePageFooter());
553  builder.append("</div>\n</body>\n</html>\n"); //NON-NLS
554  out.write(builder.toString());
555  } catch (IOException ex) {
556  logger.log(Level.SEVERE, "Failed to write end of HTML report.", ex); //NON-NLS
557  } finally {
558  if (out != null) {
559  try {
560  out.flush();
561  out.close();
562  } catch (IOException ex) {
563  logger.log(Level.WARNING, "Could not close the output writer when ending data type.", ex); //NON-NLS
564  }
565  out = null;
566  }
567  }
568  }
569 
576  private String writePageHeader() {
577  StringBuilder output = new StringBuilder();
578  String pageHeader = configPanel.getHeader();
579  if (pageHeader.isEmpty() == false) {
580  output.append("<div id=\"pageHeaderFooter\">")
581  .append(StringEscapeUtils.escapeHtml4(pageHeader))
582  .append("</div>\n"); //NON-NLS
583  }
584  return output.toString();
585  }
586 
593  private String writePageFooter() {
594  StringBuilder output = new StringBuilder();
595  String pageFooter = configPanel.getFooter();
596  if (pageFooter.isEmpty() == false) {
597  output.append("<br/><div id=\"pageHeaderFooter\">")
598  .append(StringEscapeUtils.escapeHtml4(pageFooter))
599  .append("</div>"); //NON-NLS
600  }
601  return output.toString();
602  }
603 
609  @Override
610  public void startSet(String setName) {
611  StringBuilder set = new StringBuilder();
612  set.append("<h1><a name=\"").append(setName).append("\">").append(setName).append("</a></h1>\n"); //NON-NLS
613  set.append("<div class=\"keyword_list\">\n"); //NON-NLS
614 
615  try {
616  out.write(set.toString());
617  } catch (IOException ex) {
618  logger.log(Level.SEVERE, "Failed to write set: {0}", ex); //NON-NLS
619  }
620  }
621 
625  @Override
626  public void endSet() {
627  try {
628  out.write("</div>\n"); //NON-NLS
629  } catch (IOException ex) {
630  logger.log(Level.SEVERE, "Failed to write end of set: {0}", ex); //NON-NLS
631  }
632  }
633 
639  @Override
640  public void addSetIndex(List<String> sets) {
641  StringBuilder index = new StringBuilder();
642  index.append("<ul>\n"); //NON-NLS
643  for (String set : sets) {
644  index.append("\t<li><a href=\"#").append(set).append("\">").append(set).append("</a></li>\n"); //NON-NLS
645  }
646  index.append("</ul>\n"); //NON-NLS
647  try {
648  out.write(index.toString());
649  } catch (IOException ex) {
650  logger.log(Level.SEVERE, "Failed to add set index: {0}", ex); //NON-NLS
651  }
652  }
653 
659  @Override
660  public void addSetElement(String elementName) {
661  try {
662  out.write("<h4>" + elementName + "</h4>\n"); //NON-NLS
663  } catch (IOException ex) {
664  logger.log(Level.SEVERE, "Failed to write set element: {0}", ex); //NON-NLS
665  }
666  }
667 
673  @Override
674  public void startTable(List<String> titles) {
675  StringBuilder ele = new StringBuilder();
676  ele.append("<table>\n<thead>\n\t<tr>\n"); //NON-NLS
677  for (String title : titles) {
678  ele.append("\t\t<th>").append(title).append("</th>\n"); //NON-NLS
679  }
680  ele.append("\t</tr>\n</thead>\n"); //NON-NLS
681 
682  try {
683  out.write(ele.toString());
684  } catch (IOException ex) {
685  logger.log(Level.SEVERE, "Failed to write table start: {0}", ex); //NON-NLS
686  }
687  }
688 
695  public void startContentTagsTable(List<String> columnHeaders) {
696  StringBuilder htmlOutput = new StringBuilder();
697  htmlOutput.append("<table>\n<thead>\n\t<tr>\n"); //NON-NLS
698 
699  // Add the specified columns.
700  for (String columnHeader : columnHeaders) {
701  htmlOutput.append("\t\t<th>").append(columnHeader).append("</th>\n"); //NON-NLS
702  }
703 
704  // Add a column for a hyperlink to a local copy of the tagged content.
705  htmlOutput.append("\t\t<th></th>\n"); //NON-NLS
706 
707  htmlOutput.append("\t</tr>\n</thead>\n"); //NON-NLS
708 
709  try {
710  out.write(htmlOutput.toString());
711  } catch (IOException ex) {
712  logger.log(Level.SEVERE, "Failed to write table start: {0}", ex); //NON-NLS
713  }
714  }
715 
719  @Override
720  public void endTable() {
721  try {
722  out.write("</table>\n"); //NON-NLS
723  } catch (IOException ex) {
724  logger.log(Level.SEVERE, "Failed to write end of table: {0}", ex); //NON-NLS
725  }
726  }
727 
734  @Override
735  public void addRow(List<String> row) {
736  addRow(row, true);
737  }
738 
746  private void addRow(List<String> row, boolean escapeText) {
747  StringBuilder builder = new StringBuilder();
748  builder.append("\t<tr>\n"); //NON-NLS
749  for (String cell : row) {
750  String cellText = escapeText ? EscapeUtil.escapeHtml(cell) : cell;
751  builder.append("\t\t<td>").append(cellText).append("</td>\n"); //NON-NLS
752  }
753  builder.append("\t</tr>\n"); //NON-NLS
754  rowCount++;
755 
756  try {
757  out.write(builder.toString());
758  } catch (IOException ex) {
759  logger.log(Level.SEVERE, "Failed to write row to out.", ex); //NON-NLS
760  } catch (NullPointerException ex) {
761  logger.log(Level.SEVERE, "Output writer is null. Page was not initialized before writing.", ex); //NON-NLS
762  }
763  }
764 
772  public void addRowWithTaggedContentHyperlink(List<String> row, ContentTag contentTag) {
773  Content content = contentTag.getContent();
774  if (content instanceof AbstractFile == false) {
775  addRow(row, true);
776  return;
777  }
778  AbstractFile file = (AbstractFile) content;
779  // Add the hyperlink to the row. A column header for it was created in startTable().
780  StringBuilder localFileLink = new StringBuilder();
781  // Don't make a local copy of the file if it is a directory or unallocated space.
782  if (!(file.isDir()
783  || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS
784  || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS)) {
785  localFileLink.append("<a href=\""); //NON-NLS
786  // save it in a folder based on the tag name
787  String localFilePath = saveContent(file, contentTag.getName().getDisplayName());
788  localFileLink.append(localFilePath);
789  localFileLink.append("\" target=\"_top\">");
790  }
791 
792  StringBuilder builder = new StringBuilder();
793  builder.append("\t<tr>\n"); //NON-NLS
794  int positionCounter = 0;
795  for (String cell : row) {
796  // position-dependent code used to format this report. Not great, but understandable for formatting.
797  switch (positionCounter) {
798  case 1:
799  // Convert the file name to a hyperlink and left-align it
800  builder.append("\t\t<td class=\"left_align_cell\">").append(localFileLink.toString()).append(cell).append("</a></td>\n"); //NON-NLS
801  break;
802  case 7:
803  // Right-align the bytes column.
804  builder.append("\t\t<td class=\"right_align_cell\">").append(cell).append("</td>\n"); //NON-NLS
805  break;
806  default:
807  // Regular case, not a file name nor a byte count
808  builder.append("\t\t<td>").append(cell).append("</td>\n"); //NON-NLS
809  break;
810  }
811  ++positionCounter;
812  }
813  builder.append("\t</tr>\n"); //NON-NLS
814  rowCount++;
815 
816  try {
817  out.write(builder.toString());
818  } catch (IOException ex) {
819  logger.log(Level.SEVERE, "Failed to write row to out.", ex); //NON-NLS
820  } catch (NullPointerException ex) {
821  logger.log(Level.SEVERE, "Output writer is null. Page was not initialized before writing.", ex); //NON-NLS
822  }
823  }
824 
832  private List<ImageTagRegion> getTaggedRegions(List<ContentTag> contentTags) {
833  ArrayList<ImageTagRegion> tagRegions = new ArrayList<>();
834  contentTags.forEach((contentTag) -> {
835  try {
837  .getTag(contentTag, ImageTagRegion.class);
838  if (contentViewerTag != null) {
839  tagRegions.add(contentViewerTag.getDetails());
840  }
841  } catch (TskCoreException | NoCurrentCaseException ex) {
842  logger.log(Level.WARNING, "Could not get content viewer tag "
843  + "from case db for content_tag with id %d", contentTag.getId());
844  }
845  });
846  return tagRegions;
847  }
848 
854  public void addThumbnailRows(Set<Content> images) {
855  List<String> currentRow = new ArrayList<>();
856  int totalCount = 0;
857  int pages = 1;
858  for (Content content : images) {
859  if (currentRow.size() == THUMBNAIL_COLUMNS) {
860  addRow(currentRow, false);
861  currentRow.clear();
862  }
863 
864  if (totalCount == MAX_THUMBS_PER_PAGE) {
865  // manually set the row count so the count of items shown in the
866  // navigation page reflects the number of thumbnails instead of
867  // the number of rows.
868  rowCount = totalCount;
869  totalCount = 0;
870  pages++;
871  endTable();
872  endDataType();
873  startDataType(NbBundle.getMessage(this.getClass(), "ReportHTML.addThumbRows.dataType.title", pages),
874  NbBundle.getMessage(this.getClass(), "ReportHTML.addThumbRows.dataType.msg"));
875  List<String> emptyHeaders = new ArrayList<>();
876  for (int i = 0; i < THUMBNAIL_COLUMNS; i++) {
877  emptyHeaders.add("");
878  }
879  startTable(emptyHeaders);
880  }
881 
882  if (failsContentCheck(content)) {
883  continue;
884  }
885 
886  AbstractFile file = (AbstractFile) content;
887  List<ContentTag> contentTags = new ArrayList<>();
888 
889  String thumbnailPath = null;
890  String imageWithTagsFullPath = null;
891  try {
892  //Get content tags and all image tags
893  contentTags = Case.getCurrentCase().getServices()
895  List<ImageTagRegion> imageTags = getTaggedRegions(contentTags);
896 
897  if (!imageTags.isEmpty()) {
898  //Write the tags to the fullsize and thumbnail images
899  BufferedImage fullImageWithTags = ImageTagsUtil.getImageWithTags(file, imageTags);
900 
901  BufferedImage thumbnailWithTags = ImageTagsUtil.getThumbnailWithTags(file,
902  imageTags, ImageTagsUtil.IconSize.MEDIUM);
903 
904  String fileName = org.sleuthkit.autopsy.coreutils.FileUtil.escapeFileName(file.getName());
905 
906  //Create paths in report to write tagged images
907  File thumbnailImageWithTagsFile = Paths.get(thumbsPath, FilenameUtils.removeExtension(fileName) + ".png").toFile();
908  String fullImageWithTagsPath = makeCustomUniqueFilePath(file, "thumbs_fullsize");
909  fullImageWithTagsPath = FilenameUtils.removeExtension(fullImageWithTagsPath) + ".png";
910  File fullImageWithTagsFile = Paths.get(fullImageWithTagsPath).toFile();
911 
912  //Save images
913  ImageIO.write(thumbnailWithTags, "png", thumbnailImageWithTagsFile);
914  ImageIO.write(fullImageWithTags, "png", fullImageWithTagsFile);
915 
916  thumbnailPath = THUMBS_REL_PATH + thumbnailImageWithTagsFile.getName();
917  //Relative path
918  imageWithTagsFullPath = fullImageWithTagsPath.substring(subPath.length());
919  }
920  } catch (TskCoreException ex) {
921  logger.log(Level.WARNING, "Could not get tags for file.", ex); //NON-NLS
922  } catch (IOException | InterruptedException | ExecutionException ex) {
923  logger.log(Level.WARNING, "Could make marked up thumbnail.", ex); //NON-NLS
924  }
925 
926  // save copies of the orginal image and thumbnail image
927  if (thumbnailPath == null) {
928  thumbnailPath = prepareThumbnail(file);
929  }
930 
931  if (thumbnailPath == null) {
932  continue;
933  }
934  String contentPath = saveContent(file, "original"); //NON-NLS
935  String nameInImage;
936  try {
937  nameInImage = file.getUniquePath();
938  } catch (TskCoreException ex) {
939  nameInImage = file.getName();
940  }
941 
942  StringBuilder linkToThumbnail = new StringBuilder();
943  linkToThumbnail.append("<div id='thumbnail_link'><a href=\"")
944  .append((imageWithTagsFullPath != null) ? imageWithTagsFullPath : contentPath)
945  .append("\" target=\"_top\"><img src=\"")
946  .append(thumbnailPath).append("\" title=\"").append(nameInImage).append("\"/></a><br>") //NON-NLS
947  .append(file.getName()).append("<br>"); //NON-NLS
948  if (imageWithTagsFullPath != null) {
949  linkToThumbnail.append("<a href=\"").append(contentPath).append("\" target=\"_top\">View Original</a><br>");
950  }
951 
952  if (!contentTags.isEmpty()) {
953  linkToThumbnail.append(NbBundle.getMessage(this.getClass(), "ReportHTML.thumbLink.tags"));
954  }
955  for (int i = 0; i < contentTags.size(); i++) {
956  ContentTag tag = contentTags.get(i);
957  String notableString = tag.getName().getKnownStatus() == TskData.FileKnown.BAD ? TagsManager.getNotableTagLabel() : "";
958  linkToThumbnail.append(tag.getName().getDisplayName()).append(notableString);
959  if (i != contentTags.size() - 1) {
960  linkToThumbnail.append(", ");
961  }
962  }
963 
964  linkToThumbnail.append("</div>");
965  currentRow.add(linkToThumbnail.toString());
966 
967  totalCount++;
968  }
969 
970  if (currentRow.isEmpty() == false) {
971  int extraCells = THUMBNAIL_COLUMNS - currentRow.size();
972  for (int i = 0; i < extraCells; i++) {
973  // Finish out the row.
974  currentRow.add("");
975  }
976  addRow(currentRow, false);
977  }
978 
979  // manually set rowCount to be the total number of images.
980  rowCount = totalCount;
981  }
982 
983  private boolean failsContentCheck(Content c) {
984  if (c instanceof AbstractFile == false) {
985  return true;
986  }
987  AbstractFile file = (AbstractFile) c;
988  return file.isDir()
989  || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS
990  || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS;
991  }
992 
993  private String makeCustomUniqueFilePath(AbstractFile file, String dirName) {
994  // clean up the dir name passed in
995  String dirName2 = org.sleuthkit.autopsy.coreutils.FileUtil.escapeFileName(dirName);
996 
997  // Make a folder for the local file with the same tagName as the tag.
998  StringBuilder localFilePath = new StringBuilder(); // full path
999 
1000  localFilePath.append(subPath);
1001  localFilePath.append(dirName2);
1002  File localFileFolder = new File(localFilePath.toString());
1003  if (!localFileFolder.exists()) {
1004  localFileFolder.mkdirs();
1005  }
1006 
1007  /*
1008  * Construct a file tagName for the local file that incorporates the
1009  * file ID to ensure uniqueness.
1010  *
1011  * Note: File name is normalized to account for possible attribute name
1012  * which will be separated by a ':' character.
1013  */
1014  String fileName = org.sleuthkit.autopsy.coreutils.FileUtil.escapeFileName(file.getName());
1015  String objectIdSuffix = "_" + file.getId();
1016  int lastDotIndex = fileName.lastIndexOf(".");
1017  if (lastDotIndex != -1 && lastDotIndex != 0) {
1018  // The file tagName has a conventional extension. Insert the object id before the '.' of the extension.
1019  fileName = fileName.substring(0, lastDotIndex) + objectIdSuffix + fileName.substring(lastDotIndex, fileName.length());
1020  } else {
1021  // The file has no extension or the only '.' in the file is an initial '.', as in a hidden file.
1022  // Add the object id to the end of the file tagName.
1023  fileName += objectIdSuffix;
1024  }
1025  localFilePath.append(File.separator);
1026  localFilePath.append(fileName);
1027 
1028  return localFilePath.toString();
1029  }
1030 
1040  public String saveContent(AbstractFile file, String dirName) {
1041 
1042  String localFilePath = makeCustomUniqueFilePath(file, dirName);
1043 
1044  // If the local file doesn't already exist, create it now.
1045  // The existence check is necessary because it is possible to apply multiple tags with the same tagName to a file.
1046  File localFile = new File(localFilePath);
1047  if (!localFile.exists()) {
1048  ExtractFscContentVisitor.extract(file, localFile, null, null);
1049  }
1050 
1051  // get the relative path
1052  return localFilePath.substring(subPath.length());
1053  }
1054 
1062  @Override
1063  public String dateToString(long date) {
1064  SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
1065  return sdf.format(new java.util.Date(date * 1000));
1066  }
1067 
1068  @Override
1069  public String getRelativeFilePath() {
1070  return "report.html"; //NON-NLS
1071  }
1072 
1073  @Override
1074  public String getName() {
1075  return NbBundle.getMessage(this.getClass(), "ReportHTML.getName.text");
1076  }
1077 
1078  @Override
1079  public String getDescription() {
1080  return NbBundle.getMessage(this.getClass(), "ReportHTML.getDesc.text");
1081  }
1082 
1086  private void writeCss() {
1087  Writer cssOut = null;
1088  try {
1089  cssOut = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(subPath + "index.css"), "UTF-8")); //NON-NLS NON-NLS
1090  String css = "body {margin: 0px; padding: 0px; background: #FFFFFF; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353;}\n"
1091  + //NON-NLS
1092  "#content {padding: 30px;}\n"
1093  + //NON-NLS
1094  "#header {width:100%; padding: 10px; line-height: 25px; background: #07A; color: #FFF; font-size: 20px;}\n"
1095  + //NON-NLS
1096  "#pageHeaderFooter {width: 100%; padding: 10px; line-height: 25px; text-align: center; font-size: 20px;}\n"
1097  + //NON-NLS
1098  "h1 {font-size: 20px; font-weight: normal; color: #07A; padding: 0 0 7px 0; margin-top: 25px; border-bottom: 1px solid #D6D6D6;}\n"
1099  + //NON-NLS
1100  "h2 {font-size: 20px; font-weight: bolder; color: #07A;}\n"
1101  + //NON-NLS
1102  "h3 {font-size: 16px; color: #07A;}\n"
1103  + //NON-NLS
1104  "h4 {background: #07A; color: #FFF; font-size: 16px; margin: 0 0 0 25px; padding: 0; padding-left: 15px;}\n"
1105  + //NON-NLS
1106  "ul.nav {list-style-type: none; line-height: 35px; padding: 0px; margin-left: 15px;}\n"
1107  + //NON-NLS
1108  "ul li a {font-size: 14px; color: #444; text-decoration: none; padding-left: 25px;}\n"
1109  + //NON-NLS
1110  "ul li a:hover {text-decoration: underline;}\n"
1111  + //NON-NLS
1112  "p {margin: 0 0 20px 0;}\n"
1113  + //NON-NLS
1114  "table {white-space:nowrap; min-width: 700px; padding: 2; margin: 0; border-collapse: collapse; border-bottom: 2px solid #e5e5e5;}\n"
1115  + //NON-NLS
1116  ".keyword_list table {margin: 0 0 25px 25px; border-bottom: 2px solid #dedede;}\n"
1117  + //NON-NLS
1118  "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"
1119  + //NON-NLS
1120  "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"
1121  + //NON-NLS
1122  "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"
1123  + //NON-NLS
1124  "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"
1125  + //NON-NLS
1126  "table tr:nth-child(even) td {background: #f3f3f3;}\n"
1127  + //NON-NLS
1128  "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;}";
1129  cssOut.write(css);
1130  } catch (FileNotFoundException ex) {
1131  logger.log(Level.SEVERE, "Could not find index.css file to write to.", ex); //NON-NLS
1132  } catch (UnsupportedEncodingException ex) {
1133  logger.log(Level.SEVERE, "Did not recognize encoding when writing index.css.", ex); //NON-NLS
1134  } catch (IOException ex) {
1135  logger.log(Level.SEVERE, "Error creating Writer for index.css.", ex); //NON-NLS
1136  } finally {
1137  try {
1138  if (cssOut != null) {
1139  cssOut.flush();
1140  cssOut.close();
1141  }
1142  } catch (IOException ex) {
1143  }
1144  }
1145  }
1146 
1150  private void writeIndex() {
1151  Writer indexOut = null;
1152  String indexFilePath = path + "report.html"; //NON-NLS
1153  Case openCase;
1154  try {
1155  openCase = Case.getCurrentCaseThrows();
1156  } catch (NoCurrentCaseException ex) {
1157  logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS
1158  return;
1159  }
1160  try {
1161  indexOut = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(indexFilePath), "UTF-8")); //NON-NLS
1162  StringBuilder index = new StringBuilder();
1163  final String reportTitle = reportBranding.getReportTitle();
1164  String iconPath = reportBranding.getAgencyLogoPath();
1165  if (iconPath == null) {
1166  // use default Autopsy icon if custom icon is not set
1167  iconPath = HTML_SUBDIR + "favicon.ico";
1168  } else {
1169  iconPath = Paths.get(reportBranding.getAgencyLogoPath()).getFileName().toString(); //ref to writeNav() for agency_logo
1170  }
1171  index.append("<head>\n<title>").append(reportTitle).append(" ").append(
1172  NbBundle.getMessage(this.getClass(), "ReportHTML.writeIndex.title", currentCase.getDisplayName())).append(
1173  "</title>\n"); //NON-NLS
1174  index.append("<link rel=\"icon\" type=\"image/ico\" href=\"")
1175  .append(iconPath).append("\" />\n"); //NON-NLS
1176  index.append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"); //NON-NLS
1177  index.append("</head>\n"); //NON-NLS
1178  index.append("<frameset cols=\"350px,*\">\n"); //NON-NLS
1179  index.append("<frame src=\"" + HTML_SUBDIR).append(File.separator).append("nav.html\" name=\"nav\">\n"); //NON-NLS
1180  index.append("<frame src=\"" + HTML_SUBDIR).append(File.separator).append("summary.html\" name=\"content\">\n"); //NON-NLS
1181  index.append("<noframes>").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeIndex.noFrames.msg")).append("<br />\n"); //NON-NLS
1182  index.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeIndex.noFrames.seeNav")).append("<br />\n"); //NON-NLS
1183  index.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeIndex.seeSum")).append("</noframes>\n"); //NON-NLS
1184  index.append("</frameset>\n"); //NON-NLS
1185  index.append("</html>"); //NON-NLS
1186  indexOut.write(index.toString());
1187  openCase.addReport(indexFilePath, NbBundle.getMessage(this.getClass(),
1188  "ReportHTML.writeIndex.srcModuleName.text"), "");
1189  } catch (IOException ex) {
1190  logger.log(Level.SEVERE, "Error creating Writer for report.html: {0}", ex); //NON-NLS
1191  } catch (TskCoreException ex) {
1192  String errorMessage = String.format("Error adding %s to case as a report", indexFilePath); //NON-NLS
1193  logger.log(Level.SEVERE, errorMessage, ex);
1194  } finally {
1195  try {
1196  if (indexOut != null) {
1197  indexOut.flush();
1198  indexOut.close();
1199  }
1200  } catch (IOException ex) {
1201  }
1202  }
1203  }
1204 
1208  private void writeNav() {
1209  Writer navOut = null;
1210  try {
1211  navOut = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(subPath + "nav.html"), "UTF-8")); //NON-NLS
1212  StringBuilder nav = new StringBuilder();
1213  nav.append("<html>\n<head>\n\t<title>").append( //NON-NLS
1214  NbBundle.getMessage(this.getClass(), "ReportHTML.writeNav.title"))
1215  .append("</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"index.css\" />\n"); //NON-NLS
1216  nav.append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n</head>\n<body>\n"); //NON-NLS
1217  nav.append("<div id=\"content\">\n<h1>").append( //NON-NLS
1218  NbBundle.getMessage(this.getClass(), "ReportHTML.writeNav.h1")).append("</h1>\n"); //NON-NLS
1219  nav.append("<ul class=\"nav\">\n"); //NON-NLS
1220  nav.append("<li style=\"background: url(summary.png) left center no-repeat;\"><a href=\"summary.html\" target=\"content\">") //NON-NLS
1221  .append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeNav.summary")).append("</a></li>\n"); //NON-NLS
1222 
1223  for (String dataType : dataTypes.keySet()) {
1224  String dataTypeEsc = dataTypeToFileName(dataType);
1225  String iconFileName = useDataTypeIcon(dataType);
1226  nav.append("<li style=\"background: url('").append(iconFileName) //NON-NLS
1227  .append("') left center no-repeat;\"><a href=\"") //NON-NLS
1228  .append(dataTypeEsc).append(".html\" target=\"content\">") //NON-NLS
1229  .append(dataType).append(" (").append(dataTypes.get(dataType))
1230  .append(")</a></li>\n"); //NON-NLS
1231  }
1232  nav.append("</ul>\n"); //NON-NLS
1233  nav.append("</div>\n</body>\n</html>"); //NON-NLS
1234  navOut.write(nav.toString());
1235  } catch (IOException ex) {
1236  logger.log(Level.SEVERE, "Failed to write end of report navigation menu: {0}", ex); //NON-NLS
1237  } finally {
1238  if (navOut != null) {
1239  try {
1240  navOut.flush();
1241  navOut.close();
1242  } catch (IOException ex) {
1243  logger.log(Level.WARNING, "Could not close navigation out writer."); //NON-NLS
1244  }
1245  }
1246  }
1247 
1248  InputStream in = null;
1249  OutputStream output = null;
1250  try {
1251 
1252  //pull generator and agency logo from branding, and the remaining resources from the core jar
1253  String generatorLogoPath = reportBranding.getGeneratorLogoPath();
1254  if (generatorLogoPath != null && !generatorLogoPath.isEmpty()) {
1255  File from = new File(generatorLogoPath);
1256  File to = new File(subPath);
1257  FileUtil.copyFile(FileUtil.toFileObject(from), FileUtil.toFileObject(to), "generator_logo"); //NON-NLS
1258  }
1259 
1260  String agencyLogoPath = reportBranding.getAgencyLogoPath();
1261  if (agencyLogoPath != null && !agencyLogoPath.isEmpty()) {
1262  Path destinationPath = Paths.get(subPath);
1263  Files.copy(Files.newInputStream(Paths.get(agencyLogoPath)), destinationPath.resolve(Paths.get(agencyLogoPath).getFileName())); //NON-NLS
1264  }
1265 
1266  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/favicon.ico"); //NON-NLS
1267  output = new FileOutputStream(new File(subPath + "favicon.ico"));
1268  FileUtil.copy(in, output);
1269  in.close();
1270  output.close();
1271 
1272  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/summary.png"); //NON-NLS
1273  output = new FileOutputStream(new File(subPath + "summary.png"));
1274  FileUtil.copy(in, output);
1275  in.close();
1276  output.close();
1277 
1278  } catch (IOException ex) {
1279  logger.log(Level.SEVERE, "Failed to extract images for HTML report.", ex); //NON-NLS
1280  } finally {
1281  if (output != null) {
1282  try {
1283  output.flush();
1284  output.close();
1285  } catch (IOException ex) {
1286  }
1287  }
1288  if (in != null) {
1289  try {
1290  in.close();
1291  } catch (IOException ex) {
1292  }
1293  }
1294  }
1295  }
1296 
1300  private void writeSummary() {
1301  Writer output = null;
1302  try {
1303  output = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(subPath + "summary.html"), "UTF-8")); //NON-NLS
1304  StringBuilder head = new StringBuilder();
1305  head.append("<html>\n<head>\n<title>").append( //NON-NLS
1306  NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.title")).append("</title>\n"); //NON-NLS
1307  head.append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"); //NON-NLS
1308  head.append("<style type=\"text/css\">\n"); //NON-NLS
1309  head.append("#pageHeaderFooter {width: 100%; padding: 10px; line-height: 25px; text-align: center; font-size: 20px;}\n"); //NON-NLS
1310  head.append("body { padding: 0px; margin: 0px; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353; }\n"); //NON-NLS
1311  head.append("#wrapper { width: 90%; margin: 0px auto; margin-top: 35px; }\n"); //NON-NLS
1312  head.append("h1 { color: #07A; font-size: 36px; line-height: 42px; font-weight: normal; margin: 0px; border-bottom: 1px solid #81B9DB; }\n"); //NON-NLS
1313  head.append("h1 span { color: #F00; display: block; font-size: 16px; font-weight: bold; line-height: 22px;}\n"); //NON-NLS
1314  head.append("h2 { padding: 0 0 3px 0; margin: 0px; color: #07A; font-weight: normal; border-bottom: 1px dotted #81B9DB; }\n"); //NON-NLS
1315  head.append("h3 { padding: 5 0 3px 0; margin: 0px; color: #07A; font-weight: normal; }\n");
1316  head.append("table td { padding: 5px 25px 5px 0px; vertical-align:top;}\n"); //NON-NLS
1317  head.append("p.subheadding { padding: 0px; margin: 0px; font-size: 11px; color: #B5B5B5; }\n"); //NON-NLS
1318  head.append(".title { width: 660px; margin-bottom: 50px; }\n"); //NON-NLS
1319  head.append(".left { float: left; width: 250px; margin-top: 20px; text-align: center; }\n"); //NON-NLS
1320  head.append(".left img { max-width: 250px; max-height: 250px; min-width: 200px; min-height: 200px; }\n"); //NON-NLS
1321  head.append(".right { float: right; width: 385px; margin-top: 25px; font-size: 14px; }\n"); //NON-NLS
1322  head.append(".clear { clear: both; }\n"); //NON-NLS
1323  head.append(".info { padding: 10px 0;}\n");
1324  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
1325  head.append(".info table { margin: 10px 25px 10px 25px; }\n"); //NON-NLS
1326  head.append("ul {padding: 0;margin: 0;list-style-type: none;}");
1327  head.append("li {padding-bottom: 5px;}");
1328  head.append("</style>\n"); //NON-NLS
1329  head.append("</head>\n<body>\n"); //NON-NLS
1330  output.write(head.toString());
1331 
1332  DateFormat datetimeFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
1333  Date date = new Date();
1334  String datetime = datetimeFormat.format(date);
1335 
1336  StringBuilder summary = new StringBuilder();
1337  boolean running = false;
1339  running = true;
1340  }
1341  SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
1342  List<IngestJobInfo> ingestJobs = skCase.getIngestJobs();
1343  final String reportTitle = reportBranding.getReportTitle();
1344  final String reportFooter = reportBranding.getReportFooter();
1345  final boolean generatorLogoSet = reportBranding.getGeneratorLogoPath() != null && !reportBranding.getGeneratorLogoPath().isEmpty();
1346 
1347  summary.append("<div id=\"wrapper\">\n"); //NON-NLS
1348  summary.append(writePageHeader());
1349  summary.append("<h1>").append(reportTitle) //NON-NLS
1350  .append(running ? NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.warningMsg") : "")
1351  .append("</h1>\n"); //NON-NLS
1352  summary.append("<p class=\"subheadding\">").append( //NON-NLS
1353  NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.reportGenOn.text", datetime)).append("</p>\n"); //NON-NLS
1354  summary.append("<div class=\"title\">\n"); //NON-NLS
1355  summary.append(writeSummaryCaseDetails());
1356  summary.append(writeSummaryImageInfo());
1357  summary.append(writeSummarySoftwareInfo(skCase, ingestJobs));
1358  summary.append(writeSummaryIngestHistoryInfo(skCase, ingestJobs));
1359  if (generatorLogoSet) {
1360  summary.append("<div class=\"left\">\n"); //NON-NLS
1361  summary.append("<img src=\"generator_logo.png\" />\n"); //NON-NLS
1362  summary.append("</div>\n"); //NON-NLS
1363  }
1364  summary.append("<div class=\"clear\"></div>\n"); //NON-NLS
1365  if (reportFooter != null) {
1366  summary.append("<p class=\"subheadding\">").append(reportFooter).append("</p>\n"); //NON-NLS
1367  }
1368  summary.append("</div>\n"); //NON-NLS
1369  summary.append(writePageFooter());
1370  summary.append("</body></html>"); //NON-NLS
1371  output.write(summary.toString());
1372  } catch (FileNotFoundException ex) {
1373  logger.log(Level.SEVERE, "Could not find summary.html file to write to."); //NON-NLS
1374  } catch (UnsupportedEncodingException ex) {
1375  logger.log(Level.SEVERE, "Did not recognize encoding when writing summary.hmtl."); //NON-NLS
1376  } catch (IOException ex) {
1377  logger.log(Level.SEVERE, "Error creating Writer for summary.html."); //NON-NLS
1378  } catch (NoCurrentCaseException | TskCoreException ex) {
1379  logger.log(Level.WARNING, "Unable to get current sleuthkit Case for the HTML report.");
1380  } finally {
1381  try {
1382  if (output != null) {
1383  output.flush();
1384  output.close();
1385  }
1386  } catch (IOException ex) {
1387  }
1388  }
1389  }
1390 
1391  @Messages({
1392  "ReportHTML.writeSum.case=Case:",
1393  "ReportHTML.writeSum.caseNumber=Case Number:",
1394  "ReportHTML.writeSum.caseNumImages=Number of data sources in case:",
1395  "ReportHTML.writeSum.caseNotes=Notes:",
1396  "ReportHTML.writeSum.examiner=Examiner:"
1397  })
1403  private StringBuilder writeSummaryCaseDetails() {
1404  StringBuilder summary = new StringBuilder();
1405 
1406  final boolean agencyLogoSet = reportBranding.getAgencyLogoPath() != null && !reportBranding.getAgencyLogoPath().isEmpty();
1407 
1408  // Case
1409  String caseName = currentCase.getDisplayName();
1410  String caseNumber = currentCase.getNumber();
1411  int imagecount;
1412  try {
1413  imagecount = currentCase.getDataSources().size();
1414  } catch (TskCoreException ex) {
1415  imagecount = 0;
1416  }
1417  String caseNotes = currentCase.getCaseNotes();
1418 
1419  // Examiner
1420  String examinerName = currentCase.getExaminer();
1421 
1422  // Start the layout.
1423  summary.append("<div class=\"title\">\n"); //NON-NLS
1424  if (agencyLogoSet) {
1425  summary.append("<div class=\"left\">\n"); //NON-NLS
1426  summary.append("<img src=\"");
1427  summary.append(Paths.get(reportBranding.getAgencyLogoPath()).getFileName().toString());
1428  summary.append("\" />\n"); //NON-NLS
1429  summary.append("</div>\n"); //NON-NLS
1430  }
1431  final String align = agencyLogoSet ? "right" : "left"; //NON-NLS NON-NLS
1432  summary.append("<div class=\"").append(align).append("\">\n"); //NON-NLS
1433  summary.append("<table>\n"); //NON-NLS
1434 
1435  // Case details
1436  summary.append("<tr><td>").append(Bundle.ReportHTML_writeSum_case()).append("</td><td>") //NON-NLS
1437  .append(formatHtmlString(caseName)).append("</td></tr>\n"); //NON-NLS
1438 
1439  if (!caseNumber.isEmpty()) {
1440  summary.append("<tr><td>").append(Bundle.ReportHTML_writeSum_caseNumber()).append("</td><td>") //NON-NLS
1441  .append(formatHtmlString(caseNumber)).append("</td></tr>\n"); //NON-NLS
1442  }
1443 
1444  summary.append("<tr><td>").append(Bundle.ReportHTML_writeSum_caseNumImages()).append("</td><td>") //NON-NLS
1445  .append(imagecount).append("</td></tr>\n"); //NON-NLS
1446 
1447  if (!caseNotes.isEmpty()) {
1448  summary.append("<tr><td>").append(Bundle.ReportHTML_writeSum_caseNotes()).append("</td><td>") //NON-NLS
1449  .append(formatHtmlString(caseNotes)).append("</td></tr>\n"); //NON-NLS
1450  }
1451 
1452  // Examiner details
1453  if (!examinerName.isEmpty()) {
1454  summary.append("<tr><td>").append(Bundle.ReportHTML_writeSum_examiner()).append("</td><td>") //NON-NLS
1455  .append(formatHtmlString(examinerName)).append("</td></tr>\n"); //NON-NLS
1456  }
1457 
1458  // End the layout.
1459  summary.append("</table>\n"); //NON-NLS
1460  summary.append("</div>\n"); //NON-NLS
1461  summary.append("<div class=\"clear\"></div>\n"); //NON-NLS
1462  summary.append("</div>\n"); //NON-NLS
1463  return summary;
1464  }
1465 
1471  private StringBuilder writeSummaryImageInfo() {
1472  StringBuilder summary = new StringBuilder();
1473  summary.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.imageInfoHeading"));
1474  summary.append("<div class=\"info\">\n"); //NON-NLS
1475  try {
1476  for (Content c : currentCase.getDataSources()) {
1477  summary.append("<p>").append(c.getName()).append("</p>\n"); //NON-NLS
1478  if (c instanceof Image) {
1479  Image img = (Image) c;
1480 
1481  summary.append("<table>\n"); //NON-NLS
1482  summary.append("<tr><td>").append( //NON-NLS
1483  NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.timezone"))
1484  .append("</td><td>").append(img.getTimeZone()).append("</td></tr>\n"); //NON-NLS
1485  for (String imgPath : img.getPaths()) {
1486  summary.append("<tr><td>").append( //NON-NLS
1487  NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.path"))
1488  .append("</td><td>").append(imgPath).append("</td></tr>\n"); //NON-NLS
1489  }
1490  summary.append("</table>\n"); //NON-NLS
1491  }
1492  }
1493  } catch (TskCoreException ex) {
1494  logger.log(Level.WARNING, "Unable to get image information for the HTML report."); //NON-NLS
1495  }
1496  summary.append("</div>\n"); //NON-NLS
1497  return summary;
1498  }
1499 
1505  private StringBuilder writeSummarySoftwareInfo(SleuthkitCase skCase, List<IngestJobInfo> ingestJobs) {
1506  StringBuilder summary = new StringBuilder();
1507  summary.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.softwareInfoHeading"));
1508  summary.append("<div class=\"info\">\n");
1509  summary.append("<table>\n");
1510  summary.append("<tr><td>").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.autopsyVersion"))
1511  .append("</td><td>").append(Version.getVersion()).append("</td></tr>\n");
1512  Map<Long, IngestModuleInfo> moduleInfoHashMap = new HashMap<>();
1513  for (IngestJobInfo ingestJob : ingestJobs) {
1514  List<IngestModuleInfo> ingestModules = ingestJob.getIngestModuleInfo();
1515  for (IngestModuleInfo ingestModule : ingestModules) {
1516  if (!moduleInfoHashMap.containsKey(ingestModule.getIngestModuleId())) {
1517  moduleInfoHashMap.put(ingestModule.getIngestModuleId(), ingestModule);
1518  }
1519  }
1520  }
1521  TreeMap<String, String> modules = new TreeMap<>();
1522  for (IngestModuleInfo moduleinfo : moduleInfoHashMap.values()) {
1523  modules.put(moduleinfo.getDisplayName(), moduleinfo.getVersion());
1524  }
1525  for (Map.Entry<String, String> module : modules.entrySet()) {
1526  summary.append("<tr><td>").append(module.getKey()).append(" Module:")
1527  .append("</td><td>").append(module.getValue()).append("</td></tr>\n");
1528  }
1529  summary.append("</table>\n");
1530  summary.append("</div>\n");
1531  summary.append("<div class=\"clear\"></div>\n"); //NON-NLS
1532  return summary;
1533  }
1534 
1540  private StringBuilder writeSummaryIngestHistoryInfo(SleuthkitCase skCase, List<IngestJobInfo> ingestJobs) {
1541  StringBuilder summary = new StringBuilder();
1542  try {
1543  summary.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.ingestHistoryHeading"));
1544  summary.append("<div class=\"info\">\n");
1545  int jobnumber = 1;
1546 
1547  for (IngestJobInfo ingestJob : ingestJobs) {
1548  summary.append("<h3>Job ").append(jobnumber).append(":</h3>\n");
1549  summary.append("<table>\n");
1550  summary.append("<tr><td>").append("Data Source:")
1551  .append("</td><td>").append(skCase.getContentById(ingestJob.getObjectId()).getName()).append("</td></tr>\n");
1552  summary.append("<tr><td>").append("Status:")
1553  .append("</td><td>").append(ingestJob.getStatus()).append("</td></tr>\n");
1554  summary.append("<tr><td>").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.modulesEnabledHeading"))
1555  .append("</td><td>");
1556  List<IngestModuleInfo> ingestModules = ingestJob.getIngestModuleInfo();
1557  summary.append("<ul>\n");
1558  for (IngestModuleInfo ingestModule : ingestModules) {
1559  summary.append("<li>").append(ingestModule.getDisplayName()).append("</li>");
1560  }
1561  summary.append("</ul>\n");
1562  jobnumber++;
1563  summary.append("</td></tr>\n");
1564  summary.append("</table>\n");
1565  }
1566  summary.append("</div>\n");
1567  } catch (TskCoreException ex) {
1568  logger.log(Level.WARNING, "Unable to get ingest jobs for the HTML report.");
1569  }
1570  return summary;
1571  }
1572 
1581  private String prepareThumbnail(AbstractFile file) {
1582  BufferedImage bufferedThumb = ImageUtils.getThumbnail(file, ImageUtils.ICON_SIZE_MEDIUM);
1583 
1584  /*
1585  * File name is normalized to account for possible attribute name which
1586  * will be separated by a ':' character.
1587  */
1588  String fileName = org.sleuthkit.autopsy.coreutils.FileUtil.escapeFileName(file.getName());
1589 
1590  File thumbFile = Paths.get(thumbsPath, fileName + ".png").toFile();
1591  if (bufferedThumb == null) {
1592  return null;
1593  }
1594  try {
1595  ImageIO.write(bufferedThumb, "png", thumbFile);
1596  } catch (IOException ex) {
1597  logger.log(Level.WARNING, "Failed to write thumb file to report directory.", ex); //NON-NLS
1598  return null;
1599  }
1600  if (thumbFile.exists()
1601  == false) {
1602  return null;
1603  }
1604  return THUMBS_REL_PATH
1605  + thumbFile.getName();
1606  }
1607 
1616  private String formatHtmlString(String text) {
1617  String formattedString = StringEscapeUtils.escapeHtml4(text);
1618  return formattedString.replaceAll("(\r\n|\r|\n|\n\r)", "<br>");
1619  }
1620 
1621 }
List< Content > getDataSources()
Definition: Case.java:1696
static String escapeHtml(String toEscape)
Definition: EscapeUtil.java:75
void startContentTagsTable(List< String > columnHeaders)
List< ImageTagRegion > getTaggedRegions(List< ContentTag > contentTags)
static synchronized IngestManager getInstance()
void addRow(List< String > row, boolean escapeText)
void setConfiguration(ReportModuleSettings settings)
List< ContentTag > getContentTagsByContent(Content content)
void addReport(String localPath, String srcModuleName, String reportName)
Definition: Case.java:1919
static BufferedImage getImageWithTags(AbstractFile file, Collection< ImageTagRegion > tagRegions)
static< T > ContentViewerTag< T > getTag(ContentTag contentTag, Class< T > clazz)
static BufferedImage getThumbnailWithTags(AbstractFile file, Collection< ImageTagRegion > tagRegions, IconSize iconSize)
void startDataType(String name, String description)
void addRowWithTaggedContentHyperlink(List< String > row, ContentTag contentTag)
static< T, V > void extract(Content cntnt, java.io.File dest, ProgressHandle progress, SwingWorker< T, V > worker)
static synchronized HTMLReport getDefault()
StringBuilder writeSummaryIngestHistoryInfo(SleuthkitCase skCase, List< IngestJobInfo > ingestJobs)
void close(ProgressIndicator progressIndicator)
Definition: Case.java:3044
static String escapeFileName(String fileName)
Definition: FileUtil.java:169
StringBuilder writeSummarySoftwareInfo(SleuthkitCase skCase, List< IngestJobInfo > ingestJobs)
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
String makeCustomUniqueFilePath(AbstractFile file, String dirName)
String saveContent(AbstractFile file, String dirName)
static BufferedImage getThumbnail(Content content, int iconSize)

Copyright © 2012-2022 Basis Technology. Generated on: Tue Aug 1 2023
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.