Autopsy  4.19.1
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.lang3.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  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/interesting_item.png"); //NON-NLS
330  break;
331  case TSK_INTERESTING_FILE_HIT:
332  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/interesting_item.png"); //NON-NLS
333  break;
334  case TSK_PROG_RUN:
335  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/installed.png"); //NON-NLS
336  break;
337  case TSK_REMOTE_DRIVE:
338  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/drive_network.png"); //NON-NLS
339  break;
340  case TSK_OS_ACCOUNT:
341  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/os-account.png"); //NON-NLS
342  break;
343  case TSK_OBJECT_DETECTED:
344  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/objects.png"); //NON-NLS
345  break;
346  case TSK_WEB_FORM_AUTOFILL:
347  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/web-form.png"); //NON-NLS
348  break;
349  case TSK_WEB_CACHE:
350  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/cache.png"); //NON-NLS
351  break;
352  case TSK_USER_CONTENT_SUSPECTED:
353  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/user-content.png"); //NON-NLS
354  break;
355  case TSK_METADATA:
356  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/metadata.png"); //NON-NLS
357  break;
358  case TSK_CLIPBOARD_CONTENT:
359  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/clipboard.png"); //NON-NLS
360  break;
361  case TSK_ACCOUNT:
362  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/accounts.png"); //NON-NLS
363  break;
364  case TSK_WIFI_NETWORK:
365  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/network-wifi.png"); //NON-NLS
366  break;
367  case TSK_WIFI_NETWORK_ADAPTER:
368  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/network-wifi.png"); //NON-NLS
369  break;
370  case TSK_SIM_ATTACHED:
371  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/sim_card.png"); //NON-NLS
372  break;
373  case TSK_BLUETOOTH_ADAPTER:
374  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/Bluetooth.png"); //NON-NLS
375  break;
376  case TSK_DEVICE_INFO:
377  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/devices.png"); //NON-NLS
378  break;
379  case TSK_VERIFICATION_FAILED:
380  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/validationFailed.png"); //NON-NLS
381  break;
382  case TSK_WEB_ACCOUNT_TYPE:
383  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/web-account-type.png"); //NON-NLS
384  break;
385  case TSK_WEB_FORM_ADDRESS:
386  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/web-form-address.png"); //NON-NLS
387  break;
388  case TSK_GPS_AREA:
389  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/gps-area.png"); //NON-NLS
390  break;
391  case TSK_WEB_CATEGORIZATION:
392  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/domain-16.png"); //NON-NLS
393  break;
394  case TSK_YARA_HIT:
395  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/yara_16.png"); //NON-NLS
396  break;
397  case TSK_PREVIOUSLY_SEEN:
398  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/previously-seen.png"); //NON-NLS
399  break;
400  case TSK_PREVIOUSLY_UNSEEN:
401  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/previously-unseen.png"); //NON-NLS
402  break;
403  case TSK_PREVIOUSLY_NOTABLE:
404  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/red-circle-exclamation.png"); //NON-NLS
405  break;
406  default:
407  logger.log(Level.WARNING, "useDataTypeIcon: unhandled artifact type = {0}", dataType); //NON-NLS
408  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/star.png"); //NON-NLS
409  iconFileName = "star.png"; //NON-NLS
410  iconFilePath = subPath + File.separator + iconFileName;
411  break;
412  }
413  } else if (dataType.startsWith(ARTIFACT_TYPE.TSK_ACCOUNT.getDisplayName())) {
414  /*
415  * TSK_ACCOUNT artifacts get separated by their TSK_ACCOUNT_TYPE
416  * attribute, with a synthetic compound dataType name, so they are
417  * not caught by the switch statement above. For now we just give
418  * them all the general account icon, but we could do something else
419  * in the future.
420  */
421  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/accounts.png"); //NON-NLS
422  iconFileName = "accounts.png"; //NON-NLS
423  iconFilePath = subPath + File.separator + iconFileName;
424  } else { // no defined artifact found for this dataType
425  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/star.png"); //NON-NLS
426  iconFileName = "star.png"; //NON-NLS
427  iconFilePath = subPath + File.separator + iconFileName;
428  }
429 
430  try {
431  output = new FileOutputStream(iconFilePath);
432  FileUtil.copy(in, output);
433  in.close();
434  output.close();
435  } catch (IOException ex) {
436  logger.log(Level.SEVERE, "Failed to extract images for HTML report.", ex); //NON-NLS
437  } finally {
438  if (output != null) {
439  try {
440  output.flush();
441  output.close();
442  } catch (IOException ex) {
443  }
444  }
445  if (in != null) {
446  try {
447  in.close();
448  } catch (IOException ex) {
449  }
450  }
451  }
452 
453  return iconFileName;
454  }
455 
462  @Override
463  public void startReport(String baseReportDir) {
464 
465  // Refresh the HTML report
466  try {
467  refresh();
468  } catch (NoCurrentCaseException ex) {
469  logger.log(Level.SEVERE, "Exception while getting open case."); //NON-NLS
470  return;
471  }
472  // Setup the path for the HTML report
473  this.path = baseReportDir; //NON-NLS
474  this.subPath = this.path + HTML_SUBDIR + File.separator;
475  this.thumbsPath = this.subPath + THUMBS_REL_PATH; //NON-NLS
476  try {
477  FileUtil.createFolder(new File(this.subPath));
478  FileUtil.createFolder(new File(this.thumbsPath));
479  } catch (IOException ex) {
480  logger.log(Level.SEVERE, "Unable to make HTML report folder."); //NON-NLS
481  }
482  // Write the basic files
483  writeCss();
484  writeIndex();
485  writeSummary();
486  }
487 
492  @Override
493  public void endReport() {
494  writeNav();
495  if (out != null) {
496  try {
497  out.close();
498  } catch (IOException ex) {
499  logger.log(Level.WARNING, "Could not close the output writer when ending report.", ex); //NON-NLS
500  }
501  }
502  }
503 
512  @Override
513  public void startDataType(String name, String description) {
514  String title = dataTypeToFileName(name);
515  try {
516  out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(subPath + title + ".html"), "UTF-8")); //NON-NLS
517  } catch (FileNotFoundException ex) {
518  logger.log(Level.SEVERE, "File not found: {0}", ex); //NON-NLS
519  } catch (UnsupportedEncodingException ex) {
520  logger.log(Level.SEVERE, "Unrecognized encoding"); //NON-NLS
521  }
522 
523  try {
524  StringBuilder page = new StringBuilder();
525  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
526  .append(writePageHeader())
527  .append("<div id=\"header\">").append(name).append("</div>\n")
528  .append("<div id=\"content\">\n"); //NON-NLS
529  if (!description.isEmpty()) {
530  page.append("<p><strong>"); //NON-NLS
531  page.append(description);
532  page.append("</strong></p>\n"); //NON-NLS
533  }
534  out.write(page.toString());
535  currentDataType = name;
536  rowCount = 0;
537  } catch (IOException ex) {
538  logger.log(Level.SEVERE, "Failed to write page head: {0}", ex); //NON-NLS
539  }
540  }
541 
546  @Override
547  public void endDataType() {
548  dataTypes.put(currentDataType, rowCount);
549  try {
550  StringBuilder builder = new StringBuilder();
551  builder.append(writePageFooter());
552  builder.append("</div>\n</body>\n</html>\n"); //NON-NLS
553  out.write(builder.toString());
554  } catch (IOException ex) {
555  logger.log(Level.SEVERE, "Failed to write end of HTML report.", ex); //NON-NLS
556  } finally {
557  if (out != null) {
558  try {
559  out.flush();
560  out.close();
561  } catch (IOException ex) {
562  logger.log(Level.WARNING, "Could not close the output writer when ending data type.", ex); //NON-NLS
563  }
564  out = null;
565  }
566  }
567  }
568 
575  private String writePageHeader() {
576  StringBuilder output = new StringBuilder();
577  String pageHeader = configPanel.getHeader();
578  if (pageHeader.isEmpty() == false) {
579  output.append("<div id=\"pageHeaderFooter\">")
580  .append(StringEscapeUtils.escapeHtml4(pageHeader))
581  .append("</div>\n"); //NON-NLS
582  }
583  return output.toString();
584  }
585 
592  private String writePageFooter() {
593  StringBuilder output = new StringBuilder();
594  String pageFooter = configPanel.getFooter();
595  if (pageFooter.isEmpty() == false) {
596  output.append("<br/><div id=\"pageHeaderFooter\">")
597  .append(StringEscapeUtils.escapeHtml4(pageFooter))
598  .append("</div>"); //NON-NLS
599  }
600  return output.toString();
601  }
602 
608  @Override
609  public void startSet(String setName) {
610  StringBuilder set = new StringBuilder();
611  set.append("<h1><a name=\"").append(setName).append("\">").append(setName).append("</a></h1>\n"); //NON-NLS
612  set.append("<div class=\"keyword_list\">\n"); //NON-NLS
613 
614  try {
615  out.write(set.toString());
616  } catch (IOException ex) {
617  logger.log(Level.SEVERE, "Failed to write set: {0}", ex); //NON-NLS
618  }
619  }
620 
624  @Override
625  public void endSet() {
626  try {
627  out.write("</div>\n"); //NON-NLS
628  } catch (IOException ex) {
629  logger.log(Level.SEVERE, "Failed to write end of set: {0}", ex); //NON-NLS
630  }
631  }
632 
638  @Override
639  public void addSetIndex(List<String> sets) {
640  StringBuilder index = new StringBuilder();
641  index.append("<ul>\n"); //NON-NLS
642  for (String set : sets) {
643  index.append("\t<li><a href=\"#").append(set).append("\">").append(set).append("</a></li>\n"); //NON-NLS
644  }
645  index.append("</ul>\n"); //NON-NLS
646  try {
647  out.write(index.toString());
648  } catch (IOException ex) {
649  logger.log(Level.SEVERE, "Failed to add set index: {0}", ex); //NON-NLS
650  }
651  }
652 
658  @Override
659  public void addSetElement(String elementName) {
660  try {
661  out.write("<h4>" + elementName + "</h4>\n"); //NON-NLS
662  } catch (IOException ex) {
663  logger.log(Level.SEVERE, "Failed to write set element: {0}", ex); //NON-NLS
664  }
665  }
666 
672  @Override
673  public void startTable(List<String> titles) {
674  StringBuilder ele = new StringBuilder();
675  ele.append("<table>\n<thead>\n\t<tr>\n"); //NON-NLS
676  for (String title : titles) {
677  ele.append("\t\t<th>").append(title).append("</th>\n"); //NON-NLS
678  }
679  ele.append("\t</tr>\n</thead>\n"); //NON-NLS
680 
681  try {
682  out.write(ele.toString());
683  } catch (IOException ex) {
684  logger.log(Level.SEVERE, "Failed to write table start: {0}", ex); //NON-NLS
685  }
686  }
687 
694  public void startContentTagsTable(List<String> columnHeaders) {
695  StringBuilder htmlOutput = new StringBuilder();
696  htmlOutput.append("<table>\n<thead>\n\t<tr>\n"); //NON-NLS
697 
698  // Add the specified columns.
699  for (String columnHeader : columnHeaders) {
700  htmlOutput.append("\t\t<th>").append(columnHeader).append("</th>\n"); //NON-NLS
701  }
702 
703  // Add a column for a hyperlink to a local copy of the tagged content.
704  htmlOutput.append("\t\t<th></th>\n"); //NON-NLS
705 
706  htmlOutput.append("\t</tr>\n</thead>\n"); //NON-NLS
707 
708  try {
709  out.write(htmlOutput.toString());
710  } catch (IOException ex) {
711  logger.log(Level.SEVERE, "Failed to write table start: {0}", ex); //NON-NLS
712  }
713  }
714 
718  @Override
719  public void endTable() {
720  try {
721  out.write("</table>\n"); //NON-NLS
722  } catch (IOException ex) {
723  logger.log(Level.SEVERE, "Failed to write end of table: {0}", ex); //NON-NLS
724  }
725  }
726 
733  @Override
734  public void addRow(List<String> row) {
735  addRow(row, true);
736  }
737 
745  private void addRow(List<String> row, boolean escapeText) {
746  StringBuilder builder = new StringBuilder();
747  builder.append("\t<tr>\n"); //NON-NLS
748  for (String cell : row) {
749  String cellText = escapeText ? EscapeUtil.escapeHtml(cell) : cell;
750  builder.append("\t\t<td>").append(cellText).append("</td>\n"); //NON-NLS
751  }
752  builder.append("\t</tr>\n"); //NON-NLS
753  rowCount++;
754 
755  try {
756  out.write(builder.toString());
757  } catch (IOException ex) {
758  logger.log(Level.SEVERE, "Failed to write row to out.", ex); //NON-NLS
759  } catch (NullPointerException ex) {
760  logger.log(Level.SEVERE, "Output writer is null. Page was not initialized before writing.", ex); //NON-NLS
761  }
762  }
763 
771  public void addRowWithTaggedContentHyperlink(List<String> row, ContentTag contentTag) {
772  Content content = contentTag.getContent();
773  if (content instanceof AbstractFile == false) {
774  addRow(row, true);
775  return;
776  }
777  AbstractFile file = (AbstractFile) content;
778  // Add the hyperlink to the row. A column header for it was created in startTable().
779  StringBuilder localFileLink = new StringBuilder();
780  // Don't make a local copy of the file if it is a directory or unallocated space.
781  if (!(file.isDir()
782  || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS
783  || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS)) {
784  localFileLink.append("<a href=\""); //NON-NLS
785  // save it in a folder based on the tag name
786  String localFilePath = saveContent(file, contentTag.getName().getDisplayName());
787  localFileLink.append(localFilePath);
788  localFileLink.append("\" target=\"_top\">");
789  }
790 
791  StringBuilder builder = new StringBuilder();
792  builder.append("\t<tr>\n"); //NON-NLS
793  int positionCounter = 0;
794  for (String cell : row) {
795  // position-dependent code used to format this report. Not great, but understandable for formatting.
796  switch (positionCounter) {
797  case 1:
798  // Convert the file name to a hyperlink and left-align it
799  builder.append("\t\t<td class=\"left_align_cell\">").append(localFileLink.toString()).append(cell).append("</a></td>\n"); //NON-NLS
800  break;
801  case 7:
802  // Right-align the bytes column.
803  builder.append("\t\t<td class=\"right_align_cell\">").append(cell).append("</td>\n"); //NON-NLS
804  break;
805  default:
806  // Regular case, not a file name nor a byte count
807  builder.append("\t\t<td>").append(cell).append("</td>\n"); //NON-NLS
808  break;
809  }
810  ++positionCounter;
811  }
812  builder.append("\t</tr>\n"); //NON-NLS
813  rowCount++;
814 
815  try {
816  out.write(builder.toString());
817  } catch (IOException ex) {
818  logger.log(Level.SEVERE, "Failed to write row to out.", ex); //NON-NLS
819  } catch (NullPointerException ex) {
820  logger.log(Level.SEVERE, "Output writer is null. Page was not initialized before writing.", ex); //NON-NLS
821  }
822  }
823 
830  private List<ImageTagRegion> getTaggedRegions(List<ContentTag> contentTags) {
831  ArrayList<ImageTagRegion> tagRegions = new ArrayList<>();
832  contentTags.forEach((contentTag) -> {
833  try {
835  .getTag(contentTag, ImageTagRegion.class);
836  if (contentViewerTag != null) {
837  tagRegions.add(contentViewerTag.getDetails());
838  }
839  } catch (TskCoreException | NoCurrentCaseException ex) {
840  logger.log(Level.WARNING, "Could not get content viewer tag "
841  + "from case db for content_tag with id %d", contentTag.getId());
842  }
843  });
844  return tagRegions;
845  }
846 
852  public void addThumbnailRows(Set<Content> images) {
853  List<String> currentRow = new ArrayList<>();
854  int totalCount = 0;
855  int pages = 1;
856  for (Content content : images) {
857  if (currentRow.size() == THUMBNAIL_COLUMNS) {
858  addRow(currentRow, false);
859  currentRow.clear();
860  }
861 
862  if (totalCount == MAX_THUMBS_PER_PAGE) {
863  // manually set the row count so the count of items shown in the
864  // navigation page reflects the number of thumbnails instead of
865  // the number of rows.
866  rowCount = totalCount;
867  totalCount = 0;
868  pages++;
869  endTable();
870  endDataType();
871  startDataType(NbBundle.getMessage(this.getClass(), "ReportHTML.addThumbRows.dataType.title", pages),
872  NbBundle.getMessage(this.getClass(), "ReportHTML.addThumbRows.dataType.msg"));
873  List<String> emptyHeaders = new ArrayList<>();
874  for (int i = 0; i < THUMBNAIL_COLUMNS; i++) {
875  emptyHeaders.add("");
876  }
877  startTable(emptyHeaders);
878  }
879 
880  if (failsContentCheck(content)) {
881  continue;
882  }
883 
884  AbstractFile file = (AbstractFile) content;
885  List<ContentTag> contentTags = new ArrayList<>();
886 
887  String thumbnailPath = null;
888  String imageWithTagsFullPath = null;
889  try {
890  //Get content tags and all image tags
891  contentTags = Case.getCurrentCase().getServices()
893  List<ImageTagRegion> imageTags = getTaggedRegions(contentTags);
894 
895  if (!imageTags.isEmpty()) {
896  //Write the tags to the fullsize and thumbnail images
897  BufferedImage fullImageWithTags = ImageTagsUtil.getImageWithTags(file, imageTags);
898 
899  BufferedImage thumbnailWithTags = ImageTagsUtil.getThumbnailWithTags(file,
900  imageTags, ImageTagsUtil.IconSize.MEDIUM);
901 
902  String fileName = org.sleuthkit.autopsy.coreutils.FileUtil.escapeFileName(file.getName());
903 
904  //Create paths in report to write tagged images
905  File thumbnailImageWithTagsFile = Paths.get(thumbsPath, FilenameUtils.removeExtension(fileName) + ".png").toFile();
906  String fullImageWithTagsPath = makeCustomUniqueFilePath(file, "thumbs_fullsize");
907  fullImageWithTagsPath = FilenameUtils.removeExtension(fullImageWithTagsPath) + ".png";
908  File fullImageWithTagsFile = Paths.get(fullImageWithTagsPath).toFile();
909 
910  //Save images
911  ImageIO.write(thumbnailWithTags, "png", thumbnailImageWithTagsFile);
912  ImageIO.write(fullImageWithTags, "png", fullImageWithTagsFile);
913 
914  thumbnailPath = THUMBS_REL_PATH + thumbnailImageWithTagsFile.getName();
915  //Relative path
916  imageWithTagsFullPath = fullImageWithTagsPath.substring(subPath.length());
917  }
918  } catch (TskCoreException ex) {
919  logger.log(Level.WARNING, "Could not get tags for file.", ex); //NON-NLS
920  } catch (IOException | InterruptedException | ExecutionException ex) {
921  logger.log(Level.WARNING, "Could make marked up thumbnail.", ex); //NON-NLS
922  }
923 
924  // save copies of the orginal image and thumbnail image
925  if (thumbnailPath == null) {
926  thumbnailPath = prepareThumbnail(file);
927  }
928 
929  if (thumbnailPath == null) {
930  continue;
931  }
932  String contentPath = saveContent(file, "original"); //NON-NLS
933  String nameInImage;
934  try {
935  nameInImage = file.getUniquePath();
936  } catch (TskCoreException ex) {
937  nameInImage = file.getName();
938  }
939 
940  StringBuilder linkToThumbnail = new StringBuilder();
941  linkToThumbnail.append("<div id='thumbnail_link'><a href=\"")
942  .append((imageWithTagsFullPath != null) ? imageWithTagsFullPath : contentPath)
943  .append("\" target=\"_top\"><img src=\"")
944  .append(thumbnailPath).append("\" title=\"").append(nameInImage).append("\"/></a><br>") //NON-NLS
945  .append(file.getName()).append("<br>"); //NON-NLS
946  if (imageWithTagsFullPath != null) {
947  linkToThumbnail.append("<a href=\"").append(contentPath).append("\" target=\"_top\">View Original</a><br>");
948  }
949 
950  if (!contentTags.isEmpty()) {
951  linkToThumbnail.append(NbBundle.getMessage(this.getClass(), "ReportHTML.thumbLink.tags"));
952  }
953  for (int i = 0; i < contentTags.size(); i++) {
954  ContentTag tag = contentTags.get(i);
955  String notableString = tag.getName().getKnownStatus() == TskData.FileKnown.BAD ? TagsManager.getNotableTagLabel() : "";
956  linkToThumbnail.append(tag.getName().getDisplayName()).append(notableString);
957  if (i != contentTags.size() - 1) {
958  linkToThumbnail.append(", ");
959  }
960  }
961 
962  linkToThumbnail.append("</div>");
963  currentRow.add(linkToThumbnail.toString());
964 
965  totalCount++;
966  }
967 
968  if (currentRow.isEmpty() == false) {
969  int extraCells = THUMBNAIL_COLUMNS - currentRow.size();
970  for (int i = 0; i < extraCells; i++) {
971  // Finish out the row.
972  currentRow.add("");
973  }
974  addRow(currentRow, false);
975  }
976 
977  // manually set rowCount to be the total number of images.
978  rowCount = totalCount;
979  }
980 
981  private boolean failsContentCheck(Content c) {
982  if (c instanceof AbstractFile == false) {
983  return true;
984  }
985  AbstractFile file = (AbstractFile) c;
986  return file.isDir()
987  || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS
988  || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS;
989  }
990 
991  private String makeCustomUniqueFilePath(AbstractFile file, String dirName) {
992  // clean up the dir name passed in
993  String dirName2 = org.sleuthkit.autopsy.coreutils.FileUtil.escapeFileName(dirName);
994 
995  // Make a folder for the local file with the same tagName as the tag.
996  StringBuilder localFilePath = new StringBuilder(); // full path
997 
998  localFilePath.append(subPath);
999  localFilePath.append(dirName2);
1000  File localFileFolder = new File(localFilePath.toString());
1001  if (!localFileFolder.exists()) {
1002  localFileFolder.mkdirs();
1003  }
1004 
1005  /*
1006  * Construct a file tagName for the local file that incorporates the
1007  * file ID to ensure uniqueness.
1008  *
1009  * Note: File name is normalized to account for possible attribute name
1010  * which will be separated by a ':' character.
1011  */
1012  String fileName = org.sleuthkit.autopsy.coreutils.FileUtil.escapeFileName(file.getName());
1013  String objectIdSuffix = "_" + file.getId();
1014  int lastDotIndex = fileName.lastIndexOf(".");
1015  if (lastDotIndex != -1 && lastDotIndex != 0) {
1016  // The file tagName has a conventional extension. Insert the object id before the '.' of the extension.
1017  fileName = fileName.substring(0, lastDotIndex) + objectIdSuffix + fileName.substring(lastDotIndex, fileName.length());
1018  } else {
1019  // The file has no extension or the only '.' in the file is an initial '.', as in a hidden file.
1020  // Add the object id to the end of the file tagName.
1021  fileName += objectIdSuffix;
1022  }
1023  localFilePath.append(File.separator);
1024  localFilePath.append(fileName);
1025 
1026  return localFilePath.toString();
1027  }
1028 
1038  public String saveContent(AbstractFile file, String dirName) {
1039 
1040  String localFilePath = makeCustomUniqueFilePath(file, dirName);
1041 
1042  // If the local file doesn't already exist, create it now.
1043  // The existence check is necessary because it is possible to apply multiple tags with the same tagName to a file.
1044  File localFile = new File(localFilePath);
1045  if (!localFile.exists()) {
1046  ExtractFscContentVisitor.extract(file, localFile, null, null);
1047  }
1048 
1049  // get the relative path
1050  return localFilePath.substring(subPath.length());
1051  }
1052 
1060  @Override
1061  public String dateToString(long date) {
1062  SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
1063  return sdf.format(new java.util.Date(date * 1000));
1064  }
1065 
1066  @Override
1067  public String getRelativeFilePath() {
1068  return "report.html"; //NON-NLS
1069  }
1070 
1071  @Override
1072  public String getName() {
1073  return NbBundle.getMessage(this.getClass(), "ReportHTML.getName.text");
1074  }
1075 
1076  @Override
1077  public String getDescription() {
1078  return NbBundle.getMessage(this.getClass(), "ReportHTML.getDesc.text");
1079  }
1080 
1084  private void writeCss() {
1085  Writer cssOut = null;
1086  try {
1087  cssOut = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(subPath + "index.css"), "UTF-8")); //NON-NLS NON-NLS
1088  String css = "body {margin: 0px; padding: 0px; background: #FFFFFF; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353;}\n"
1089  + //NON-NLS
1090  "#content {padding: 30px;}\n"
1091  + //NON-NLS
1092  "#header {width:100%; padding: 10px; line-height: 25px; background: #07A; color: #FFF; font-size: 20px;}\n"
1093  + //NON-NLS
1094  "#pageHeaderFooter {width: 100%; padding: 10px; line-height: 25px; text-align: center; font-size: 20px;}\n"
1095  + //NON-NLS
1096  "h1 {font-size: 20px; font-weight: normal; color: #07A; padding: 0 0 7px 0; margin-top: 25px; border-bottom: 1px solid #D6D6D6;}\n"
1097  + //NON-NLS
1098  "h2 {font-size: 20px; font-weight: bolder; color: #07A;}\n"
1099  + //NON-NLS
1100  "h3 {font-size: 16px; color: #07A;}\n"
1101  + //NON-NLS
1102  "h4 {background: #07A; color: #FFF; font-size: 16px; margin: 0 0 0 25px; padding: 0; padding-left: 15px;}\n"
1103  + //NON-NLS
1104  "ul.nav {list-style-type: none; line-height: 35px; padding: 0px; margin-left: 15px;}\n"
1105  + //NON-NLS
1106  "ul li a {font-size: 14px; color: #444; text-decoration: none; padding-left: 25px;}\n"
1107  + //NON-NLS
1108  "ul li a:hover {text-decoration: underline;}\n"
1109  + //NON-NLS
1110  "p {margin: 0 0 20px 0;}\n"
1111  + //NON-NLS
1112  "table {white-space:nowrap; min-width: 700px; padding: 2; margin: 0; border-collapse: collapse; border-bottom: 2px solid #e5e5e5;}\n"
1113  + //NON-NLS
1114  ".keyword_list table {margin: 0 0 25px 25px; border-bottom: 2px solid #dedede;}\n"
1115  + //NON-NLS
1116  "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"
1117  + //NON-NLS
1118  "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"
1119  + //NON-NLS
1120  "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"
1121  + //NON-NLS
1122  "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"
1123  + //NON-NLS
1124  "table tr:nth-child(even) td {background: #f3f3f3;}\n"
1125  + //NON-NLS
1126  "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;}";
1127  cssOut.write(css);
1128  } catch (FileNotFoundException ex) {
1129  logger.log(Level.SEVERE, "Could not find index.css file to write to.", ex); //NON-NLS
1130  } catch (UnsupportedEncodingException ex) {
1131  logger.log(Level.SEVERE, "Did not recognize encoding when writing index.css.", ex); //NON-NLS
1132  } catch (IOException ex) {
1133  logger.log(Level.SEVERE, "Error creating Writer for index.css.", ex); //NON-NLS
1134  } finally {
1135  try {
1136  if (cssOut != null) {
1137  cssOut.flush();
1138  cssOut.close();
1139  }
1140  } catch (IOException ex) {
1141  }
1142  }
1143  }
1144 
1148  private void writeIndex() {
1149  Writer indexOut = null;
1150  String indexFilePath = path + "report.html"; //NON-NLS
1151  Case openCase;
1152  try {
1153  openCase = Case.getCurrentCaseThrows();
1154  } catch (NoCurrentCaseException ex) {
1155  logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS
1156  return;
1157  }
1158  try {
1159  indexOut = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(indexFilePath), "UTF-8")); //NON-NLS
1160  StringBuilder index = new StringBuilder();
1161  final String reportTitle = reportBranding.getReportTitle();
1162  String iconPath = reportBranding.getAgencyLogoPath();
1163  if (iconPath == null) {
1164  // use default Autopsy icon if custom icon is not set
1165  iconPath = HTML_SUBDIR + "favicon.ico";
1166  } else {
1167  iconPath = Paths.get(reportBranding.getAgencyLogoPath()).getFileName().toString(); //ref to writeNav() for agency_logo
1168  }
1169  index.append("<head>\n<title>").append(reportTitle).append(" ").append(
1170  NbBundle.getMessage(this.getClass(), "ReportHTML.writeIndex.title", currentCase.getDisplayName())).append(
1171  "</title>\n"); //NON-NLS
1172  index.append("<link rel=\"icon\" type=\"image/ico\" href=\"")
1173  .append(iconPath).append("\" />\n"); //NON-NLS
1174  index.append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"); //NON-NLS
1175  index.append("</head>\n"); //NON-NLS
1176  index.append("<frameset cols=\"350px,*\">\n"); //NON-NLS
1177  index.append("<frame src=\"" + HTML_SUBDIR).append(File.separator).append("nav.html\" name=\"nav\">\n"); //NON-NLS
1178  index.append("<frame src=\"" + HTML_SUBDIR).append(File.separator).append("summary.html\" name=\"content\">\n"); //NON-NLS
1179  index.append("<noframes>").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeIndex.noFrames.msg")).append("<br />\n"); //NON-NLS
1180  index.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeIndex.noFrames.seeNav")).append("<br />\n"); //NON-NLS
1181  index.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeIndex.seeSum")).append("</noframes>\n"); //NON-NLS
1182  index.append("</frameset>\n"); //NON-NLS
1183  index.append("</html>"); //NON-NLS
1184  indexOut.write(index.toString());
1185  openCase.addReport(indexFilePath, NbBundle.getMessage(this.getClass(),
1186  "ReportHTML.writeIndex.srcModuleName.text"), "");
1187  } catch (IOException ex) {
1188  logger.log(Level.SEVERE, "Error creating Writer for report.html: {0}", ex); //NON-NLS
1189  } catch (TskCoreException ex) {
1190  String errorMessage = String.format("Error adding %s to case as a report", indexFilePath); //NON-NLS
1191  logger.log(Level.SEVERE, errorMessage, ex);
1192  } finally {
1193  try {
1194  if (indexOut != null) {
1195  indexOut.flush();
1196  indexOut.close();
1197  }
1198  } catch (IOException ex) {
1199  }
1200  }
1201  }
1202 
1206  private void writeNav() {
1207  Writer navOut = null;
1208  try {
1209  navOut = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(subPath + "nav.html"), "UTF-8")); //NON-NLS
1210  StringBuilder nav = new StringBuilder();
1211  nav.append("<html>\n<head>\n\t<title>").append( //NON-NLS
1212  NbBundle.getMessage(this.getClass(), "ReportHTML.writeNav.title"))
1213  .append("</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"index.css\" />\n"); //NON-NLS
1214  nav.append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n</head>\n<body>\n"); //NON-NLS
1215  nav.append("<div id=\"content\">\n<h1>").append( //NON-NLS
1216  NbBundle.getMessage(this.getClass(), "ReportHTML.writeNav.h1")).append("</h1>\n"); //NON-NLS
1217  nav.append("<ul class=\"nav\">\n"); //NON-NLS
1218  nav.append("<li style=\"background: url(summary.png) left center no-repeat;\"><a href=\"summary.html\" target=\"content\">") //NON-NLS
1219  .append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeNav.summary")).append("</a></li>\n"); //NON-NLS
1220 
1221  for (String dataType : dataTypes.keySet()) {
1222  String dataTypeEsc = dataTypeToFileName(dataType);
1223  String iconFileName = useDataTypeIcon(dataType);
1224  nav.append("<li style=\"background: url('").append(iconFileName) //NON-NLS
1225  .append("') left center no-repeat;\"><a href=\"") //NON-NLS
1226  .append(dataTypeEsc).append(".html\" target=\"content\">") //NON-NLS
1227  .append(dataType).append(" (").append(dataTypes.get(dataType))
1228  .append(")</a></li>\n"); //NON-NLS
1229  }
1230  nav.append("</ul>\n"); //NON-NLS
1231  nav.append("</div>\n</body>\n</html>"); //NON-NLS
1232  navOut.write(nav.toString());
1233  } catch (IOException ex) {
1234  logger.log(Level.SEVERE, "Failed to write end of report navigation menu: {0}", ex); //NON-NLS
1235  } finally {
1236  if (navOut != null) {
1237  try {
1238  navOut.flush();
1239  navOut.close();
1240  } catch (IOException ex) {
1241  logger.log(Level.WARNING, "Could not close navigation out writer."); //NON-NLS
1242  }
1243  }
1244  }
1245 
1246  InputStream in = null;
1247  OutputStream output = null;
1248  try {
1249 
1250  //pull generator and agency logo from branding, and the remaining resources from the core jar
1251  String generatorLogoPath = reportBranding.getGeneratorLogoPath();
1252  if (generatorLogoPath != null && !generatorLogoPath.isEmpty()) {
1253  File from = new File(generatorLogoPath);
1254  File to = new File(subPath);
1255  FileUtil.copyFile(FileUtil.toFileObject(from), FileUtil.toFileObject(to), "generator_logo"); //NON-NLS
1256  }
1257 
1258  String agencyLogoPath = reportBranding.getAgencyLogoPath();
1259  if (agencyLogoPath != null && !agencyLogoPath.isEmpty()) {
1260  Path destinationPath = Paths.get(subPath);
1261  Files.copy(Files.newInputStream(Paths.get(agencyLogoPath)), destinationPath.resolve(Paths.get(agencyLogoPath).getFileName())); //NON-NLS
1262  }
1263 
1264  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/favicon.ico"); //NON-NLS
1265  output = new FileOutputStream(new File(subPath + "favicon.ico"));
1266  FileUtil.copy(in, output);
1267  in.close();
1268  output.close();
1269 
1270  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/summary.png"); //NON-NLS
1271  output = new FileOutputStream(new File(subPath + "summary.png"));
1272  FileUtil.copy(in, output);
1273  in.close();
1274  output.close();
1275 
1276  } catch (IOException ex) {
1277  logger.log(Level.SEVERE, "Failed to extract images for HTML report.", ex); //NON-NLS
1278  } finally {
1279  if (output != null) {
1280  try {
1281  output.flush();
1282  output.close();
1283  } catch (IOException ex) {
1284  }
1285  }
1286  if (in != null) {
1287  try {
1288  in.close();
1289  } catch (IOException ex) {
1290  }
1291  }
1292  }
1293  }
1294 
1298  private void writeSummary() {
1299  Writer output = null;
1300  try {
1301  output = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(subPath + "summary.html"), "UTF-8")); //NON-NLS
1302  StringBuilder head = new StringBuilder();
1303  head.append("<html>\n<head>\n<title>").append( //NON-NLS
1304  NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.title")).append("</title>\n"); //NON-NLS
1305  head.append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"); //NON-NLS
1306  head.append("<style type=\"text/css\">\n"); //NON-NLS
1307  head.append("#pageHeaderFooter {width: 100%; padding: 10px; line-height: 25px; text-align: center; font-size: 20px;}\n"); //NON-NLS
1308  head.append("body { padding: 0px; margin: 0px; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353; }\n"); //NON-NLS
1309  head.append("#wrapper { width: 90%; margin: 0px auto; margin-top: 35px; }\n"); //NON-NLS
1310  head.append("h1 { color: #07A; font-size: 36px; line-height: 42px; font-weight: normal; margin: 0px; border-bottom: 1px solid #81B9DB; }\n"); //NON-NLS
1311  head.append("h1 span { color: #F00; display: block; font-size: 16px; font-weight: bold; line-height: 22px;}\n"); //NON-NLS
1312  head.append("h2 { padding: 0 0 3px 0; margin: 0px; color: #07A; font-weight: normal; border-bottom: 1px dotted #81B9DB; }\n"); //NON-NLS
1313  head.append("h3 { padding: 5 0 3px 0; margin: 0px; color: #07A; font-weight: normal; }\n");
1314  head.append("table td { padding: 5px 25px 5px 0px; vertical-align:top;}\n"); //NON-NLS
1315  head.append("p.subheadding { padding: 0px; margin: 0px; font-size: 11px; color: #B5B5B5; }\n"); //NON-NLS
1316  head.append(".title { width: 660px; margin-bottom: 50px; }\n"); //NON-NLS
1317  head.append(".left { float: left; width: 250px; margin-top: 20px; text-align: center; }\n"); //NON-NLS
1318  head.append(".left img { max-width: 250px; max-height: 250px; min-width: 200px; min-height: 200px; }\n"); //NON-NLS
1319  head.append(".right { float: right; width: 385px; margin-top: 25px; font-size: 14px; }\n"); //NON-NLS
1320  head.append(".clear { clear: both; }\n"); //NON-NLS
1321  head.append(".info { padding: 10px 0;}\n");
1322  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
1323  head.append(".info table { margin: 10px 25px 10px 25px; }\n"); //NON-NLS
1324  head.append("ul {padding: 0;margin: 0;list-style-type: none;}");
1325  head.append("li {padding-bottom: 5px;}");
1326  head.append("</style>\n"); //NON-NLS
1327  head.append("</head>\n<body>\n"); //NON-NLS
1328  output.write(head.toString());
1329 
1330  DateFormat datetimeFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
1331  Date date = new Date();
1332  String datetime = datetimeFormat.format(date);
1333 
1334  StringBuilder summary = new StringBuilder();
1335  boolean running = false;
1337  running = true;
1338  }
1339  SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
1340  List<IngestJobInfo> ingestJobs = skCase.getIngestJobs();
1341  final String reportTitle = reportBranding.getReportTitle();
1342  final String reportFooter = reportBranding.getReportFooter();
1343  final boolean generatorLogoSet = reportBranding.getGeneratorLogoPath() != null && !reportBranding.getGeneratorLogoPath().isEmpty();
1344 
1345  summary.append("<div id=\"wrapper\">\n"); //NON-NLS
1346  summary.append(writePageHeader());
1347  summary.append("<h1>").append(reportTitle) //NON-NLS
1348  .append(running ? NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.warningMsg") : "")
1349  .append("</h1>\n"); //NON-NLS
1350  summary.append("<p class=\"subheadding\">").append( //NON-NLS
1351  NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.reportGenOn.text", datetime)).append("</p>\n"); //NON-NLS
1352  summary.append("<div class=\"title\">\n"); //NON-NLS
1353  summary.append(writeSummaryCaseDetails());
1354  summary.append(writeSummaryImageInfo());
1355  summary.append(writeSummarySoftwareInfo(skCase, ingestJobs));
1356  summary.append(writeSummaryIngestHistoryInfo(skCase, ingestJobs));
1357  if (generatorLogoSet) {
1358  summary.append("<div class=\"left\">\n"); //NON-NLS
1359  summary.append("<img src=\"generator_logo.png\" />\n"); //NON-NLS
1360  summary.append("</div>\n"); //NON-NLS
1361  }
1362  summary.append("<div class=\"clear\"></div>\n"); //NON-NLS
1363  if (reportFooter != null) {
1364  summary.append("<p class=\"subheadding\">").append(reportFooter).append("</p>\n"); //NON-NLS
1365  }
1366  summary.append("</div>\n"); //NON-NLS
1367  summary.append(writePageFooter());
1368  summary.append("</body></html>"); //NON-NLS
1369  output.write(summary.toString());
1370  } catch (FileNotFoundException ex) {
1371  logger.log(Level.SEVERE, "Could not find summary.html file to write to."); //NON-NLS
1372  } catch (UnsupportedEncodingException ex) {
1373  logger.log(Level.SEVERE, "Did not recognize encoding when writing summary.hmtl."); //NON-NLS
1374  } catch (IOException ex) {
1375  logger.log(Level.SEVERE, "Error creating Writer for summary.html."); //NON-NLS
1376  } catch (NoCurrentCaseException | TskCoreException ex) {
1377  logger.log(Level.WARNING, "Unable to get current sleuthkit Case for the HTML report.");
1378  } finally {
1379  try {
1380  if (output != null) {
1381  output.flush();
1382  output.close();
1383  }
1384  } catch (IOException ex) {
1385  }
1386  }
1387  }
1388 
1389  @Messages({
1390  "ReportHTML.writeSum.case=Case:",
1391  "ReportHTML.writeSum.caseNumber=Case Number:",
1392  "ReportHTML.writeSum.caseNumImages=Number of data sources in case:",
1393  "ReportHTML.writeSum.caseNotes=Notes:",
1394  "ReportHTML.writeSum.examiner=Examiner:"
1395  })
1401  private StringBuilder writeSummaryCaseDetails() {
1402  StringBuilder summary = new StringBuilder();
1403 
1404  final boolean agencyLogoSet = reportBranding.getAgencyLogoPath() != null && !reportBranding.getAgencyLogoPath().isEmpty();
1405 
1406  // Case
1407  String caseName = currentCase.getDisplayName();
1408  String caseNumber = currentCase.getNumber();
1409  int imagecount;
1410  try {
1411  imagecount = currentCase.getDataSources().size();
1412  } catch (TskCoreException ex) {
1413  imagecount = 0;
1414  }
1415  String caseNotes = currentCase.getCaseNotes();
1416 
1417  // Examiner
1418  String examinerName = currentCase.getExaminer();
1419 
1420  // Start the layout.
1421  summary.append("<div class=\"title\">\n"); //NON-NLS
1422  if (agencyLogoSet) {
1423  summary.append("<div class=\"left\">\n"); //NON-NLS
1424  summary.append("<img src=\"");
1425  summary.append(Paths.get(reportBranding.getAgencyLogoPath()).getFileName().toString());
1426  summary.append("\" />\n"); //NON-NLS
1427  summary.append("</div>\n"); //NON-NLS
1428  }
1429  final String align = agencyLogoSet ? "right" : "left"; //NON-NLS NON-NLS
1430  summary.append("<div class=\"").append(align).append("\">\n"); //NON-NLS
1431  summary.append("<table>\n"); //NON-NLS
1432 
1433  // Case details
1434  summary.append("<tr><td>").append(Bundle.ReportHTML_writeSum_case()).append("</td><td>") //NON-NLS
1435  .append(formatHtmlString(caseName)).append("</td></tr>\n"); //NON-NLS
1436 
1437  if (!caseNumber.isEmpty()) {
1438  summary.append("<tr><td>").append(Bundle.ReportHTML_writeSum_caseNumber()).append("</td><td>") //NON-NLS
1439  .append(formatHtmlString(caseNumber)).append("</td></tr>\n"); //NON-NLS
1440  }
1441 
1442  summary.append("<tr><td>").append(Bundle.ReportHTML_writeSum_caseNumImages()).append("</td><td>") //NON-NLS
1443  .append(imagecount).append("</td></tr>\n"); //NON-NLS
1444 
1445  if (!caseNotes.isEmpty()) {
1446  summary.append("<tr><td>").append(Bundle.ReportHTML_writeSum_caseNotes()).append("</td><td>") //NON-NLS
1447  .append(formatHtmlString(caseNotes)).append("</td></tr>\n"); //NON-NLS
1448  }
1449 
1450  // Examiner details
1451  if (!examinerName.isEmpty()) {
1452  summary.append("<tr><td>").append(Bundle.ReportHTML_writeSum_examiner()).append("</td><td>") //NON-NLS
1453  .append(formatHtmlString(examinerName)).append("</td></tr>\n"); //NON-NLS
1454  }
1455 
1456  // End the layout.
1457  summary.append("</table>\n"); //NON-NLS
1458  summary.append("</div>\n"); //NON-NLS
1459  summary.append("<div class=\"clear\"></div>\n"); //NON-NLS
1460  summary.append("</div>\n"); //NON-NLS
1461  return summary;
1462  }
1463 
1469  private StringBuilder writeSummaryImageInfo() {
1470  StringBuilder summary = new StringBuilder();
1471  summary.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.imageInfoHeading"));
1472  summary.append("<div class=\"info\">\n"); //NON-NLS
1473  try {
1474  for (Content c : currentCase.getDataSources()) {
1475  summary.append("<p>").append(c.getName()).append("</p>\n"); //NON-NLS
1476  if (c instanceof Image) {
1477  Image img = (Image) c;
1478 
1479  summary.append("<table>\n"); //NON-NLS
1480  summary.append("<tr><td>").append( //NON-NLS
1481  NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.timezone"))
1482  .append("</td><td>").append(img.getTimeZone()).append("</td></tr>\n"); //NON-NLS
1483  for (String imgPath : img.getPaths()) {
1484  summary.append("<tr><td>").append( //NON-NLS
1485  NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.path"))
1486  .append("</td><td>").append(imgPath).append("</td></tr>\n"); //NON-NLS
1487  }
1488  summary.append("</table>\n"); //NON-NLS
1489  }
1490  }
1491  } catch (TskCoreException ex) {
1492  logger.log(Level.WARNING, "Unable to get image information for the HTML report."); //NON-NLS
1493  }
1494  summary.append("</div>\n"); //NON-NLS
1495  return summary;
1496  }
1497 
1503  private StringBuilder writeSummarySoftwareInfo(SleuthkitCase skCase, List<IngestJobInfo> ingestJobs) {
1504  StringBuilder summary = new StringBuilder();
1505  summary.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.softwareInfoHeading"));
1506  summary.append("<div class=\"info\">\n");
1507  summary.append("<table>\n");
1508  summary.append("<tr><td>").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.autopsyVersion"))
1509  .append("</td><td>").append(Version.getVersion()).append("</td></tr>\n");
1510  Map<Long, IngestModuleInfo> moduleInfoHashMap = new HashMap<>();
1511  for (IngestJobInfo ingestJob : ingestJobs) {
1512  List<IngestModuleInfo> ingestModules = ingestJob.getIngestModuleInfo();
1513  for (IngestModuleInfo ingestModule : ingestModules) {
1514  if (!moduleInfoHashMap.containsKey(ingestModule.getIngestModuleId())) {
1515  moduleInfoHashMap.put(ingestModule.getIngestModuleId(), ingestModule);
1516  }
1517  }
1518  }
1519  TreeMap<String, String> modules = new TreeMap<>();
1520  for (IngestModuleInfo moduleinfo : moduleInfoHashMap.values()) {
1521  modules.put(moduleinfo.getDisplayName(), moduleinfo.getVersion());
1522  }
1523  for (Map.Entry<String, String> module : modules.entrySet()) {
1524  summary.append("<tr><td>").append(module.getKey()).append(" Module:")
1525  .append("</td><td>").append(module.getValue()).append("</td></tr>\n");
1526  }
1527  summary.append("</table>\n");
1528  summary.append("</div>\n");
1529  summary.append("<div class=\"clear\"></div>\n"); //NON-NLS
1530  return summary;
1531  }
1532 
1538  private StringBuilder writeSummaryIngestHistoryInfo(SleuthkitCase skCase, List<IngestJobInfo> ingestJobs) {
1539  StringBuilder summary = new StringBuilder();
1540  try {
1541  summary.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.ingestHistoryHeading"));
1542  summary.append("<div class=\"info\">\n");
1543  int jobnumber = 1;
1544 
1545  for (IngestJobInfo ingestJob : ingestJobs) {
1546  summary.append("<h3>Job ").append(jobnumber).append(":</h3>\n");
1547  summary.append("<table>\n");
1548  summary.append("<tr><td>").append("Data Source:")
1549  .append("</td><td>").append(skCase.getContentById(ingestJob.getObjectId()).getName()).append("</td></tr>\n");
1550  summary.append("<tr><td>").append("Status:")
1551  .append("</td><td>").append(ingestJob.getStatus()).append("</td></tr>\n");
1552  summary.append("<tr><td>").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.modulesEnabledHeading"))
1553  .append("</td><td>");
1554  List<IngestModuleInfo> ingestModules = ingestJob.getIngestModuleInfo();
1555  summary.append("<ul>\n");
1556  for (IngestModuleInfo ingestModule : ingestModules) {
1557  summary.append("<li>").append(ingestModule.getDisplayName()).append("</li>");
1558  }
1559  summary.append("</ul>\n");
1560  jobnumber++;
1561  summary.append("</td></tr>\n");
1562  summary.append("</table>\n");
1563  }
1564  summary.append("</div>\n");
1565  } catch (TskCoreException ex) {
1566  logger.log(Level.WARNING, "Unable to get ingest jobs for the HTML report.");
1567  }
1568  return summary;
1569  }
1570 
1579  private String prepareThumbnail(AbstractFile file) {
1580  BufferedImage bufferedThumb = ImageUtils.getThumbnail(file, ImageUtils.ICON_SIZE_MEDIUM);
1581 
1582  /*
1583  * File name is normalized to account for possible attribute name which
1584  * will be separated by a ':' character.
1585  */
1586  String fileName = org.sleuthkit.autopsy.coreutils.FileUtil.escapeFileName(file.getName());
1587 
1588  File thumbFile = Paths.get(thumbsPath, fileName + ".png").toFile();
1589  if (bufferedThumb == null) {
1590  return null;
1591  }
1592  try {
1593  ImageIO.write(bufferedThumb, "png", thumbFile);
1594  } catch (IOException ex) {
1595  logger.log(Level.WARNING, "Failed to write thumb file to report directory.", ex); //NON-NLS
1596  return null;
1597  }
1598  if (thumbFile.exists()
1599  == false) {
1600  return null;
1601  }
1602  return THUMBS_REL_PATH
1603  + thumbFile.getName();
1604  }
1605 
1614  private String formatHtmlString(String text) {
1615  String formattedString = StringEscapeUtils.escapeHtml4(text);
1616  return formattedString.replaceAll("(\r\n|\r|\n|\n\r)", "<br>");
1617  }
1618 
1619 }
List< Content > getDataSources()
Definition: Case.java:1703
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:1926
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:3013
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-2021 Basis Technology. Generated on: Thu Sep 30 2021
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.