Autopsy  4.16.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.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  private String useDataTypeIcon(String dataType) {
217  String iconFilePath;
218  String iconFileName;
219  InputStream in;
220  OutputStream output = null;
221 
222  logger.log(Level.INFO, "useDataTypeIcon: dataType = {0}", dataType); //NON-NLS
223 
224  // find the artifact with matching display name
225  BlackboardArtifact.ARTIFACT_TYPE artifactType = null;
226  for (ARTIFACT_TYPE v : ARTIFACT_TYPE.values()) {
227  if (v.getDisplayName().equals(dataType)) {
228  artifactType = v;
229  }
230  }
231 
232  if (null != artifactType) {
233  // set the icon file name
234  iconFileName = dataTypeToFileName(artifactType.getDisplayName()) + ".png"; //NON-NLS
235  iconFilePath = subPath + File.separator + iconFileName;
236 
237  // determine the source image to use
238  switch (artifactType) {
239  case TSK_WEB_BOOKMARK:
240  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/bookmarks.png"); //NON-NLS
241  break;
242  case TSK_WEB_COOKIE:
243  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/cookies.png"); //NON-NLS
244  break;
245  case TSK_WEB_HISTORY:
246  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/history.png"); //NON-NLS
247  break;
248  case TSK_WEB_DOWNLOAD:
249  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/downloads.png"); //NON-NLS
250  break;
251  case TSK_RECENT_OBJECT:
252  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/recent.png"); //NON-NLS
253  break;
254  case TSK_INSTALLED_PROG:
255  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/installed.png"); //NON-NLS
256  break;
257  case TSK_KEYWORD_HIT:
258  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/keywords.png"); //NON-NLS
259  break;
260  case TSK_HASHSET_HIT:
261  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/hash.png"); //NON-NLS
262  break;
263  case TSK_DEVICE_ATTACHED:
264  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/devices.png"); //NON-NLS
265  break;
266  case TSK_WEB_SEARCH_QUERY:
267  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/search.png"); //NON-NLS
268  break;
269  case TSK_METADATA_EXIF:
270  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/exif.png"); //NON-NLS
271  break;
272  case TSK_TAG_FILE:
273  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/userbookmarks.png"); //NON-NLS
274  break;
275  case TSK_TAG_ARTIFACT:
276  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/userbookmarks.png"); //NON-NLS
277  break;
278  case TSK_SERVICE_ACCOUNT:
279  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/account-icon-16.png"); //NON-NLS
280  break;
281  case TSK_CONTACT:
282  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/contact.png"); //NON-NLS
283  break;
284  case TSK_MESSAGE:
285  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/message.png"); //NON-NLS
286  break;
287  case TSK_CALLLOG:
288  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/calllog.png"); //NON-NLS
289  break;
290  case TSK_CALENDAR_ENTRY:
291  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/calendar.png"); //NON-NLS
292  break;
293  case TSK_SPEED_DIAL_ENTRY:
294  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/speeddialentry.png"); //NON-NLS
295  break;
296  case TSK_BLUETOOTH_PAIRING:
297  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/bluetooth.png"); //NON-NLS
298  break;
299  case TSK_GPS_BOOKMARK:
300  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/gpsfav.png"); //NON-NLS
301  break;
302  case TSK_GPS_LAST_KNOWN_LOCATION:
303  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/gps-lastlocation.png"); //NON-NLS
304  break;
305  case TSK_GPS_SEARCH:
306  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/gps-search.png"); //NON-NLS
307  break;
308  case TSK_OS_INFO:
309  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/computer.png"); //NON-NLS
310  break;
311  case TSK_GPS_TRACKPOINT:
312  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/gps_trackpoint.png"); //NON-NLS
313  break;
314  case TSK_GPS_ROUTE:
315  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/gps_trackpoint.png"); //NON-NLS
316  break;
317  case TSK_EMAIL_MSG:
318  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/mail-icon-16.png"); //NON-NLS
319  break;
320  case TSK_ENCRYPTION_SUSPECTED:
321  case TSK_ENCRYPTION_DETECTED:
322  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/encrypted-file.png"); //NON-NLS
323  break;
324  case TSK_EXT_MISMATCH_DETECTED:
325  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/mismatch-16.png"); //NON-NLS
326  break;
327  case TSK_INTERESTING_ARTIFACT_HIT:
328  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/interesting_item.png"); //NON-NLS
329  break;
330  case TSK_INTERESTING_FILE_HIT:
331  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/interesting_item.png"); //NON-NLS
332  break;
333  case TSK_PROG_RUN:
334  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/installed.png"); //NON-NLS
335  break;
336  case TSK_REMOTE_DRIVE:
337  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/drive_network.png"); //NON-NLS
338  break;
339  case TSK_OS_ACCOUNT:
340  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/os-account.png"); //NON-NLS
341  break;
342  case TSK_OBJECT_DETECTED:
343  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/objects.png"); //NON-NLS
344  break;
345  case TSK_WEB_FORM_AUTOFILL:
346  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/web-form.png"); //NON-NLS
347  break;
348  case TSK_WEB_CACHE:
349  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/cache.png"); //NON-NLS
350  break;
351  case TSK_USER_CONTENT_SUSPECTED:
352  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/user-content.png"); //NON-NLS
353  break;
354  case TSK_METADATA:
355  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/metadata.png"); //NON-NLS
356  break;
357  case TSK_CLIPBOARD_CONTENT:
358  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/clipboard.png"); //NON-NLS
359  break;
360  case TSK_ACCOUNT:
361  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/accounts.png"); //NON-NLS
362  break;
363  case TSK_WIFI_NETWORK:
364  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/network-wifi.png"); //NON-NLS
365  break;
366  case TSK_WIFI_NETWORK_ADAPTER:
367  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/network-wifi.png"); //NON-NLS
368  break;
369  case TSK_SIM_ATTACHED:
370  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/sim_card.png"); //NON-NLS
371  break;
372  case TSK_BLUETOOTH_ADAPTER:
373  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/Bluetooth.png"); //NON-NLS
374  break;
375  case TSK_DEVICE_INFO:
376  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/devices.png"); //NON-NLS
377  break;
378  case TSK_VERIFICATION_FAILED:
379  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/validationFailed.png"); //NON-NLS
380  break;
381  case TSK_WEB_ACCOUNT_TYPE:
382  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/web-account-type.png.png"); //NON-NLS
383  break;
384  default:
385  logger.log(Level.WARNING, "useDataTypeIcon: unhandled artifact type = {0}", dataType); //NON-NLS
386  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/star.png"); //NON-NLS
387  iconFileName = "star.png"; //NON-NLS
388  iconFilePath = subPath + File.separator + iconFileName;
389  break;
390  }
391  } else if (dataType.startsWith(ARTIFACT_TYPE.TSK_ACCOUNT.getDisplayName())) {
392  /*
393  * TSK_ACCOUNT artifacts get separated by their TSK_ACCOUNT_TYPE
394  * attribute, with a synthetic compound dataType name, so they are
395  * not caught by the switch statement above. For now we just give
396  * them all the general account icon, but we could do something else
397  * in the future.
398  */
399  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/accounts.png"); //NON-NLS
400  iconFileName = "accounts.png"; //NON-NLS
401  iconFilePath = subPath + File.separator + iconFileName;
402  } else { // no defined artifact found for this dataType
403  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/star.png"); //NON-NLS
404  iconFileName = "star.png"; //NON-NLS
405  iconFilePath = subPath + File.separator + iconFileName;
406  }
407 
408  try {
409  output = new FileOutputStream(iconFilePath);
410  FileUtil.copy(in, output);
411  in.close();
412  output.close();
413  } catch (IOException ex) {
414  logger.log(Level.SEVERE, "Failed to extract images for HTML report.", ex); //NON-NLS
415  } finally {
416  if (output != null) {
417  try {
418  output.flush();
419  output.close();
420  } catch (IOException ex) {
421  }
422  }
423  if (in != null) {
424  try {
425  in.close();
426  } catch (IOException ex) {
427  }
428  }
429  }
430 
431  return iconFileName;
432  }
433 
440  @Override
441  public void startReport(String baseReportDir) {
442 
443  // Refresh the HTML report
444  try {
445  refresh();
446  } catch (NoCurrentCaseException ex) {
447  logger.log(Level.SEVERE, "Exception while getting open case."); //NON-NLS
448  return;
449  }
450  // Setup the path for the HTML report
451  this.path = baseReportDir; //NON-NLS
452  this.subPath = this.path + HTML_SUBDIR + File.separator;
453  this.thumbsPath = this.subPath + THUMBS_REL_PATH; //NON-NLS
454  try {
455  FileUtil.createFolder(new File(this.subPath));
456  FileUtil.createFolder(new File(this.thumbsPath));
457  } catch (IOException ex) {
458  logger.log(Level.SEVERE, "Unable to make HTML report folder."); //NON-NLS
459  }
460  // Write the basic files
461  writeCss();
462  writeIndex();
463  writeSummary();
464  }
465 
470  @Override
471  public void endReport() {
472  writeNav();
473  if (out != null) {
474  try {
475  out.close();
476  } catch (IOException ex) {
477  logger.log(Level.WARNING, "Could not close the output writer when ending report.", ex); //NON-NLS
478  }
479  }
480  }
481 
490  @Override
491  public void startDataType(String name, String description) {
492  String title = dataTypeToFileName(name);
493  try {
494  out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(subPath + title + ".html"), "UTF-8")); //NON-NLS
495  } catch (FileNotFoundException ex) {
496  logger.log(Level.SEVERE, "File not found: {0}", ex); //NON-NLS
497  } catch (UnsupportedEncodingException ex) {
498  logger.log(Level.SEVERE, "Unrecognized encoding"); //NON-NLS
499  }
500 
501  try {
502  StringBuilder page = new StringBuilder();
503  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
504  .append(writePageHeader())
505  .append("<div id=\"header\">").append(name).append("</div>\n")
506  .append("<div id=\"content\">\n"); //NON-NLS
507  if (!description.isEmpty()) {
508  page.append("<p><strong>"); //NON-NLS
509  page.append(description);
510  page.append("</strong></p>\n"); //NON-NLS
511  }
512  out.write(page.toString());
513  currentDataType = name;
514  rowCount = 0;
515  } catch (IOException ex) {
516  logger.log(Level.SEVERE, "Failed to write page head: {0}", ex); //NON-NLS
517  }
518  }
519 
524  @Override
525  public void endDataType() {
526  dataTypes.put(currentDataType, rowCount);
527  try {
528  StringBuilder builder = new StringBuilder();
529  builder.append(writePageFooter());
530  builder.append("</div>\n</body>\n</html>\n"); //NON-NLS
531  out.write(builder.toString());
532  } catch (IOException ex) {
533  logger.log(Level.SEVERE, "Failed to write end of HTML report.", ex); //NON-NLS
534  } finally {
535  if (out != null) {
536  try {
537  out.flush();
538  out.close();
539  } catch (IOException ex) {
540  logger.log(Level.WARNING, "Could not close the output writer when ending data type.", ex); //NON-NLS
541  }
542  out = null;
543  }
544  }
545  }
546 
553  private String writePageHeader() {
554  StringBuilder output = new StringBuilder();
555  String pageHeader = configPanel.getHeader();
556  if (pageHeader.isEmpty() == false) {
557  output.append("<div id=\"pageHeaderFooter\">")
558  .append(StringEscapeUtils.escapeHtml4(pageHeader))
559  .append("</div>\n"); //NON-NLS
560  }
561  return output.toString();
562  }
563 
570  private String writePageFooter() {
571  StringBuilder output = new StringBuilder();
572  String pageFooter = configPanel.getFooter();
573  if (pageFooter.isEmpty() == false) {
574  output.append("<br/><div id=\"pageHeaderFooter\">")
575  .append(StringEscapeUtils.escapeHtml4(pageFooter))
576  .append("</div>"); //NON-NLS
577  }
578  return output.toString();
579  }
580 
586  @Override
587  public void startSet(String setName) {
588  StringBuilder set = new StringBuilder();
589  set.append("<h1><a name=\"").append(setName).append("\">").append(setName).append("</a></h1>\n"); //NON-NLS
590  set.append("<div class=\"keyword_list\">\n"); //NON-NLS
591 
592  try {
593  out.write(set.toString());
594  } catch (IOException ex) {
595  logger.log(Level.SEVERE, "Failed to write set: {0}", ex); //NON-NLS
596  }
597  }
598 
602  @Override
603  public void endSet() {
604  try {
605  out.write("</div>\n"); //NON-NLS
606  } catch (IOException ex) {
607  logger.log(Level.SEVERE, "Failed to write end of set: {0}", ex); //NON-NLS
608  }
609  }
610 
616  @Override
617  public void addSetIndex(List<String> sets) {
618  StringBuilder index = new StringBuilder();
619  index.append("<ul>\n"); //NON-NLS
620  for (String set : sets) {
621  index.append("\t<li><a href=\"#").append(set).append("\">").append(set).append("</a></li>\n"); //NON-NLS
622  }
623  index.append("</ul>\n"); //NON-NLS
624  try {
625  out.write(index.toString());
626  } catch (IOException ex) {
627  logger.log(Level.SEVERE, "Failed to add set index: {0}", ex); //NON-NLS
628  }
629  }
630 
636  @Override
637  public void addSetElement(String elementName) {
638  try {
639  out.write("<h4>" + elementName + "</h4>\n"); //NON-NLS
640  } catch (IOException ex) {
641  logger.log(Level.SEVERE, "Failed to write set element: {0}", ex); //NON-NLS
642  }
643  }
644 
650  @Override
651  public void startTable(List<String> titles) {
652  StringBuilder ele = new StringBuilder();
653  ele.append("<table>\n<thead>\n\t<tr>\n"); //NON-NLS
654  for (String title : titles) {
655  ele.append("\t\t<th>").append(title).append("</th>\n"); //NON-NLS
656  }
657  ele.append("\t</tr>\n</thead>\n"); //NON-NLS
658 
659  try {
660  out.write(ele.toString());
661  } catch (IOException ex) {
662  logger.log(Level.SEVERE, "Failed to write table start: {0}", ex); //NON-NLS
663  }
664  }
665 
672  public void startContentTagsTable(List<String> columnHeaders) {
673  StringBuilder htmlOutput = new StringBuilder();
674  htmlOutput.append("<table>\n<thead>\n\t<tr>\n"); //NON-NLS
675 
676  // Add the specified columns.
677  for (String columnHeader : columnHeaders) {
678  htmlOutput.append("\t\t<th>").append(columnHeader).append("</th>\n"); //NON-NLS
679  }
680 
681  // Add a column for a hyperlink to a local copy of the tagged content.
682  htmlOutput.append("\t\t<th></th>\n"); //NON-NLS
683 
684  htmlOutput.append("\t</tr>\n</thead>\n"); //NON-NLS
685 
686  try {
687  out.write(htmlOutput.toString());
688  } catch (IOException ex) {
689  logger.log(Level.SEVERE, "Failed to write table start: {0}", ex); //NON-NLS
690  }
691  }
692 
696  @Override
697  public void endTable() {
698  try {
699  out.write("</table>\n"); //NON-NLS
700  } catch (IOException ex) {
701  logger.log(Level.SEVERE, "Failed to write end of table: {0}", ex); //NON-NLS
702  }
703  }
704 
711  @Override
712  public void addRow(List<String> row) {
713  addRow(row, true);
714  }
715 
723  private void addRow(List<String> row, boolean escapeText) {
724  StringBuilder builder = new StringBuilder();
725  builder.append("\t<tr>\n"); //NON-NLS
726  for (String cell : row) {
727  String cellText = escapeText ? EscapeUtil.escapeHtml(cell) : cell;
728  builder.append("\t\t<td>").append(cellText).append("</td>\n"); //NON-NLS
729  }
730  builder.append("\t</tr>\n"); //NON-NLS
731  rowCount++;
732 
733  try {
734  out.write(builder.toString());
735  } catch (IOException ex) {
736  logger.log(Level.SEVERE, "Failed to write row to out.", ex); //NON-NLS
737  } catch (NullPointerException ex) {
738  logger.log(Level.SEVERE, "Output writer is null. Page was not initialized before writing.", ex); //NON-NLS
739  }
740  }
741 
749  public void addRowWithTaggedContentHyperlink(List<String> row, ContentTag contentTag) {
750  Content content = contentTag.getContent();
751  if (content instanceof AbstractFile == false) {
752  addRow(row, true);
753  return;
754  }
755  AbstractFile file = (AbstractFile) content;
756  // Add the hyperlink to the row. A column header for it was created in startTable().
757  StringBuilder localFileLink = new StringBuilder();
758  // Don't make a local copy of the file if it is a directory or unallocated space.
759  if (!(file.isDir()
760  || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS
761  || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS)) {
762  localFileLink.append("<a href=\""); //NON-NLS
763  // save it in a folder based on the tag name
764  String localFilePath = saveContent(file, contentTag.getName().getDisplayName());
765  localFileLink.append(localFilePath);
766  localFileLink.append("\" target=\"_top\">");
767  }
768 
769  StringBuilder builder = new StringBuilder();
770  builder.append("\t<tr>\n"); //NON-NLS
771  int positionCounter = 0;
772  for (String cell : row) {
773  // position-dependent code used to format this report. Not great, but understandable for formatting.
774  switch (positionCounter) {
775  case 1:
776  // Convert the file name to a hyperlink and left-align it
777  builder.append("\t\t<td class=\"left_align_cell\">").append(localFileLink.toString()).append(cell).append("</a></td>\n"); //NON-NLS
778  break;
779  case 7:
780  // Right-align the bytes column.
781  builder.append("\t\t<td class=\"right_align_cell\">").append(cell).append("</td>\n"); //NON-NLS
782  break;
783  default:
784  // Regular case, not a file name nor a byte count
785  builder.append("\t\t<td>").append(cell).append("</td>\n"); //NON-NLS
786  break;
787  }
788  ++positionCounter;
789  }
790  builder.append("\t</tr>\n"); //NON-NLS
791  rowCount++;
792 
793  try {
794  out.write(builder.toString());
795  } catch (IOException ex) {
796  logger.log(Level.SEVERE, "Failed to write row to out.", ex); //NON-NLS
797  } catch (NullPointerException ex) {
798  logger.log(Level.SEVERE, "Output writer is null. Page was not initialized before writing.", ex); //NON-NLS
799  }
800  }
801 
808  private List<ImageTagRegion> getTaggedRegions(List<ContentTag> contentTags) {
809  ArrayList<ImageTagRegion> tagRegions = new ArrayList<>();
810  contentTags.forEach((contentTag) -> {
811  try {
813  .getTag(contentTag, ImageTagRegion.class);
814  if (contentViewerTag != null) {
815  tagRegions.add(contentViewerTag.getDetails());
816  }
817  } catch (TskCoreException | NoCurrentCaseException ex) {
818  logger.log(Level.WARNING, "Could not get content viewer tag "
819  + "from case db for content_tag with id %d", contentTag.getId());
820  }
821  });
822  return tagRegions;
823  }
824 
830  public void addThumbnailRows(Set<Content> images) {
831  List<String> currentRow = new ArrayList<>();
832  int totalCount = 0;
833  int pages = 1;
834  for (Content content : images) {
835  if (currentRow.size() == THUMBNAIL_COLUMNS) {
836  addRow(currentRow, false);
837  currentRow.clear();
838  }
839 
840  if (totalCount == MAX_THUMBS_PER_PAGE) {
841  // manually set the row count so the count of items shown in the
842  // navigation page reflects the number of thumbnails instead of
843  // the number of rows.
844  rowCount = totalCount;
845  totalCount = 0;
846  pages++;
847  endTable();
848  endDataType();
849  startDataType(NbBundle.getMessage(this.getClass(), "ReportHTML.addThumbRows.dataType.title", pages),
850  NbBundle.getMessage(this.getClass(), "ReportHTML.addThumbRows.dataType.msg"));
851  List<String> emptyHeaders = new ArrayList<>();
852  for (int i = 0; i < THUMBNAIL_COLUMNS; i++) {
853  emptyHeaders.add("");
854  }
855  startTable(emptyHeaders);
856  }
857 
858  if (failsContentCheck(content)) {
859  continue;
860  }
861 
862  AbstractFile file = (AbstractFile) content;
863  List<ContentTag> contentTags = new ArrayList<>();
864 
865  String thumbnailPath = null;
866  String imageWithTagsFullPath = null;
867  try {
868  //Get content tags and all image tags
869  contentTags = Case.getCurrentCase().getServices()
871  List<ImageTagRegion> imageTags = getTaggedRegions(contentTags);
872 
873  if (!imageTags.isEmpty()) {
874  //Write the tags to the fullsize and thumbnail images
875  BufferedImage fullImageWithTags = ImageTagsUtil.getImageWithTags(file, imageTags);
876 
877  BufferedImage thumbnailWithTags = ImageTagsUtil.getThumbnailWithTags(file,
878  imageTags, ImageTagsUtil.IconSize.MEDIUM);
879 
880  String fileName = org.sleuthkit.autopsy.coreutils.FileUtil.escapeFileName(file.getName());
881 
882  //Create paths in report to write tagged images
883  File thumbnailImageWithTagsFile = Paths.get(thumbsPath, FilenameUtils.removeExtension(fileName) + ".png").toFile();
884  String fullImageWithTagsPath = makeCustomUniqueFilePath(file, "thumbs_fullsize");
885  fullImageWithTagsPath = FilenameUtils.removeExtension(fullImageWithTagsPath) + ".png";
886  File fullImageWithTagsFile = Paths.get(fullImageWithTagsPath).toFile();
887 
888  //Save images
889  ImageIO.write(thumbnailWithTags, "png", thumbnailImageWithTagsFile);
890  ImageIO.write(fullImageWithTags, "png", fullImageWithTagsFile);
891 
892  thumbnailPath = THUMBS_REL_PATH + thumbnailImageWithTagsFile.getName();
893  //Relative path
894  imageWithTagsFullPath = fullImageWithTagsPath.substring(subPath.length());
895  }
896  } catch (TskCoreException ex) {
897  logger.log(Level.WARNING, "Could not get tags for file.", ex); //NON-NLS
898  } catch (IOException | InterruptedException | ExecutionException ex) {
899  logger.log(Level.WARNING, "Could make marked up thumbnail.", ex); //NON-NLS
900  }
901 
902  // save copies of the orginal image and thumbnail image
903  if (thumbnailPath == null) {
904  thumbnailPath = prepareThumbnail(file);
905  }
906 
907  if (thumbnailPath == null) {
908  continue;
909  }
910  String contentPath = saveContent(file, "original"); //NON-NLS
911  String nameInImage;
912  try {
913  nameInImage = file.getUniquePath();
914  } catch (TskCoreException ex) {
915  nameInImage = file.getName();
916  }
917 
918  StringBuilder linkToThumbnail = new StringBuilder();
919  linkToThumbnail.append("<div id='thumbnail_link'><a href=\"")
920  .append((imageWithTagsFullPath != null) ? imageWithTagsFullPath : contentPath)
921  .append("\" target=\"_top\"><img src=\"")
922  .append(thumbnailPath).append("\" title=\"").append(nameInImage).append("\"/></a><br>") //NON-NLS
923  .append(file.getName()).append("<br>"); //NON-NLS
924  if (imageWithTagsFullPath != null) {
925  linkToThumbnail.append("<a href=\"").append(contentPath).append("\" target=\"_top\">View Original</a><br>");
926  }
927 
928  if (!contentTags.isEmpty()) {
929  linkToThumbnail.append(NbBundle.getMessage(this.getClass(), "ReportHTML.thumbLink.tags"));
930  }
931  for (int i = 0; i < contentTags.size(); i++) {
932  ContentTag tag = contentTags.get(i);
933  String notableString = tag.getName().getKnownStatus() == TskData.FileKnown.BAD ? TagsManager.getNotableTagLabel() : "";
934  linkToThumbnail.append(tag.getName().getDisplayName()).append(notableString);
935  if (i != contentTags.size() - 1) {
936  linkToThumbnail.append(", ");
937  }
938  }
939 
940  linkToThumbnail.append("</div>");
941  currentRow.add(linkToThumbnail.toString());
942 
943  totalCount++;
944  }
945 
946  if (currentRow.isEmpty() == false) {
947  int extraCells = THUMBNAIL_COLUMNS - currentRow.size();
948  for (int i = 0; i < extraCells; i++) {
949  // Finish out the row.
950  currentRow.add("");
951  }
952  addRow(currentRow, false);
953  }
954 
955  // manually set rowCount to be the total number of images.
956  rowCount = totalCount;
957  }
958 
959  private boolean failsContentCheck(Content c) {
960  if (c instanceof AbstractFile == false) {
961  return true;
962  }
963  AbstractFile file = (AbstractFile) c;
964  return file.isDir()
965  || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS
966  || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS;
967  }
968 
969  private String makeCustomUniqueFilePath(AbstractFile file, String dirName) {
970  // clean up the dir name passed in
971  String dirName2 = org.sleuthkit.autopsy.coreutils.FileUtil.escapeFileName(dirName);
972 
973  // Make a folder for the local file with the same tagName as the tag.
974  StringBuilder localFilePath = new StringBuilder(); // full path
975 
976  localFilePath.append(subPath);
977  localFilePath.append(dirName2);
978  File localFileFolder = new File(localFilePath.toString());
979  if (!localFileFolder.exists()) {
980  localFileFolder.mkdirs();
981  }
982 
983  /*
984  * Construct a file tagName for the local file that incorporates the
985  * file ID to ensure uniqueness.
986  *
987  * Note: File name is normalized to account for possible attribute name
988  * which will be separated by a ':' character.
989  */
990  String fileName = org.sleuthkit.autopsy.coreutils.FileUtil.escapeFileName(file.getName());
991  String objectIdSuffix = "_" + file.getId();
992  int lastDotIndex = fileName.lastIndexOf(".");
993  if (lastDotIndex != -1 && lastDotIndex != 0) {
994  // The file tagName has a conventional extension. Insert the object id before the '.' of the extension.
995  fileName = fileName.substring(0, lastDotIndex) + objectIdSuffix + fileName.substring(lastDotIndex, fileName.length());
996  } else {
997  // The file has no extension or the only '.' in the file is an initial '.', as in a hidden file.
998  // Add the object id to the end of the file tagName.
999  fileName += objectIdSuffix;
1000  }
1001  localFilePath.append(File.separator);
1002  localFilePath.append(fileName);
1003 
1004  return localFilePath.toString();
1005  }
1006 
1016  public String saveContent(AbstractFile file, String dirName) {
1017 
1018  String localFilePath = makeCustomUniqueFilePath(file, dirName);
1019 
1020  // If the local file doesn't already exist, create it now.
1021  // The existence check is necessary because it is possible to apply multiple tags with the same tagName to a file.
1022  File localFile = new File(localFilePath);
1023  if (!localFile.exists()) {
1024  ExtractFscContentVisitor.extract(file, localFile, null, null);
1025  }
1026 
1027  // get the relative path
1028  return localFilePath.substring(subPath.length());
1029  }
1030 
1038  @Override
1039  public String dateToString(long date) {
1040  SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
1041  return sdf.format(new java.util.Date(date * 1000));
1042  }
1043 
1044  @Override
1045  public String getRelativeFilePath() {
1046  return "report.html"; //NON-NLS
1047  }
1048 
1049  @Override
1050  public String getName() {
1051  return NbBundle.getMessage(this.getClass(), "ReportHTML.getName.text");
1052  }
1053 
1054  @Override
1055  public String getDescription() {
1056  return NbBundle.getMessage(this.getClass(), "ReportHTML.getDesc.text");
1057  }
1058 
1062  private void writeCss() {
1063  Writer cssOut = null;
1064  try {
1065  cssOut = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(subPath + "index.css"), "UTF-8")); //NON-NLS NON-NLS
1066  String css = "body {margin: 0px; padding: 0px; background: #FFFFFF; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353;}\n"
1067  + //NON-NLS
1068  "#content {padding: 30px;}\n"
1069  + //NON-NLS
1070  "#header {width:100%; padding: 10px; line-height: 25px; background: #07A; color: #FFF; font-size: 20px;}\n"
1071  + //NON-NLS
1072  "#pageHeaderFooter {width: 100%; padding: 10px; line-height: 25px; text-align: center; font-size: 20px;}\n"
1073  + //NON-NLS
1074  "h1 {font-size: 20px; font-weight: normal; color: #07A; padding: 0 0 7px 0; margin-top: 25px; border-bottom: 1px solid #D6D6D6;}\n"
1075  + //NON-NLS
1076  "h2 {font-size: 20px; font-weight: bolder; color: #07A;}\n"
1077  + //NON-NLS
1078  "h3 {font-size: 16px; color: #07A;}\n"
1079  + //NON-NLS
1080  "h4 {background: #07A; color: #FFF; font-size: 16px; margin: 0 0 0 25px; padding: 0; padding-left: 15px;}\n"
1081  + //NON-NLS
1082  "ul.nav {list-style-type: none; line-height: 35px; padding: 0px; margin-left: 15px;}\n"
1083  + //NON-NLS
1084  "ul li a {font-size: 14px; color: #444; text-decoration: none; padding-left: 25px;}\n"
1085  + //NON-NLS
1086  "ul li a:hover {text-decoration: underline;}\n"
1087  + //NON-NLS
1088  "p {margin: 0 0 20px 0;}\n"
1089  + //NON-NLS
1090  "table {white-space:nowrap; min-width: 700px; padding: 2; margin: 0; border-collapse: collapse; border-bottom: 2px solid #e5e5e5;}\n"
1091  + //NON-NLS
1092  ".keyword_list table {margin: 0 0 25px 25px; border-bottom: 2px solid #dedede;}\n"
1093  + //NON-NLS
1094  "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"
1095  + //NON-NLS
1096  "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"
1097  + //NON-NLS
1098  "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"
1099  + //NON-NLS
1100  "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"
1101  + //NON-NLS
1102  "table tr:nth-child(even) td {background: #f3f3f3;}\n"
1103  + //NON-NLS
1104  "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;}";
1105  cssOut.write(css);
1106  } catch (FileNotFoundException ex) {
1107  logger.log(Level.SEVERE, "Could not find index.css file to write to.", ex); //NON-NLS
1108  } catch (UnsupportedEncodingException ex) {
1109  logger.log(Level.SEVERE, "Did not recognize encoding when writing index.css.", ex); //NON-NLS
1110  } catch (IOException ex) {
1111  logger.log(Level.SEVERE, "Error creating Writer for index.css.", ex); //NON-NLS
1112  } finally {
1113  try {
1114  if (cssOut != null) {
1115  cssOut.flush();
1116  cssOut.close();
1117  }
1118  } catch (IOException ex) {
1119  }
1120  }
1121  }
1122 
1126  private void writeIndex() {
1127  Writer indexOut = null;
1128  String indexFilePath = path + "report.html"; //NON-NLS
1129  Case openCase;
1130  try {
1131  openCase = Case.getCurrentCaseThrows();
1132  } catch (NoCurrentCaseException ex) {
1133  logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS
1134  return;
1135  }
1136  try {
1137  indexOut = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(indexFilePath), "UTF-8")); //NON-NLS
1138  StringBuilder index = new StringBuilder();
1139  final String reportTitle = reportBranding.getReportTitle();
1140  String iconPath = reportBranding.getAgencyLogoPath();
1141  if (iconPath == null) {
1142  // use default Autopsy icon if custom icon is not set
1143  iconPath = HTML_SUBDIR + "favicon.ico";
1144  } else {
1145  iconPath = Paths.get(reportBranding.getAgencyLogoPath()).getFileName().toString(); //ref to writeNav() for agency_logo
1146  }
1147  index.append("<head>\n<title>").append(reportTitle).append(" ").append(
1148  NbBundle.getMessage(this.getClass(), "ReportHTML.writeIndex.title", currentCase.getDisplayName())).append(
1149  "</title>\n"); //NON-NLS
1150  index.append("<link rel=\"icon\" type=\"image/ico\" href=\"")
1151  .append(iconPath).append("\" />\n"); //NON-NLS
1152  index.append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"); //NON-NLS
1153  index.append("</head>\n"); //NON-NLS
1154  index.append("<frameset cols=\"350px,*\">\n"); //NON-NLS
1155  index.append("<frame src=\"" + HTML_SUBDIR).append(File.separator).append("nav.html\" name=\"nav\">\n"); //NON-NLS
1156  index.append("<frame src=\"" + HTML_SUBDIR).append(File.separator).append("summary.html\" name=\"content\">\n"); //NON-NLS
1157  index.append("<noframes>").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeIndex.noFrames.msg")).append("<br />\n"); //NON-NLS
1158  index.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeIndex.noFrames.seeNav")).append("<br />\n"); //NON-NLS
1159  index.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeIndex.seeSum")).append("</noframes>\n"); //NON-NLS
1160  index.append("</frameset>\n"); //NON-NLS
1161  index.append("</html>"); //NON-NLS
1162  indexOut.write(index.toString());
1163  openCase.addReport(indexFilePath, NbBundle.getMessage(this.getClass(),
1164  "ReportHTML.writeIndex.srcModuleName.text"), "");
1165  } catch (IOException ex) {
1166  logger.log(Level.SEVERE, "Error creating Writer for report.html: {0}", ex); //NON-NLS
1167  } catch (TskCoreException ex) {
1168  String errorMessage = String.format("Error adding %s to case as a report", indexFilePath); //NON-NLS
1169  logger.log(Level.SEVERE, errorMessage, ex);
1170  } finally {
1171  try {
1172  if (indexOut != null) {
1173  indexOut.flush();
1174  indexOut.close();
1175  }
1176  } catch (IOException ex) {
1177  }
1178  }
1179  }
1180 
1184  private void writeNav() {
1185  Writer navOut = null;
1186  try {
1187  navOut = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(subPath + "nav.html"), "UTF-8")); //NON-NLS
1188  StringBuilder nav = new StringBuilder();
1189  nav.append("<html>\n<head>\n\t<title>").append( //NON-NLS
1190  NbBundle.getMessage(this.getClass(), "ReportHTML.writeNav.title"))
1191  .append("</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"index.css\" />\n"); //NON-NLS
1192  nav.append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n</head>\n<body>\n"); //NON-NLS
1193  nav.append("<div id=\"content\">\n<h1>").append( //NON-NLS
1194  NbBundle.getMessage(this.getClass(), "ReportHTML.writeNav.h1")).append("</h1>\n"); //NON-NLS
1195  nav.append("<ul class=\"nav\">\n"); //NON-NLS
1196  nav.append("<li style=\"background: url(summary.png) left center no-repeat;\"><a href=\"summary.html\" target=\"content\">") //NON-NLS
1197  .append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeNav.summary")).append("</a></li>\n"); //NON-NLS
1198 
1199  for (String dataType : dataTypes.keySet()) {
1200  String dataTypeEsc = dataTypeToFileName(dataType);
1201  String iconFileName = useDataTypeIcon(dataType);
1202  nav.append("<li style=\"background: url('").append(iconFileName) //NON-NLS
1203  .append("') left center no-repeat;\"><a href=\"") //NON-NLS
1204  .append(dataTypeEsc).append(".html\" target=\"content\">") //NON-NLS
1205  .append(dataType).append(" (").append(dataTypes.get(dataType))
1206  .append(")</a></li>\n"); //NON-NLS
1207  }
1208  nav.append("</ul>\n"); //NON-NLS
1209  nav.append("</div>\n</body>\n</html>"); //NON-NLS
1210  navOut.write(nav.toString());
1211  } catch (IOException ex) {
1212  logger.log(Level.SEVERE, "Failed to write end of report navigation menu: {0}", ex); //NON-NLS
1213  } finally {
1214  if (navOut != null) {
1215  try {
1216  navOut.flush();
1217  navOut.close();
1218  } catch (IOException ex) {
1219  logger.log(Level.WARNING, "Could not close navigation out writer."); //NON-NLS
1220  }
1221  }
1222  }
1223 
1224  InputStream in = null;
1225  OutputStream output = null;
1226  try {
1227 
1228  //pull generator and agency logo from branding, and the remaining resources from the core jar
1229  String generatorLogoPath = reportBranding.getGeneratorLogoPath();
1230  if (generatorLogoPath != null && !generatorLogoPath.isEmpty()) {
1231  File from = new File(generatorLogoPath);
1232  File to = new File(subPath);
1233  FileUtil.copyFile(FileUtil.toFileObject(from), FileUtil.toFileObject(to), "generator_logo"); //NON-NLS
1234  }
1235 
1236  String agencyLogoPath = reportBranding.getAgencyLogoPath();
1237  if (agencyLogoPath != null && !agencyLogoPath.isEmpty()) {
1238  Path destinationPath = Paths.get(subPath);
1239  Files.copy(Files.newInputStream(Paths.get(agencyLogoPath)), destinationPath.resolve(Paths.get(agencyLogoPath).getFileName())); //NON-NLS
1240  }
1241 
1242  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/favicon.ico"); //NON-NLS
1243  output = new FileOutputStream(new File(subPath + "favicon.ico"));
1244  FileUtil.copy(in, output);
1245  in.close();
1246  output.close();
1247 
1248  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/summary.png"); //NON-NLS
1249  output = new FileOutputStream(new File(subPath + "summary.png"));
1250  FileUtil.copy(in, output);
1251  in.close();
1252  output.close();
1253 
1254  } catch (IOException ex) {
1255  logger.log(Level.SEVERE, "Failed to extract images for HTML report.", ex); //NON-NLS
1256  } finally {
1257  if (output != null) {
1258  try {
1259  output.flush();
1260  output.close();
1261  } catch (IOException ex) {
1262  }
1263  }
1264  if (in != null) {
1265  try {
1266  in.close();
1267  } catch (IOException ex) {
1268  }
1269  }
1270  }
1271  }
1272 
1276  private void writeSummary() {
1277  Writer output = null;
1278  try {
1279  output = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(subPath + "summary.html"), "UTF-8")); //NON-NLS
1280  StringBuilder head = new StringBuilder();
1281  head.append("<html>\n<head>\n<title>").append( //NON-NLS
1282  NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.title")).append("</title>\n"); //NON-NLS
1283  head.append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"); //NON-NLS
1284  head.append("<style type=\"text/css\">\n"); //NON-NLS
1285  head.append("#pageHeaderFooter {width: 100%; padding: 10px; line-height: 25px; text-align: center; font-size: 20px;}\n"); //NON-NLS
1286  head.append("body { padding: 0px; margin: 0px; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353; }\n"); //NON-NLS
1287  head.append("#wrapper { width: 90%; margin: 0px auto; margin-top: 35px; }\n"); //NON-NLS
1288  head.append("h1 { color: #07A; font-size: 36px; line-height: 42px; font-weight: normal; margin: 0px; border-bottom: 1px solid #81B9DB; }\n"); //NON-NLS
1289  head.append("h1 span { color: #F00; display: block; font-size: 16px; font-weight: bold; line-height: 22px;}\n"); //NON-NLS
1290  head.append("h2 { padding: 0 0 3px 0; margin: 0px; color: #07A; font-weight: normal; border-bottom: 1px dotted #81B9DB; }\n"); //NON-NLS
1291  head.append("h3 { padding: 5 0 3px 0; margin: 0px; color: #07A; font-weight: normal; }\n");
1292  head.append("table td { padding: 5px 25px 5px 0px; vertical-align:top;}\n"); //NON-NLS
1293  head.append("p.subheadding { padding: 0px; margin: 0px; font-size: 11px; color: #B5B5B5; }\n"); //NON-NLS
1294  head.append(".title { width: 660px; margin-bottom: 50px; }\n"); //NON-NLS
1295  head.append(".left { float: left; width: 250px; margin-top: 20px; text-align: center; }\n"); //NON-NLS
1296  head.append(".left img { max-width: 250px; max-height: 250px; min-width: 200px; min-height: 200px; }\n"); //NON-NLS
1297  head.append(".right { float: right; width: 385px; margin-top: 25px; font-size: 14px; }\n"); //NON-NLS
1298  head.append(".clear { clear: both; }\n"); //NON-NLS
1299  head.append(".info { padding: 10px 0;}\n");
1300  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
1301  head.append(".info table { margin: 10px 25px 10px 25px; }\n"); //NON-NLS
1302  head.append("ul {padding: 0;margin: 0;list-style-type: none;}");
1303  head.append("li {padding-bottom: 5px;}");
1304  head.append("</style>\n"); //NON-NLS
1305  head.append("</head>\n<body>\n"); //NON-NLS
1306  output.write(head.toString());
1307 
1308  DateFormat datetimeFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
1309  Date date = new Date();
1310  String datetime = datetimeFormat.format(date);
1311 
1312  StringBuilder summary = new StringBuilder();
1313  boolean running = false;
1315  running = true;
1316  }
1317  SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
1318  List<IngestJobInfo> ingestJobs = skCase.getIngestJobs();
1319  final String reportTitle = reportBranding.getReportTitle();
1320  final String reportFooter = reportBranding.getReportFooter();
1321  final boolean generatorLogoSet = reportBranding.getGeneratorLogoPath() != null && !reportBranding.getGeneratorLogoPath().isEmpty();
1322 
1323  summary.append("<div id=\"wrapper\">\n"); //NON-NLS
1324  summary.append(writePageHeader());
1325  summary.append("<h1>").append(reportTitle) //NON-NLS
1326  .append(running ? NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.warningMsg") : "")
1327  .append("</h1>\n"); //NON-NLS
1328  summary.append("<p class=\"subheadding\">").append( //NON-NLS
1329  NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.reportGenOn.text", datetime)).append("</p>\n"); //NON-NLS
1330  summary.append("<div class=\"title\">\n"); //NON-NLS
1331  summary.append(writeSummaryCaseDetails());
1332  summary.append(writeSummaryImageInfo());
1333  summary.append(writeSummarySoftwareInfo(skCase, ingestJobs));
1334  summary.append(writeSummaryIngestHistoryInfo(skCase, ingestJobs));
1335  if (generatorLogoSet) {
1336  summary.append("<div class=\"left\">\n"); //NON-NLS
1337  summary.append("<img src=\"generator_logo.png\" />\n"); //NON-NLS
1338  summary.append("</div>\n"); //NON-NLS
1339  }
1340  summary.append("<div class=\"clear\"></div>\n"); //NON-NLS
1341  if (reportFooter != null) {
1342  summary.append("<p class=\"subheadding\">").append(reportFooter).append("</p>\n"); //NON-NLS
1343  }
1344  summary.append("</div>\n"); //NON-NLS
1345  summary.append(writePageFooter());
1346  summary.append("</body></html>"); //NON-NLS
1347  output.write(summary.toString());
1348  } catch (FileNotFoundException ex) {
1349  logger.log(Level.SEVERE, "Could not find summary.html file to write to."); //NON-NLS
1350  } catch (UnsupportedEncodingException ex) {
1351  logger.log(Level.SEVERE, "Did not recognize encoding when writing summary.hmtl."); //NON-NLS
1352  } catch (IOException ex) {
1353  logger.log(Level.SEVERE, "Error creating Writer for summary.html."); //NON-NLS
1354  } catch (NoCurrentCaseException | TskCoreException ex) {
1355  logger.log(Level.WARNING, "Unable to get current sleuthkit Case for the HTML report.");
1356  } finally {
1357  try {
1358  if (output != null) {
1359  output.flush();
1360  output.close();
1361  }
1362  } catch (IOException ex) {
1363  }
1364  }
1365  }
1366 
1367  @Messages({
1368  "ReportHTML.writeSum.case=Case:",
1369  "ReportHTML.writeSum.caseNumber=Case Number:",
1370  "ReportHTML.writeSum.caseNumImages=Number of data sources in case:",
1371  "ReportHTML.writeSum.caseNotes=Notes:",
1372  "ReportHTML.writeSum.examiner=Examiner:"
1373  })
1379  private StringBuilder writeSummaryCaseDetails() {
1380  StringBuilder summary = new StringBuilder();
1381 
1382  final boolean agencyLogoSet = reportBranding.getAgencyLogoPath() != null && !reportBranding.getAgencyLogoPath().isEmpty();
1383 
1384  // Case
1385  String caseName = currentCase.getDisplayName();
1386  String caseNumber = currentCase.getNumber();
1387  int imagecount;
1388  try {
1389  imagecount = currentCase.getDataSources().size();
1390  } catch (TskCoreException ex) {
1391  imagecount = 0;
1392  }
1393  String caseNotes = currentCase.getCaseNotes();
1394 
1395  // Examiner
1396  String examinerName = currentCase.getExaminer();
1397 
1398  // Start the layout.
1399  summary.append("<div class=\"title\">\n"); //NON-NLS
1400  if (agencyLogoSet) {
1401  summary.append("<div class=\"left\">\n"); //NON-NLS
1402  summary.append("<img src=\"");
1403  summary.append(Paths.get(reportBranding.getAgencyLogoPath()).getFileName().toString());
1404  summary.append("\" />\n"); //NON-NLS
1405  summary.append("</div>\n"); //NON-NLS
1406  }
1407  final String align = agencyLogoSet ? "right" : "left"; //NON-NLS NON-NLS
1408  summary.append("<div class=\"").append(align).append("\">\n"); //NON-NLS
1409  summary.append("<table>\n"); //NON-NLS
1410 
1411  // Case details
1412  summary.append("<tr><td>").append(Bundle.ReportHTML_writeSum_case()).append("</td><td>") //NON-NLS
1413  .append(formatHtmlString(caseName)).append("</td></tr>\n"); //NON-NLS
1414 
1415  if (!caseNumber.isEmpty()) {
1416  summary.append("<tr><td>").append(Bundle.ReportHTML_writeSum_caseNumber()).append("</td><td>") //NON-NLS
1417  .append(formatHtmlString(caseNumber)).append("</td></tr>\n"); //NON-NLS
1418  }
1419 
1420  summary.append("<tr><td>").append(Bundle.ReportHTML_writeSum_caseNumImages()).append("</td><td>") //NON-NLS
1421  .append(imagecount).append("</td></tr>\n"); //NON-NLS
1422 
1423  if (!caseNotes.isEmpty()) {
1424  summary.append("<tr><td>").append(Bundle.ReportHTML_writeSum_caseNotes()).append("</td><td>") //NON-NLS
1425  .append(formatHtmlString(caseNotes)).append("</td></tr>\n"); //NON-NLS
1426  }
1427 
1428  // Examiner details
1429  if (!examinerName.isEmpty()) {
1430  summary.append("<tr><td>").append(Bundle.ReportHTML_writeSum_examiner()).append("</td><td>") //NON-NLS
1431  .append(formatHtmlString(examinerName)).append("</td></tr>\n"); //NON-NLS
1432  }
1433 
1434  // End the layout.
1435  summary.append("</table>\n"); //NON-NLS
1436  summary.append("</div>\n"); //NON-NLS
1437  summary.append("<div class=\"clear\"></div>\n"); //NON-NLS
1438  summary.append("</div>\n"); //NON-NLS
1439  return summary;
1440  }
1441 
1447  private StringBuilder writeSummaryImageInfo() {
1448  StringBuilder summary = new StringBuilder();
1449  summary.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.imageInfoHeading"));
1450  summary.append("<div class=\"info\">\n"); //NON-NLS
1451  try {
1452  for (Content c : currentCase.getDataSources()) {
1453  summary.append("<p>").append(c.getName()).append("</p>\n"); //NON-NLS
1454  if (c instanceof Image) {
1455  Image img = (Image) c;
1456 
1457  summary.append("<table>\n"); //NON-NLS
1458  summary.append("<tr><td>").append( //NON-NLS
1459  NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.timezone"))
1460  .append("</td><td>").append(img.getTimeZone()).append("</td></tr>\n"); //NON-NLS
1461  for (String imgPath : img.getPaths()) {
1462  summary.append("<tr><td>").append( //NON-NLS
1463  NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.path"))
1464  .append("</td><td>").append(imgPath).append("</td></tr>\n"); //NON-NLS
1465  }
1466  summary.append("</table>\n"); //NON-NLS
1467  }
1468  }
1469  } catch (TskCoreException ex) {
1470  logger.log(Level.WARNING, "Unable to get image information for the HTML report."); //NON-NLS
1471  }
1472  summary.append("</div>\n"); //NON-NLS
1473  return summary;
1474  }
1475 
1481  private StringBuilder writeSummarySoftwareInfo(SleuthkitCase skCase, List<IngestJobInfo> ingestJobs) {
1482  StringBuilder summary = new StringBuilder();
1483  summary.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.softwareInfoHeading"));
1484  summary.append("<div class=\"info\">\n");
1485  summary.append("<table>\n");
1486  summary.append("<tr><td>").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.autopsyVersion"))
1487  .append("</td><td>").append(Version.getVersion()).append("</td></tr>\n");
1488  Map<Long, IngestModuleInfo> moduleInfoHashMap = new HashMap<>();
1489  for (IngestJobInfo ingestJob : ingestJobs) {
1490  List<IngestModuleInfo> ingestModules = ingestJob.getIngestModuleInfo();
1491  for (IngestModuleInfo ingestModule : ingestModules) {
1492  if (!moduleInfoHashMap.containsKey(ingestModule.getIngestModuleId())) {
1493  moduleInfoHashMap.put(ingestModule.getIngestModuleId(), ingestModule);
1494  }
1495  }
1496  }
1497  TreeMap<String, String> modules = new TreeMap<>();
1498  for (IngestModuleInfo moduleinfo : moduleInfoHashMap.values()) {
1499  modules.put(moduleinfo.getDisplayName(), moduleinfo.getVersion());
1500  }
1501  for (Map.Entry<String, String> module : modules.entrySet()) {
1502  summary.append("<tr><td>").append(module.getKey()).append(" Module:")
1503  .append("</td><td>").append(module.getValue()).append("</td></tr>\n");
1504  }
1505  summary.append("</table>\n");
1506  summary.append("</div>\n");
1507  summary.append("<div class=\"clear\"></div>\n"); //NON-NLS
1508  return summary;
1509  }
1510 
1516  private StringBuilder writeSummaryIngestHistoryInfo(SleuthkitCase skCase, List<IngestJobInfo> ingestJobs) {
1517  StringBuilder summary = new StringBuilder();
1518  try {
1519  summary.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.ingestHistoryHeading"));
1520  summary.append("<div class=\"info\">\n");
1521  int jobnumber = 1;
1522 
1523  for (IngestJobInfo ingestJob : ingestJobs) {
1524  summary.append("<h3>Job ").append(jobnumber).append(":</h3>\n");
1525  summary.append("<table>\n");
1526  summary.append("<tr><td>").append("Data Source:")
1527  .append("</td><td>").append(skCase.getContentById(ingestJob.getObjectId()).getName()).append("</td></tr>\n");
1528  summary.append("<tr><td>").append("Status:")
1529  .append("</td><td>").append(ingestJob.getStatus()).append("</td></tr>\n");
1530  summary.append("<tr><td>").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.modulesEnabledHeading"))
1531  .append("</td><td>");
1532  List<IngestModuleInfo> ingestModules = ingestJob.getIngestModuleInfo();
1533  summary.append("<ul>\n");
1534  for (IngestModuleInfo ingestModule : ingestModules) {
1535  summary.append("<li>").append(ingestModule.getDisplayName()).append("</li>");
1536  }
1537  summary.append("</ul>\n");
1538  jobnumber++;
1539  summary.append("</td></tr>\n");
1540  summary.append("</table>\n");
1541  }
1542  summary.append("</div>\n");
1543  } catch (TskCoreException ex) {
1544  logger.log(Level.WARNING, "Unable to get ingest jobs for the HTML report.");
1545  }
1546  return summary;
1547  }
1548 
1557  private String prepareThumbnail(AbstractFile file) {
1558  BufferedImage bufferedThumb = ImageUtils.getThumbnail(file, ImageUtils.ICON_SIZE_MEDIUM);
1559 
1560  /*
1561  * File name is normalized to account for possible attribute name which
1562  * will be separated by a ':' character.
1563  */
1564  String fileName = org.sleuthkit.autopsy.coreutils.FileUtil.escapeFileName(file.getName());
1565 
1566  File thumbFile = Paths.get(thumbsPath, fileName + ".png").toFile();
1567  if (bufferedThumb == null) {
1568  return null;
1569  }
1570  try {
1571  ImageIO.write(bufferedThumb, "png", thumbFile);
1572  } catch (IOException ex) {
1573  logger.log(Level.WARNING, "Failed to write thumb file to report directory.", ex); //NON-NLS
1574  return null;
1575  }
1576  if (thumbFile.exists()
1577  == false) {
1578  return null;
1579  }
1580  return THUMBS_REL_PATH
1581  + thumbFile.getName();
1582  }
1583 
1592  private String formatHtmlString(String text) {
1593  String formattedString = StringEscapeUtils.escapeHtml4(text);
1594  return formattedString.replaceAll("(\r\n|\r|\n|\n\r)", "<br>");
1595  }
1596 
1597 }
List< Content > getDataSources()
Definition: Case.java:1428
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:1646
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:2721
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-2020 Basis Technology. Generated on: Tue Sep 22 2020
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.