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

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