23 package org.sleuthkit.autopsy.report.modules.html;
27 import java.awt.image.BufferedImage;
28 import java.io.BufferedWriter;
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;
49 import java.util.TreeMap;
50 import java.util.concurrent.ExecutionException;
51 import java.util.logging.Level;
52 import javax.imageio.ImageIO;
53 import javax.swing.JPanel;
54 import org.apache.commons.io.FilenameUtils;
55 import org.apache.commons.text.StringEscapeUtils;
56 import org.openide.filesystems.FileUtil;
57 import org.openide.util.NbBundle;
58 import org.openide.util.NbBundle.Messages;
111 if (instance == null) {
129 if (configPanel == null) {
130 configPanel =
new HTMLReportConfigurationPanel();
141 return new HTMLReportModuleSettings();
152 return configPanel.getConfiguration();
168 if (settings instanceof HTMLReportModuleSettings) {
169 configPanel.setConfiguration((HTMLReportModuleSettings) settings);
173 throw new IllegalArgumentException(
"Expected settings argument to be an instance of HTMLReportModuleSettings");
180 dataTypes =
new TreeMap<>();
185 currentDataType =
"";
191 }
catch (IOException ex) {
207 fileName = fileName.replaceAll(
" ",
"_");
216 @SuppressWarnings(
"deprecation")
221 OutputStream output = null;
223 logger.log(Level.INFO,
"useDataTypeIcon: dataType = {0}", dataType);
228 if (v.getDisplayName().equals(dataType)) {
233 if (null != artifactType) {
236 iconFilePath = subPath + File.separator + iconFileName;
239 switch (artifactType) {
240 case TSK_WEB_BOOKMARK:
241 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/bookmarks.png");
244 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/cookies.png");
246 case TSK_WEB_HISTORY:
247 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/history.png");
249 case TSK_WEB_DOWNLOAD:
250 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/downloads.png");
252 case TSK_RECENT_OBJECT:
253 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/recent.png");
255 case TSK_INSTALLED_PROG:
256 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/installed.png");
258 case TSK_KEYWORD_HIT:
259 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/keywords.png");
261 case TSK_HASHSET_HIT:
262 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/hash.png");
264 case TSK_DEVICE_ATTACHED:
265 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/devices.png");
267 case TSK_WEB_SEARCH_QUERY:
268 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/search.png");
270 case TSK_METADATA_EXIF:
271 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/exif.png");
274 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/userbookmarks.png");
276 case TSK_TAG_ARTIFACT:
277 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/userbookmarks.png");
279 case TSK_SERVICE_ACCOUNT:
280 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/account-icon-16.png");
283 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/contact.png");
286 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/message.png");
289 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/calllog.png");
291 case TSK_CALENDAR_ENTRY:
292 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/calendar.png");
294 case TSK_SPEED_DIAL_ENTRY:
295 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/speeddialentry.png");
297 case TSK_BLUETOOTH_PAIRING:
298 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/bluetooth.png");
300 case TSK_GPS_BOOKMARK:
301 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gpsfav.png");
303 case TSK_GPS_LAST_KNOWN_LOCATION:
304 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gps-lastlocation.png");
307 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gps-search.png");
310 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/computer.png");
312 case TSK_GPS_TRACKPOINT:
313 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gps_trackpoint.png");
316 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gps_trackpoint.png");
319 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/mail-icon-16.png");
321 case TSK_ENCRYPTION_SUSPECTED:
322 case TSK_ENCRYPTION_DETECTED:
323 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/encrypted-file.png");
325 case TSK_EXT_MISMATCH_DETECTED:
326 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/mismatch-16.png");
328 case TSK_INTERESTING_ARTIFACT_HIT:
330 case TSK_INTERESTING_FILE_HIT:
332 case TSK_INTERESTING_ITEM:
333 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/interesting_item.png");
336 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/installed.png");
338 case TSK_REMOTE_DRIVE:
339 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/drive_network.png");
342 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/os-account.png");
344 case TSK_OBJECT_DETECTED:
345 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/objects.png");
347 case TSK_WEB_FORM_AUTOFILL:
348 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/web-form.png");
351 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/cache.png");
353 case TSK_USER_CONTENT_SUSPECTED:
354 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/user-content.png");
357 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/metadata.png");
359 case TSK_CLIPBOARD_CONTENT:
360 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/clipboard.png");
363 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/accounts.png");
365 case TSK_WIFI_NETWORK:
366 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/network-wifi.png");
368 case TSK_WIFI_NETWORK_ADAPTER:
369 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/network-wifi.png");
371 case TSK_SIM_ATTACHED:
372 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/sim_card.png");
374 case TSK_BLUETOOTH_ADAPTER:
375 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/Bluetooth.png");
377 case TSK_DEVICE_INFO:
378 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/devices.png");
380 case TSK_VERIFICATION_FAILED:
381 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/validationFailed.png");
383 case TSK_WEB_ACCOUNT_TYPE:
384 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/web-account-type.png");
386 case TSK_WEB_FORM_ADDRESS:
387 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/web-form-address.png");
390 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/gps-area.png");
392 case TSK_WEB_CATEGORIZATION:
393 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/domain-16.png");
396 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/yara_16.png");
398 case TSK_PREVIOUSLY_SEEN:
399 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/previously-seen.png");
401 case TSK_PREVIOUSLY_UNSEEN:
402 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/previously-unseen.png");
404 case TSK_PREVIOUSLY_NOTABLE:
405 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/red-circle-exclamation.png");
408 logger.log(Level.WARNING,
"useDataTypeIcon: unhandled artifact type = {0}", dataType);
409 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/star.png");
410 iconFileName =
"star.png";
411 iconFilePath = subPath + File.separator + iconFileName;
422 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/accounts.png");
423 iconFileName =
"accounts.png";
424 iconFilePath = subPath + File.separator + iconFileName;
426 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/star.png");
427 iconFileName =
"star.png";
428 iconFilePath = subPath + File.separator + iconFileName;
432 output =
new FileOutputStream(iconFilePath);
433 FileUtil.copy(in, output);
436 }
catch (IOException ex) {
437 logger.log(Level.SEVERE,
"Failed to extract images for HTML report.", ex);
439 if (output != null) {
443 }
catch (IOException ex) {
449 }
catch (IOException ex) {
470 logger.log(Level.SEVERE,
"Exception while getting open case.");
474 this.path = baseReportDir;
475 this.subPath = this.path + HTML_SUBDIR + File.separator;
478 FileUtil.createFolder(
new File(this.subPath));
479 FileUtil.createFolder(
new File(this.thumbsPath));
480 }
catch (IOException ex) {
481 logger.log(Level.SEVERE,
"Unable to make HTML report folder.");
499 }
catch (IOException ex) {
500 logger.log(Level.WARNING,
"Could not close the output writer when ending report.", ex);
517 out =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(subPath + title +
".html"),
"UTF-8"));
518 }
catch (FileNotFoundException ex) {
519 logger.log(Level.SEVERE,
"File not found: {0}", ex);
520 }
catch (UnsupportedEncodingException ex) {
521 logger.log(Level.SEVERE,
"Unrecognized encoding");
525 StringBuilder page =
new StringBuilder();
526 page.append(
"<html>\n<head>\n\t<title>").append(name).append(
"</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"index.css\" />\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n</head>\n<body>\n")
528 .append(
"<div id=\"header\">").append(name).append(
"</div>\n")
529 .append(
"<div id=\"content\">\n");
530 if (!description.isEmpty()) {
531 page.append(
"<p><strong>");
532 page.append(description);
533 page.append(
"</strong></p>\n");
535 out.write(page.toString());
536 currentDataType = name;
538 }
catch (IOException ex) {
539 logger.log(Level.SEVERE,
"Failed to write page head: {0}", ex);
549 dataTypes.put(currentDataType, rowCount);
551 StringBuilder builder =
new StringBuilder();
553 builder.append(
"</div>\n</body>\n</html>\n");
554 out.write(builder.toString());
555 }
catch (IOException ex) {
556 logger.log(Level.SEVERE,
"Failed to write end of HTML report.", ex);
562 }
catch (IOException ex) {
563 logger.log(Level.WARNING,
"Could not close the output writer when ending data type.", ex);
577 StringBuilder output =
new StringBuilder();
578 String pageHeader = configPanel.getHeader();
579 if (pageHeader.isEmpty() ==
false) {
580 output.append(
"<div id=\"pageHeaderFooter\">")
581 .append(StringEscapeUtils.escapeHtml4(pageHeader))
584 return output.toString();
594 StringBuilder output =
new StringBuilder();
595 String pageFooter = configPanel.getFooter();
596 if (pageFooter.isEmpty() ==
false) {
597 output.append(
"<br/><div id=\"pageHeaderFooter\">")
598 .append(StringEscapeUtils.escapeHtml4(pageFooter))
601 return output.toString();
611 StringBuilder set =
new StringBuilder();
612 set.append(
"<h1><a name=\"").append(setName).append(
"\">").append(setName).append(
"</a></h1>\n");
613 set.append(
"<div class=\"keyword_list\">\n");
616 out.write(set.toString());
617 }
catch (IOException ex) {
618 logger.log(Level.SEVERE,
"Failed to write set: {0}", ex);
628 out.write(
"</div>\n");
629 }
catch (IOException ex) {
630 logger.log(Level.SEVERE,
"Failed to write end of set: {0}", ex);
641 StringBuilder index =
new StringBuilder();
642 index.append(
"<ul>\n");
643 for (String set : sets) {
644 index.append(
"\t<li><a href=\"#").append(set).append(
"\">").append(set).append(
"</a></li>\n");
646 index.append(
"</ul>\n");
648 out.write(index.toString());
649 }
catch (IOException ex) {
650 logger.log(Level.SEVERE,
"Failed to add set index: {0}", ex);
662 out.write(
"<h4>" + elementName +
"</h4>\n");
663 }
catch (IOException ex) {
664 logger.log(Level.SEVERE,
"Failed to write set element: {0}", ex);
675 StringBuilder ele =
new StringBuilder();
676 ele.append(
"<table>\n<thead>\n\t<tr>\n");
677 for (String title : titles) {
678 ele.append(
"\t\t<th>").append(title).append(
"</th>\n");
680 ele.append(
"\t</tr>\n</thead>\n");
683 out.write(ele.toString());
684 }
catch (IOException ex) {
685 logger.log(Level.SEVERE,
"Failed to write table start: {0}", ex);
696 StringBuilder htmlOutput =
new StringBuilder();
697 htmlOutput.append(
"<table>\n<thead>\n\t<tr>\n");
700 for (String columnHeader : columnHeaders) {
701 htmlOutput.append(
"\t\t<th>").append(columnHeader).append(
"</th>\n");
705 htmlOutput.append(
"\t\t<th></th>\n");
707 htmlOutput.append(
"\t</tr>\n</thead>\n");
710 out.write(htmlOutput.toString());
711 }
catch (IOException ex) {
712 logger.log(Level.SEVERE,
"Failed to write table start: {0}", ex);
722 out.write(
"</table>\n");
723 }
catch (IOException ex) {
724 logger.log(Level.SEVERE,
"Failed to write end of table: {0}", ex);
746 private void addRow(List<String> row,
boolean escapeText) {
747 StringBuilder builder =
new StringBuilder();
748 builder.append(
"\t<tr>\n");
749 for (String cell : row) {
751 builder.append(
"\t\t<td>").append(cellText).append(
"</td>\n");
753 builder.append(
"\t</tr>\n");
757 out.write(builder.toString());
758 }
catch (IOException ex) {
759 logger.log(Level.SEVERE,
"Failed to write row to out.", ex);
760 }
catch (NullPointerException ex) {
761 logger.log(Level.SEVERE,
"Output writer is null. Page was not initialized before writing.", ex);
780 StringBuilder localFileLink =
new StringBuilder();
785 localFileLink.append(
"<a href=\"");
788 localFileLink.append(localFilePath);
789 localFileLink.append(
"\" target=\"_top\">");
792 StringBuilder builder =
new StringBuilder();
793 builder.append(
"\t<tr>\n");
794 int positionCounter = 0;
795 for (String cell : row) {
797 switch (positionCounter) {
800 builder.append(
"\t\t<td class=\"left_align_cell\">").append(localFileLink.toString()).append(cell).append(
"</a></td>\n");
804 builder.append(
"\t\t<td class=\"right_align_cell\">").append(cell).append(
"</td>\n");
808 builder.append(
"\t\t<td>").append(cell).append(
"</td>\n");
813 builder.append(
"\t</tr>\n");
817 out.write(builder.toString());
818 }
catch (IOException ex) {
819 logger.log(Level.SEVERE,
"Failed to write row to out.", ex);
820 }
catch (NullPointerException ex) {
821 logger.log(Level.SEVERE,
"Output writer is null. Page was not initialized before writing.", ex);
833 ArrayList<ImageTagRegion> tagRegions =
new ArrayList<>();
834 contentTags.forEach((contentTag) -> {
838 if (contentViewerTag != null) {
839 tagRegions.add(contentViewerTag.
getDetails());
842 logger.log(Level.WARNING,
"Could not get content viewer tag "
843 +
"from case db for content_tag with id %d", contentTag.getId());
855 List<String> currentRow =
new ArrayList<>();
858 for (
Content content : images) {
860 addRow(currentRow,
false);
864 if (totalCount == MAX_THUMBS_PER_PAGE) {
868 rowCount = totalCount;
873 startDataType(NbBundle.getMessage(
this.getClass(),
"ReportHTML.addThumbRows.dataType.title", pages),
874 NbBundle.getMessage(
this.getClass(),
"ReportHTML.addThumbRows.dataType.msg"));
875 List<String> emptyHeaders =
new ArrayList<>();
877 emptyHeaders.add(
"");
887 List<ContentTag> contentTags =
new ArrayList<>();
889 String thumbnailPath = null;
890 String imageWithTagsFullPath = null;
897 if (!imageTags.isEmpty()) {
907 File thumbnailImageWithTagsFile = Paths.get(thumbsPath, FilenameUtils.removeExtension(fileName) +
".png").toFile();
909 fullImageWithTagsPath = FilenameUtils.removeExtension(fullImageWithTagsPath) +
".png";
910 File fullImageWithTagsFile = Paths.get(fullImageWithTagsPath).toFile();
913 ImageIO.write(thumbnailWithTags,
"png", thumbnailImageWithTagsFile);
914 ImageIO.write(fullImageWithTags,
"png", fullImageWithTagsFile);
916 thumbnailPath = THUMBS_REL_PATH + thumbnailImageWithTagsFile.getName();
918 imageWithTagsFullPath = fullImageWithTagsPath.substring(subPath.length());
921 logger.log(Level.WARNING,
"Could not get tags for file.", ex);
922 }
catch (IOException | InterruptedException | ExecutionException ex) {
923 logger.log(Level.WARNING,
"Could make marked up thumbnail.", ex);
927 if (thumbnailPath == null) {
931 if (thumbnailPath == null) {
934 String contentPath =
saveContent(file,
"original");
942 StringBuilder linkToThumbnail =
new StringBuilder();
943 linkToThumbnail.append(
"<div id='thumbnail_link'><a href=\"")
944 .append((imageWithTagsFullPath != null) ? imageWithTagsFullPath : contentPath)
945 .append(
"\" target=\"_top\"><img src=\"")
946 .append(thumbnailPath).append(
"\" title=\"").append(nameInImage).append(
"\"/></a><br>")
947 .append(file.
getName()).append(
"<br>");
948 if (imageWithTagsFullPath != null) {
949 linkToThumbnail.append(
"<a href=\"").append(contentPath).append(
"\" target=\"_top\">View Original</a><br>");
952 if (!contentTags.isEmpty()) {
953 linkToThumbnail.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.thumbLink.tags"));
955 for (
int i = 0; i < contentTags.size(); i++) {
959 if (i != contentTags.size() - 1) {
960 linkToThumbnail.append(
", ");
964 linkToThumbnail.append(
"</div>");
965 currentRow.add(linkToThumbnail.toString());
970 if (currentRow.isEmpty() ==
false) {
971 int extraCells = THUMBNAIL_COLUMNS - currentRow.size();
972 for (
int i = 0; i < extraCells; i++) {
976 addRow(currentRow,
false);
980 rowCount = totalCount;
998 StringBuilder localFilePath =
new StringBuilder();
1000 localFilePath.append(subPath);
1001 localFilePath.append(dirName2);
1002 File localFileFolder =
new File(localFilePath.toString());
1003 if (!localFileFolder.exists()) {
1004 localFileFolder.mkdirs();
1015 String objectIdSuffix =
"_" + file.
getId();
1016 int lastDotIndex = fileName.lastIndexOf(
".");
1017 if (lastDotIndex != -1 && lastDotIndex != 0) {
1019 fileName = fileName.substring(0, lastDotIndex) + objectIdSuffix + fileName.substring(lastDotIndex, fileName.length());
1023 fileName += objectIdSuffix;
1025 localFilePath.append(File.separator);
1026 localFilePath.append(fileName);
1028 return localFilePath.toString();
1046 File localFile =
new File(localFilePath);
1047 if (!localFile.exists()) {
1052 return localFilePath.substring(subPath.length());
1064 SimpleDateFormat sdf =
new java.text.SimpleDateFormat(
"yyyy/MM/dd HH:mm:ss");
1065 return sdf.format(
new java.util.Date(date * 1000));
1070 return "report.html";
1075 return NbBundle.getMessage(this.getClass(),
"ReportHTML.getName.text");
1080 return NbBundle.getMessage(this.getClass(),
"ReportHTML.getDesc.text");
1087 Writer cssOut = null;
1089 cssOut =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(subPath +
"index.css"),
"UTF-8"));
1090 String css =
"body {margin: 0px; padding: 0px; background: #FFFFFF; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353;}\n"
1092 "#content {padding: 30px;}\n"
1094 "#header {width:100%; padding: 10px; line-height: 25px; background: #07A; color: #FFF; font-size: 20px;}\n"
1096 "#pageHeaderFooter {width: 100%; padding: 10px; line-height: 25px; text-align: center; font-size: 20px;}\n"
1098 "h1 {font-size: 20px; font-weight: normal; color: #07A; padding: 0 0 7px 0; margin-top: 25px; border-bottom: 1px solid #D6D6D6;}\n"
1100 "h2 {font-size: 20px; font-weight: bolder; color: #07A;}\n"
1102 "h3 {font-size: 16px; color: #07A;}\n"
1104 "h4 {background: #07A; color: #FFF; font-size: 16px; margin: 0 0 0 25px; padding: 0; padding-left: 15px;}\n"
1106 "ul.nav {list-style-type: none; line-height: 35px; padding: 0px; margin-left: 15px;}\n"
1108 "ul li a {font-size: 14px; color: #444; text-decoration: none; padding-left: 25px;}\n"
1110 "ul li a:hover {text-decoration: underline;}\n"
1112 "p {margin: 0 0 20px 0;}\n"
1114 "table {white-space:nowrap; min-width: 700px; padding: 2; margin: 0; border-collapse: collapse; border-bottom: 2px solid #e5e5e5;}\n"
1116 ".keyword_list table {margin: 0 0 25px 25px; border-bottom: 2px solid #dedede;}\n"
1118 "table th {white-space:nowrap; display: table-cell; text-align: center; padding: 2px 4px; background: #e5e5e5; color: #777; font-size: 11px; text-shadow: #e9f9fd 0 1px 0; border-top: 1px solid #dedede; border-bottom: 2px solid #e5e5e5;}\n"
1120 "table .left_align_cell{display: table-cell; padding: 2px 4px; font: 13px/20px Arial, Helvetica, sans-serif; min-width: 125px; overflow: auto; text-align: left; }\n"
1122 "table .right_align_cell{display: table-cell; padding: 2px 4px; font: 13px/20px Arial, Helvetica, sans-serif; min-width: 125px; overflow: auto; text-align: right; }\n"
1124 "table td {white-space:nowrap; display: table-cell; padding: 2px 3px; font: 13px/20px Arial, Helvetica, sans-serif; min-width: 125px; overflow: auto; text-align:left; vertical-align: text-top;}\n"
1126 "table tr:nth-child(even) td {background: #f3f3f3;}\n"
1128 "div#thumbnail_link {max-width: 200px; white-space: pre-wrap; white-space: -moz-pre-wrap; white-space: -pre-wrap; white-space: -o-pre-wrap; word-wrap: break-word;}";
1130 }
catch (FileNotFoundException ex) {
1131 logger.log(Level.SEVERE,
"Could not find index.css file to write to.", ex);
1132 }
catch (UnsupportedEncodingException ex) {
1133 logger.log(Level.SEVERE,
"Did not recognize encoding when writing index.css.", ex);
1134 }
catch (IOException ex) {
1135 logger.log(Level.SEVERE,
"Error creating Writer for index.css.", ex);
1138 if (cssOut != null) {
1142 }
catch (IOException ex) {
1151 Writer indexOut = null;
1152 String indexFilePath = path +
"report.html";
1157 logger.log(Level.SEVERE,
"Exception while getting open case.", ex);
1161 indexOut =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(indexFilePath),
"UTF-8"));
1162 StringBuilder index =
new StringBuilder();
1165 if (iconPath == null) {
1167 iconPath = HTML_SUBDIR +
"favicon.ico";
1169 iconPath = Paths.get(reportBranding.
getAgencyLogoPath()).getFileName().toString();
1171 index.append(
"<head>\n<title>").append(reportTitle).append(
" ").append(
1172 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeIndex.title", currentCase.
getDisplayName())).append(
1174 index.append(
"<link rel=\"icon\" type=\"image/ico\" href=\"")
1175 .append(iconPath).append(
"\" />\n");
1176 index.append(
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n");
1177 index.append(
"</head>\n");
1178 index.append(
"<frameset cols=\"350px,*\">\n");
1179 index.append(
"<frame src=\"" + HTML_SUBDIR).append(File.separator).append(
"nav.html\" name=\"nav\">\n");
1180 index.append(
"<frame src=\"" + HTML_SUBDIR).append(File.separator).append(
"summary.html\" name=\"content\">\n");
1181 index.append(
"<noframes>").append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeIndex.noFrames.msg")).append(
"<br />\n");
1182 index.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeIndex.noFrames.seeNav")).append(
"<br />\n");
1183 index.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeIndex.seeSum")).append(
"</noframes>\n");
1184 index.append(
"</frameset>\n");
1185 index.append(
"</html>");
1186 indexOut.write(index.toString());
1187 openCase.
addReport(indexFilePath, NbBundle.getMessage(
this.getClass(),
1188 "ReportHTML.writeIndex.srcModuleName.text"),
"");
1189 }
catch (IOException ex) {
1190 logger.log(Level.SEVERE,
"Error creating Writer for report.html: {0}", ex);
1192 String errorMessage = String.format(
"Error adding %s to case as a report", indexFilePath);
1193 logger.log(Level.SEVERE, errorMessage, ex);
1196 if (indexOut != null) {
1200 }
catch (IOException ex) {
1209 Writer navOut = null;
1211 navOut =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(subPath +
"nav.html"),
"UTF-8"));
1212 StringBuilder nav =
new StringBuilder();
1213 nav.append(
"<html>\n<head>\n\t<title>").append(
1214 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeNav.title"))
1215 .append(
"</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"index.css\" />\n");
1216 nav.append(
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n</head>\n<body>\n");
1217 nav.append(
"<div id=\"content\">\n<h1>").append(
1218 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeNav.h1")).append(
"</h1>\n");
1219 nav.append(
"<ul class=\"nav\">\n");
1220 nav.append(
"<li style=\"background: url(summary.png) left center no-repeat;\"><a href=\"summary.html\" target=\"content\">")
1221 .append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeNav.summary")).append(
"</a></li>\n");
1223 for (String dataType : dataTypes.keySet()) {
1226 nav.append(
"<li style=\"background: url('").append(iconFileName)
1227 .append(
"') left center no-repeat;\"><a href=\"")
1228 .append(dataTypeEsc).append(
".html\" target=\"content\">")
1229 .append(dataType).append(
" (").append(dataTypes.get(dataType))
1230 .append(
")</a></li>\n");
1232 nav.append(
"</ul>\n");
1233 nav.append(
"</div>\n</body>\n</html>");
1234 navOut.write(nav.toString());
1235 }
catch (IOException ex) {
1236 logger.log(Level.SEVERE,
"Failed to write end of report navigation menu: {0}", ex);
1238 if (navOut != null) {
1242 }
catch (IOException ex) {
1243 logger.log(Level.WARNING,
"Could not close navigation out writer.");
1248 InputStream in = null;
1249 OutputStream output = null;
1254 if (generatorLogoPath != null && !generatorLogoPath.isEmpty()) {
1255 File from =
new File(generatorLogoPath);
1256 File to =
new File(subPath);
1257 FileUtil.copyFile(FileUtil.toFileObject(from), FileUtil.toFileObject(to),
"generator_logo");
1261 if (agencyLogoPath != null && !agencyLogoPath.isEmpty()) {
1262 Path destinationPath = Paths.get(subPath);
1263 Files.copy(Files.newInputStream(Paths.get(agencyLogoPath)), destinationPath.resolve(Paths.get(agencyLogoPath).getFileName()));
1266 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/favicon.ico");
1267 output =
new FileOutputStream(
new File(subPath +
"favicon.ico"));
1268 FileUtil.copy(in, output);
1272 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/summary.png");
1273 output =
new FileOutputStream(
new File(subPath +
"summary.png"));
1274 FileUtil.copy(in, output);
1278 }
catch (IOException ex) {
1279 logger.log(Level.SEVERE,
"Failed to extract images for HTML report.", ex);
1281 if (output != null) {
1285 }
catch (IOException ex) {
1291 }
catch (IOException ex) {
1301 Writer output = null;
1303 output =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(subPath +
"summary.html"),
"UTF-8"));
1304 StringBuilder head =
new StringBuilder();
1305 head.append(
"<html>\n<head>\n<title>").append(
1306 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.title")).append(
"</title>\n");
1307 head.append(
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n");
1308 head.append(
"<style type=\"text/css\">\n");
1309 head.append(
"#pageHeaderFooter {width: 100%; padding: 10px; line-height: 25px; text-align: center; font-size: 20px;}\n");
1310 head.append(
"body { padding: 0px; margin: 0px; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353; }\n");
1311 head.append(
"#wrapper { width: 90%; margin: 0px auto; margin-top: 35px; }\n");
1312 head.append(
"h1 { color: #07A; font-size: 36px; line-height: 42px; font-weight: normal; margin: 0px; border-bottom: 1px solid #81B9DB; }\n");
1313 head.append(
"h1 span { color: #F00; display: block; font-size: 16px; font-weight: bold; line-height: 22px;}\n");
1314 head.append(
"h2 { padding: 0 0 3px 0; margin: 0px; color: #07A; font-weight: normal; border-bottom: 1px dotted #81B9DB; }\n");
1315 head.append(
"h3 { padding: 5 0 3px 0; margin: 0px; color: #07A; font-weight: normal; }\n");
1316 head.append(
"table td { padding: 5px 25px 5px 0px; vertical-align:top;}\n");
1317 head.append(
"p.subheadding { padding: 0px; margin: 0px; font-size: 11px; color: #B5B5B5; }\n");
1318 head.append(
".title { width: 660px; margin-bottom: 50px; }\n");
1319 head.append(
".left { float: left; width: 250px; margin-top: 20px; text-align: center; }\n");
1320 head.append(
".left img { max-width: 250px; max-height: 250px; min-width: 200px; min-height: 200px; }\n");
1321 head.append(
".right { float: right; width: 385px; margin-top: 25px; font-size: 14px; }\n");
1322 head.append(
".clear { clear: both; }\n");
1323 head.append(
".info { padding: 10px 0;}\n");
1324 head.append(
".info p { padding: 3px 10px; background: #e5e5e5; color: #777; font-size: 12px; font-weight: bold; text-shadow: #e9f9fd 0 1px 0; border-top: 1px solid #dedede; border-bottom: 2px solid #dedede; }\n");
1325 head.append(
".info table { margin: 10px 25px 10px 25px; }\n");
1326 head.append(
"ul {padding: 0;margin: 0;list-style-type: none;}");
1327 head.append(
"li {padding-bottom: 5px;}");
1328 head.append(
"</style>\n");
1329 head.append(
"</head>\n<body>\n");
1330 output.write(head.toString());
1332 DateFormat datetimeFormat =
new SimpleDateFormat(
"yyyy/MM/dd HH:mm:ss");
1333 Date date =
new Date();
1334 String datetime = datetimeFormat.format(date);
1336 StringBuilder summary =
new StringBuilder();
1337 boolean running =
false;
1347 summary.append(
"<div id=\"wrapper\">\n");
1349 summary.append(
"<h1>").append(reportTitle)
1350 .append(running ? NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.warningMsg") :
"")
1352 summary.append(
"<p class=\"subheadding\">").append(
1353 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.reportGenOn.text", datetime)).append(
"</p>\n");
1354 summary.append(
"<div class=\"title\">\n");
1359 if (generatorLogoSet) {
1360 summary.append(
"<div class=\"left\">\n");
1361 summary.append(
"<img src=\"generator_logo.png\" />\n");
1362 summary.append(
"</div>\n");
1364 summary.append(
"<div class=\"clear\"></div>\n");
1365 if (reportFooter != null) {
1366 summary.append(
"<p class=\"subheadding\">").append(reportFooter).append(
"</p>\n");
1368 summary.append(
"</div>\n");
1370 summary.append(
"</body></html>");
1371 output.write(summary.toString());
1372 }
catch (FileNotFoundException ex) {
1373 logger.log(Level.SEVERE,
"Could not find summary.html file to write to.");
1374 }
catch (UnsupportedEncodingException ex) {
1375 logger.log(Level.SEVERE,
"Did not recognize encoding when writing summary.hmtl.");
1376 }
catch (IOException ex) {
1377 logger.log(Level.SEVERE,
"Error creating Writer for summary.html.");
1379 logger.log(Level.WARNING,
"Unable to get current sleuthkit Case for the HTML report.");
1382 if (output != null) {
1386 }
catch (IOException ex) {
1392 "ReportHTML.writeSum.case=Case:",
1393 "ReportHTML.writeSum.caseNumber=Case Number:",
1394 "ReportHTML.writeSum.caseNumImages=Number of data sources in case:",
1395 "ReportHTML.writeSum.caseNotes=Notes:",
1396 "ReportHTML.writeSum.examiner=Examiner:"
1404 StringBuilder summary =
new StringBuilder();
1410 String caseNumber = currentCase.
getNumber();
1423 summary.append(
"<div class=\"title\">\n");
1424 if (agencyLogoSet) {
1425 summary.append(
"<div class=\"left\">\n");
1426 summary.append(
"<img src=\"");
1427 summary.append(Paths.get(reportBranding.
getAgencyLogoPath()).getFileName().toString());
1428 summary.append(
"\" />\n");
1429 summary.append(
"</div>\n");
1431 final String align = agencyLogoSet ?
"right" :
"left";
1432 summary.append(
"<div class=\"").append(align).append(
"\">\n");
1433 summary.append(
"<table>\n");
1436 summary.append(
"<tr><td>").append(Bundle.ReportHTML_writeSum_case()).append(
"</td><td>")
1439 if (!caseNumber.isEmpty()) {
1440 summary.append(
"<tr><td>").append(Bundle.ReportHTML_writeSum_caseNumber()).append(
"</td><td>")
1444 summary.append(
"<tr><td>").append(Bundle.ReportHTML_writeSum_caseNumImages()).append(
"</td><td>")
1445 .append(imagecount).append(
"</td></tr>\n");
1447 if (!caseNotes.isEmpty()) {
1448 summary.append(
"<tr><td>").append(Bundle.ReportHTML_writeSum_caseNotes()).append(
"</td><td>")
1453 if (!examinerName.isEmpty()) {
1454 summary.append(
"<tr><td>").append(Bundle.ReportHTML_writeSum_examiner()).append(
"</td><td>")
1459 summary.append(
"</table>\n");
1460 summary.append(
"</div>\n");
1461 summary.append(
"<div class=\"clear\"></div>\n");
1462 summary.append(
"</div>\n");
1472 StringBuilder summary =
new StringBuilder();
1473 summary.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.imageInfoHeading"));
1474 summary.append(
"<div class=\"info\">\n");
1477 summary.append(
"<p>").append(c.getName()).append(
"</p>\n");
1478 if (c instanceof
Image) {
1479 Image img = (Image) c;
1481 summary.append(
"<table>\n");
1482 summary.append(
"<tr><td>").append(
1483 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.timezone"))
1484 .append(
"</td><td>").append(img.
getTimeZone()).append(
"</td></tr>\n");
1485 for (String imgPath : img.
getPaths()) {
1486 summary.append(
"<tr><td>").append(
1487 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.path"))
1488 .append(
"</td><td>").append(imgPath).append(
"</td></tr>\n");
1490 summary.append(
"</table>\n");
1494 logger.log(Level.WARNING,
"Unable to get image information for the HTML report.");
1496 summary.append(
"</div>\n");
1506 StringBuilder summary =
new StringBuilder();
1507 summary.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.softwareInfoHeading"));
1508 summary.append(
"<div class=\"info\">\n");
1509 summary.append(
"<table>\n");
1510 summary.append(
"<tr><td>").append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.autopsyVersion"))
1512 Map<Long, IngestModuleInfo> moduleInfoHashMap =
new HashMap<>();
1514 List<IngestModuleInfo> ingestModules = ingestJob.getIngestModuleInfo();
1516 if (!moduleInfoHashMap.containsKey(ingestModule.getIngestModuleId())) {
1517 moduleInfoHashMap.put(ingestModule.getIngestModuleId(), ingestModule);
1521 TreeMap<String, String> modules =
new TreeMap<>();
1523 modules.put(moduleinfo.getDisplayName(), moduleinfo.getVersion());
1525 for (Map.Entry<String, String> module : modules.entrySet()) {
1526 summary.append(
"<tr><td>").append(module.getKey()).append(
" Module:")
1527 .append(
"</td><td>").append(module.getValue()).append(
"</td></tr>\n");
1529 summary.append(
"</table>\n");
1530 summary.append(
"</div>\n");
1531 summary.append(
"<div class=\"clear\"></div>\n");
1541 StringBuilder summary =
new StringBuilder();
1543 summary.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.ingestHistoryHeading"));
1544 summary.append(
"<div class=\"info\">\n");
1548 summary.append(
"<h3>Job ").append(jobnumber).append(
":</h3>\n");
1549 summary.append(
"<table>\n");
1550 summary.append(
"<tr><td>").append(
"Data Source:")
1551 .append(
"</td><td>").append(skCase.
getContentById(ingestJob.getObjectId()).
getName()).append(
"</td></tr>\n");
1552 summary.append(
"<tr><td>").append(
"Status:")
1553 .append(
"</td><td>").append(ingestJob.getStatus()).append(
"</td></tr>\n");
1554 summary.append(
"<tr><td>").append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.modulesEnabledHeading"))
1555 .append(
"</td><td>");
1556 List<IngestModuleInfo> ingestModules = ingestJob.getIngestModuleInfo();
1557 summary.append(
"<ul>\n");
1559 summary.append(
"<li>").append(ingestModule.getDisplayName()).append(
"</li>");
1561 summary.append(
"</ul>\n");
1563 summary.append(
"</td></tr>\n");
1564 summary.append(
"</table>\n");
1566 summary.append(
"</div>\n");
1568 logger.log(Level.WARNING,
"Unable to get ingest jobs for the HTML report.");
1590 File thumbFile = Paths.get(thumbsPath, fileName +
".png").toFile();
1591 if (bufferedThumb == null) {
1595 ImageIO.write(bufferedThumb,
"png", thumbFile);
1596 }
catch (IOException ex) {
1597 logger.log(Level.WARNING,
"Failed to write thumb file to report directory.", ex);
1600 if (thumbFile.exists()
1604 return THUMBS_REL_PATH
1605 + thumbFile.getName();
1617 String formattedString = StringEscapeUtils.escapeHtml4(text);
1618 return formattedString.replaceAll(
"(\r\n|\r|\n|\n\r)",
"<br>");
List< Content > getDataSources()
static String escapeHtml(String toEscape)
String prepareThumbnail(AbstractFile file)
void startContentTagsTable(List< String > columnHeaders)
ReportModuleSettings getConfiguration()
String getAgencyLogoPath()
TskData.TagType getTagType()
List< ImageTagRegion > getTaggedRegions(List< ContentTag > contentTags)
String getGeneratorLogoPath()
static synchronized IngestManager getInstance()
void addRow(List< String > row, boolean escapeText)
static HTMLReport instance
TskData.TSK_DB_FILES_TYPE_ENUM getType()
String useDataTypeIcon(String dataType)
static final String HTML_SUBDIR
JPanel getConfigurationPanel()
Content getContentById(long id)
void addSetIndex(List< String > sets)
void setConfiguration(ReportModuleSettings settings)
String getRelativeFilePath()
boolean isIngestRunning()
StringBuilder writeSummaryImageInfo()
void addReport(String localPath, String srcModuleName, String reportName)
static BufferedImage getImageWithTags(AbstractFile file, Collection< ImageTagRegion > tagRegions)
static final String THUMBS_REL_PATH
void addThumbnailRows(Set< Content > images)
static final Logger logger
HTMLReportConfigurationPanel configPanel
void addRow(List< String > row)
static< T > ContentViewerTag< T > getTag(ContentTag contentTag, Class< T > clazz)
static BufferedImage getThumbnailWithTags(AbstractFile file, Collection< ImageTagRegion > tagRegions, IconSize iconSize)
Map< String, Integer > dataTypes
void startDataType(String name, String description)
void addRowWithTaggedContentHyperlink(List< String > row, ContentTag contentTag)
static final int ICON_SIZE_MEDIUM
static synchronized HTMLReport getDefault()
TagsManager getTagsManager()
String dataTypeToFileName(String dataType)
StringBuilder writeSummaryIngestHistoryInfo(SleuthkitCase skCase, List< IngestJobInfo > ingestJobs)
ReportModuleSettings getDefaultConfiguration()
StringBuilder writeSummaryCaseDetails()
String dateToString(long date)
void addSetElement(String elementName)
String formatHtmlString(String text)
boolean failsContentCheck(Content c)
void close(ProgressIndicator progressIndicator)
SleuthkitCase getSleuthkitCase()
static Integer THUMBNAIL_COLUMNS
static final int MAX_THUMBS_PER_PAGE
final ReportBranding reportBranding
static String escapeFileName(String fileName)
StringBuilder writeSummarySoftwareInfo(SleuthkitCase skCase, List< IngestJobInfo > ingestJobs)
void startTable(List< String > titles)
static Case getCurrentCase()
synchronized static Logger getLogger(String name)
String makeCustomUniqueFilePath(AbstractFile file, String dirName)
void startReport(String baseReportDir)
static Case getCurrentCaseThrows()
String saveContent(AbstractFile file, String dirName)
final List< IngestJobInfo > getIngestJobs()
static BufferedImage getThumbnail(Content content, int iconSize)
static String getVersion()
void startSet(String setName)