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.lang3.StringEscapeUtils;
56 import org.openide.filesystems.FileUtil;
57 import org.openide.util.NbBundle;
58 import org.openide.util.NbBundle.Messages;
76 import org.
sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
85 import org.
sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM;
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(
" ",
"_");
220 OutputStream output = null;
222 logger.log(Level.INFO,
"useDataTypeIcon: dataType = {0}", dataType);
225 BlackboardArtifact.ARTIFACT_TYPE artifactType = null;
226 for (ARTIFACT_TYPE v : ARTIFACT_TYPE.values()) {
227 if (v.getDisplayName().equals(dataType)) {
232 if (null != artifactType) {
235 iconFilePath = subPath + File.separator + iconFileName;
238 switch (artifactType) {
239 case TSK_WEB_BOOKMARK:
240 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/bookmarks.png");
243 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/cookies.png");
245 case TSK_WEB_HISTORY:
246 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/history.png");
248 case TSK_WEB_DOWNLOAD:
249 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/downloads.png");
251 case TSK_RECENT_OBJECT:
252 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/recent.png");
254 case TSK_INSTALLED_PROG:
255 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/installed.png");
257 case TSK_KEYWORD_HIT:
258 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/keywords.png");
260 case TSK_HASHSET_HIT:
261 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/hash.png");
263 case TSK_DEVICE_ATTACHED:
264 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/devices.png");
266 case TSK_WEB_SEARCH_QUERY:
267 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/search.png");
269 case TSK_METADATA_EXIF:
270 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/exif.png");
273 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/userbookmarks.png");
275 case TSK_TAG_ARTIFACT:
276 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/userbookmarks.png");
278 case TSK_SERVICE_ACCOUNT:
279 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/account-icon-16.png");
282 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/contact.png");
285 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/message.png");
288 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/calllog.png");
290 case TSK_CALENDAR_ENTRY:
291 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/calendar.png");
293 case TSK_SPEED_DIAL_ENTRY:
294 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/speeddialentry.png");
296 case TSK_BLUETOOTH_PAIRING:
297 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/bluetooth.png");
299 case TSK_GPS_BOOKMARK:
300 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gpsfav.png");
302 case TSK_GPS_LAST_KNOWN_LOCATION:
303 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gps-lastlocation.png");
306 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gps-search.png");
309 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/computer.png");
311 case TSK_GPS_TRACKPOINT:
312 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gps_trackpoint.png");
315 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gps_trackpoint.png");
318 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/mail-icon-16.png");
320 case TSK_ENCRYPTION_SUSPECTED:
321 case TSK_ENCRYPTION_DETECTED:
322 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/encrypted-file.png");
324 case TSK_EXT_MISMATCH_DETECTED:
325 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/mismatch-16.png");
327 case TSK_INTERESTING_ARTIFACT_HIT:
328 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/interesting_item.png");
330 case TSK_INTERESTING_FILE_HIT:
331 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/interesting_item.png");
334 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/installed.png");
336 case TSK_REMOTE_DRIVE:
337 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/drive_network.png");
340 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/os-account.png");
342 case TSK_OBJECT_DETECTED:
343 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/objects.png");
345 case TSK_WEB_FORM_AUTOFILL:
346 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/web-form.png");
349 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/cache.png");
351 case TSK_USER_CONTENT_SUSPECTED:
352 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/user-content.png");
355 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/metadata.png");
357 case TSK_CLIPBOARD_CONTENT:
358 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/clipboard.png");
361 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/accounts.png");
363 case TSK_WIFI_NETWORK:
364 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/network-wifi.png");
366 case TSK_WIFI_NETWORK_ADAPTER:
367 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/network-wifi.png");
369 case TSK_SIM_ATTACHED:
370 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/sim_card.png");
372 case TSK_BLUETOOTH_ADAPTER:
373 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/Bluetooth.png");
375 case TSK_DEVICE_INFO:
376 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/devices.png");
378 case TSK_VERIFICATION_FAILED:
379 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/validationFailed.png");
381 case TSK_WEB_ACCOUNT_TYPE:
382 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/web-account-type.png.png");
385 logger.log(Level.WARNING,
"useDataTypeIcon: unhandled artifact type = {0}", dataType);
386 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/star.png");
387 iconFileName =
"star.png";
388 iconFilePath = subPath + File.separator + iconFileName;
391 }
else if (dataType.startsWith(ARTIFACT_TYPE.TSK_ACCOUNT.getDisplayName())) {
399 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/accounts.png");
400 iconFileName =
"accounts.png";
401 iconFilePath = subPath + File.separator + iconFileName;
403 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/star.png");
404 iconFileName =
"star.png";
405 iconFilePath = subPath + File.separator + iconFileName;
409 output =
new FileOutputStream(iconFilePath);
410 FileUtil.copy(in, output);
413 }
catch (IOException ex) {
414 logger.log(Level.SEVERE,
"Failed to extract images for HTML report.", ex);
416 if (output != null) {
420 }
catch (IOException ex) {
426 }
catch (IOException ex) {
447 logger.log(Level.SEVERE,
"Exception while getting open case.");
451 this.path = baseReportDir;
452 this.subPath = this.path + HTML_SUBDIR + File.separator;
455 FileUtil.createFolder(
new File(this.subPath));
456 FileUtil.createFolder(
new File(this.thumbsPath));
457 }
catch (IOException ex) {
458 logger.log(Level.SEVERE,
"Unable to make HTML report folder.");
476 }
catch (IOException ex) {
477 logger.log(Level.WARNING,
"Could not close the output writer when ending report.", ex);
494 out =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(subPath + title +
".html"),
"UTF-8"));
495 }
catch (FileNotFoundException ex) {
496 logger.log(Level.SEVERE,
"File not found: {0}", ex);
497 }
catch (UnsupportedEncodingException ex) {
498 logger.log(Level.SEVERE,
"Unrecognized encoding");
502 StringBuilder page =
new StringBuilder();
503 page.append(
"<html>\n<head>\n\t<title>").append(name).append(
"</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"index.css\" />\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n</head>\n<body>\n")
505 .append(
"<div id=\"header\">").append(name).append(
"</div>\n")
506 .append(
"<div id=\"content\">\n");
507 if (!description.isEmpty()) {
508 page.append(
"<p><strong>");
509 page.append(description);
510 page.append(
"</strong></p>\n");
512 out.write(page.toString());
513 currentDataType = name;
515 }
catch (IOException ex) {
516 logger.log(Level.SEVERE,
"Failed to write page head: {0}", ex);
526 dataTypes.put(currentDataType, rowCount);
528 StringBuilder builder =
new StringBuilder();
530 builder.append(
"</div>\n</body>\n</html>\n");
531 out.write(builder.toString());
532 }
catch (IOException ex) {
533 logger.log(Level.SEVERE,
"Failed to write end of HTML report.", ex);
539 }
catch (IOException ex) {
540 logger.log(Level.WARNING,
"Could not close the output writer when ending data type.", ex);
554 StringBuilder output =
new StringBuilder();
555 String pageHeader = configPanel.getHeader();
556 if (pageHeader.isEmpty() ==
false) {
557 output.append(
"<div id=\"pageHeaderFooter\">")
558 .append(StringEscapeUtils.escapeHtml4(pageHeader))
561 return output.toString();
571 StringBuilder output =
new StringBuilder();
572 String pageFooter = configPanel.getFooter();
573 if (pageFooter.isEmpty() ==
false) {
574 output.append(
"<br/><div id=\"pageHeaderFooter\">")
575 .append(StringEscapeUtils.escapeHtml4(pageFooter))
578 return output.toString();
588 StringBuilder set =
new StringBuilder();
589 set.append(
"<h1><a name=\"").append(setName).append(
"\">").append(setName).append(
"</a></h1>\n");
590 set.append(
"<div class=\"keyword_list\">\n");
593 out.write(set.toString());
594 }
catch (IOException ex) {
595 logger.log(Level.SEVERE,
"Failed to write set: {0}", ex);
605 out.write(
"</div>\n");
606 }
catch (IOException ex) {
607 logger.log(Level.SEVERE,
"Failed to write end of set: {0}", ex);
618 StringBuilder index =
new StringBuilder();
619 index.append(
"<ul>\n");
620 for (String set : sets) {
621 index.append(
"\t<li><a href=\"#").append(set).append(
"\">").append(set).append(
"</a></li>\n");
623 index.append(
"</ul>\n");
625 out.write(index.toString());
626 }
catch (IOException ex) {
627 logger.log(Level.SEVERE,
"Failed to add set index: {0}", ex);
639 out.write(
"<h4>" + elementName +
"</h4>\n");
640 }
catch (IOException ex) {
641 logger.log(Level.SEVERE,
"Failed to write set element: {0}", ex);
652 StringBuilder ele =
new StringBuilder();
653 ele.append(
"<table>\n<thead>\n\t<tr>\n");
654 for (String title : titles) {
655 ele.append(
"\t\t<th>").append(title).append(
"</th>\n");
657 ele.append(
"\t</tr>\n</thead>\n");
660 out.write(ele.toString());
661 }
catch (IOException ex) {
662 logger.log(Level.SEVERE,
"Failed to write table start: {0}", ex);
673 StringBuilder htmlOutput =
new StringBuilder();
674 htmlOutput.append(
"<table>\n<thead>\n\t<tr>\n");
677 for (String columnHeader : columnHeaders) {
678 htmlOutput.append(
"\t\t<th>").append(columnHeader).append(
"</th>\n");
682 htmlOutput.append(
"\t\t<th></th>\n");
684 htmlOutput.append(
"\t</tr>\n</thead>\n");
687 out.write(htmlOutput.toString());
688 }
catch (IOException ex) {
689 logger.log(Level.SEVERE,
"Failed to write table start: {0}", ex);
699 out.write(
"</table>\n");
700 }
catch (IOException ex) {
701 logger.log(Level.SEVERE,
"Failed to write end of table: {0}", ex);
723 private void addRow(List<String> row,
boolean escapeText) {
724 StringBuilder builder =
new StringBuilder();
725 builder.append(
"\t<tr>\n");
726 for (String cell : row) {
728 builder.append(
"\t\t<td>").append(cellText).append(
"</td>\n");
730 builder.append(
"\t</tr>\n");
734 out.write(builder.toString());
735 }
catch (IOException ex) {
736 logger.log(Level.SEVERE,
"Failed to write row to out.", ex);
737 }
catch (NullPointerException ex) {
738 logger.log(Level.SEVERE,
"Output writer is null. Page was not initialized before writing.", ex);
750 Content content = contentTag.getContent();
751 if (content instanceof AbstractFile ==
false) {
755 AbstractFile file = (AbstractFile) content;
757 StringBuilder localFileLink =
new StringBuilder();
760 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS
761 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS)) {
762 localFileLink.append(
"<a href=\"");
764 String localFilePath =
saveContent(file, contentTag.getName().getDisplayName());
765 localFileLink.append(localFilePath);
766 localFileLink.append(
"\" target=\"_top\">");
769 StringBuilder builder =
new StringBuilder();
770 builder.append(
"\t<tr>\n");
771 int positionCounter = 0;
772 for (String cell : row) {
774 switch (positionCounter) {
777 builder.append(
"\t\t<td class=\"left_align_cell\">").append(localFileLink.toString()).append(cell).append(
"</a></td>\n");
781 builder.append(
"\t\t<td class=\"right_align_cell\">").append(cell).append(
"</td>\n");
785 builder.append(
"\t\t<td>").append(cell).append(
"</td>\n");
790 builder.append(
"\t</tr>\n");
794 out.write(builder.toString());
795 }
catch (IOException ex) {
796 logger.log(Level.SEVERE,
"Failed to write row to out.", ex);
797 }
catch (NullPointerException ex) {
798 logger.log(Level.SEVERE,
"Output writer is null. Page was not initialized before writing.", ex);
809 ArrayList<ImageTagRegion> tagRegions =
new ArrayList<>();
810 contentTags.forEach((contentTag) -> {
814 if (contentViewerTag != null) {
815 tagRegions.add(contentViewerTag.
getDetails());
818 logger.log(Level.WARNING,
"Could not get content viewer tag "
819 +
"from case db for content_tag with id %d", contentTag.getId());
831 List<String> currentRow =
new ArrayList<>();
834 for (Content content : images) {
836 addRow(currentRow,
false);
840 if (totalCount == MAX_THUMBS_PER_PAGE) {
844 rowCount = totalCount;
849 startDataType(NbBundle.getMessage(
this.getClass(),
"ReportHTML.addThumbRows.dataType.title", pages),
850 NbBundle.getMessage(
this.getClass(),
"ReportHTML.addThumbRows.dataType.msg"));
851 List<String> emptyHeaders =
new ArrayList<>();
853 emptyHeaders.add(
"");
862 AbstractFile file = (AbstractFile) content;
863 List<ContentTag> contentTags =
new ArrayList<>();
865 String thumbnailPath = null;
866 String imageWithTagsFullPath = null;
873 if (!imageTags.isEmpty()) {
883 File thumbnailImageWithTagsFile = Paths.get(thumbsPath, FilenameUtils.removeExtension(fileName) +
".png").toFile();
885 fullImageWithTagsPath = FilenameUtils.removeExtension(fullImageWithTagsPath) +
".png";
886 File fullImageWithTagsFile = Paths.get(fullImageWithTagsPath).toFile();
889 ImageIO.write(thumbnailWithTags,
"png", thumbnailImageWithTagsFile);
890 ImageIO.write(fullImageWithTags,
"png", fullImageWithTagsFile);
892 thumbnailPath = THUMBS_REL_PATH + thumbnailImageWithTagsFile.getName();
894 imageWithTagsFullPath = fullImageWithTagsPath.substring(subPath.length());
896 }
catch (TskCoreException ex) {
897 logger.log(Level.WARNING,
"Could not get tags for file.", ex);
898 }
catch (IOException | InterruptedException | ExecutionException ex) {
899 logger.log(Level.WARNING,
"Could make marked up thumbnail.", ex);
903 if (thumbnailPath == null) {
907 if (thumbnailPath == null) {
910 String contentPath =
saveContent(file,
"original");
913 nameInImage = file.getUniquePath();
914 }
catch (TskCoreException ex) {
915 nameInImage = file.getName();
918 StringBuilder linkToThumbnail =
new StringBuilder();
919 linkToThumbnail.append(
"<div id='thumbnail_link'><a href=\"")
920 .append((imageWithTagsFullPath != null) ? imageWithTagsFullPath : contentPath)
921 .append(
"\" target=\"_top\"><img src=\"")
922 .append(thumbnailPath).append(
"\" title=\"").append(nameInImage).append(
"\"/></a><br>")
923 .append(file.getName()).append(
"<br>");
924 if (imageWithTagsFullPath != null) {
925 linkToThumbnail.append(
"<a href=\"").append(contentPath).append(
"\" target=\"_top\">View Original</a><br>");
928 if (!contentTags.isEmpty()) {
929 linkToThumbnail.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.thumbLink.tags"));
931 for (
int i = 0; i < contentTags.size(); i++) {
932 ContentTag tag = contentTags.get(i);
934 linkToThumbnail.append(tag.getName().getDisplayName()).append(notableString);
935 if (i != contentTags.size() - 1) {
936 linkToThumbnail.append(
", ");
940 linkToThumbnail.append(
"</div>");
941 currentRow.add(linkToThumbnail.toString());
946 if (currentRow.isEmpty() ==
false) {
947 int extraCells = THUMBNAIL_COLUMNS - currentRow.size();
948 for (
int i = 0; i < extraCells; i++) {
952 addRow(currentRow,
false);
956 rowCount = totalCount;
960 if (c instanceof AbstractFile ==
false) {
963 AbstractFile file = (AbstractFile) c;
965 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS
966 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS;
974 StringBuilder localFilePath =
new StringBuilder();
976 localFilePath.append(subPath);
977 localFilePath.append(dirName2);
978 File localFileFolder =
new File(localFilePath.toString());
979 if (!localFileFolder.exists()) {
980 localFileFolder.mkdirs();
991 String objectIdSuffix =
"_" + file.getId();
992 int lastDotIndex = fileName.lastIndexOf(
".");
993 if (lastDotIndex != -1 && lastDotIndex != 0) {
995 fileName = fileName.substring(0, lastDotIndex) + objectIdSuffix + fileName.substring(lastDotIndex, fileName.length());
999 fileName += objectIdSuffix;
1001 localFilePath.append(File.separator);
1002 localFilePath.append(fileName);
1004 return localFilePath.toString();
1022 File localFile =
new File(localFilePath);
1023 if (!localFile.exists()) {
1028 return localFilePath.substring(subPath.length());
1040 SimpleDateFormat sdf =
new java.text.SimpleDateFormat(
"yyyy/MM/dd HH:mm:ss");
1041 return sdf.format(
new java.util.Date(date * 1000));
1046 return "report.html";
1051 return NbBundle.getMessage(this.getClass(),
"ReportHTML.getName.text");
1056 return NbBundle.getMessage(this.getClass(),
"ReportHTML.getDesc.text");
1063 Writer cssOut = null;
1065 cssOut =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(subPath +
"index.css"),
"UTF-8"));
1066 String css =
"body {margin: 0px; padding: 0px; background: #FFFFFF; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353;}\n"
1068 "#content {padding: 30px;}\n"
1070 "#header {width:100%; padding: 10px; line-height: 25px; background: #07A; color: #FFF; font-size: 20px;}\n"
1072 "#pageHeaderFooter {width: 100%; padding: 10px; line-height: 25px; text-align: center; font-size: 20px;}\n"
1074 "h1 {font-size: 20px; font-weight: normal; color: #07A; padding: 0 0 7px 0; margin-top: 25px; border-bottom: 1px solid #D6D6D6;}\n"
1076 "h2 {font-size: 20px; font-weight: bolder; color: #07A;}\n"
1078 "h3 {font-size: 16px; color: #07A;}\n"
1080 "h4 {background: #07A; color: #FFF; font-size: 16px; margin: 0 0 0 25px; padding: 0; padding-left: 15px;}\n"
1082 "ul.nav {list-style-type: none; line-height: 35px; padding: 0px; margin-left: 15px;}\n"
1084 "ul li a {font-size: 14px; color: #444; text-decoration: none; padding-left: 25px;}\n"
1086 "ul li a:hover {text-decoration: underline;}\n"
1088 "p {margin: 0 0 20px 0;}\n"
1090 "table {white-space:nowrap; min-width: 700px; padding: 2; margin: 0; border-collapse: collapse; border-bottom: 2px solid #e5e5e5;}\n"
1092 ".keyword_list table {margin: 0 0 25px 25px; border-bottom: 2px solid #dedede;}\n"
1094 "table th {white-space:nowrap; display: table-cell; text-align: center; padding: 2px 4px; background: #e5e5e5; color: #777; font-size: 11px; text-shadow: #e9f9fd 0 1px 0; border-top: 1px solid #dedede; border-bottom: 2px solid #e5e5e5;}\n"
1096 "table .left_align_cell{display: table-cell; padding: 2px 4px; font: 13px/20px Arial, Helvetica, sans-serif; min-width: 125px; overflow: auto; text-align: left; }\n"
1098 "table .right_align_cell{display: table-cell; padding: 2px 4px; font: 13px/20px Arial, Helvetica, sans-serif; min-width: 125px; overflow: auto; text-align: right; }\n"
1100 "table td {white-space:nowrap; display: table-cell; padding: 2px 3px; font: 13px/20px Arial, Helvetica, sans-serif; min-width: 125px; overflow: auto; text-align:left; vertical-align: text-top;}\n"
1102 "table tr:nth-child(even) td {background: #f3f3f3;}\n"
1104 "div#thumbnail_link {max-width: 200px; white-space: pre-wrap; white-space: -moz-pre-wrap; white-space: -pre-wrap; white-space: -o-pre-wrap; word-wrap: break-word;}";
1106 }
catch (FileNotFoundException ex) {
1107 logger.log(Level.SEVERE,
"Could not find index.css file to write to.", ex);
1108 }
catch (UnsupportedEncodingException ex) {
1109 logger.log(Level.SEVERE,
"Did not recognize encoding when writing index.css.", ex);
1110 }
catch (IOException ex) {
1111 logger.log(Level.SEVERE,
"Error creating Writer for index.css.", ex);
1114 if (cssOut != null) {
1118 }
catch (IOException ex) {
1127 Writer indexOut = null;
1128 String indexFilePath = path +
"report.html";
1133 logger.log(Level.SEVERE,
"Exception while getting open case.", ex);
1137 indexOut =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(indexFilePath),
"UTF-8"));
1138 StringBuilder index =
new StringBuilder();
1141 if (iconPath == null) {
1143 iconPath = HTML_SUBDIR +
"favicon.ico";
1145 iconPath = Paths.get(reportBranding.
getAgencyLogoPath()).getFileName().toString();
1147 index.append(
"<head>\n<title>").append(reportTitle).append(
" ").append(
1148 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeIndex.title", currentCase.
getDisplayName())).append(
1150 index.append(
"<link rel=\"icon\" type=\"image/ico\" href=\"")
1151 .append(iconPath).append(
"\" />\n");
1152 index.append(
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n");
1153 index.append(
"</head>\n");
1154 index.append(
"<frameset cols=\"350px,*\">\n");
1155 index.append(
"<frame src=\"" + HTML_SUBDIR).append(File.separator).append(
"nav.html\" name=\"nav\">\n");
1156 index.append(
"<frame src=\"" + HTML_SUBDIR).append(File.separator).append(
"summary.html\" name=\"content\">\n");
1157 index.append(
"<noframes>").append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeIndex.noFrames.msg")).append(
"<br />\n");
1158 index.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeIndex.noFrames.seeNav")).append(
"<br />\n");
1159 index.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeIndex.seeSum")).append(
"</noframes>\n");
1160 index.append(
"</frameset>\n");
1161 index.append(
"</html>");
1162 indexOut.write(index.toString());
1163 openCase.
addReport(indexFilePath, NbBundle.getMessage(
this.getClass(),
1164 "ReportHTML.writeIndex.srcModuleName.text"),
"");
1165 }
catch (IOException ex) {
1166 logger.log(Level.SEVERE,
"Error creating Writer for report.html: {0}", ex);
1167 }
catch (TskCoreException ex) {
1168 String errorMessage = String.format(
"Error adding %s to case as a report", indexFilePath);
1169 logger.log(Level.SEVERE, errorMessage, ex);
1172 if (indexOut != null) {
1176 }
catch (IOException ex) {
1185 Writer navOut = null;
1187 navOut =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(subPath +
"nav.html"),
"UTF-8"));
1188 StringBuilder nav =
new StringBuilder();
1189 nav.append(
"<html>\n<head>\n\t<title>").append(
1190 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeNav.title"))
1191 .append(
"</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"index.css\" />\n");
1192 nav.append(
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n</head>\n<body>\n");
1193 nav.append(
"<div id=\"content\">\n<h1>").append(
1194 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeNav.h1")).append(
"</h1>\n");
1195 nav.append(
"<ul class=\"nav\">\n");
1196 nav.append(
"<li style=\"background: url(summary.png) left center no-repeat;\"><a href=\"summary.html\" target=\"content\">")
1197 .append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeNav.summary")).append(
"</a></li>\n");
1199 for (String dataType : dataTypes.keySet()) {
1202 nav.append(
"<li style=\"background: url('").append(iconFileName)
1203 .append(
"') left center no-repeat;\"><a href=\"")
1204 .append(dataTypeEsc).append(
".html\" target=\"content\">")
1205 .append(dataType).append(
" (").append(dataTypes.get(dataType))
1206 .append(
")</a></li>\n");
1208 nav.append(
"</ul>\n");
1209 nav.append(
"</div>\n</body>\n</html>");
1210 navOut.write(nav.toString());
1211 }
catch (IOException ex) {
1212 logger.log(Level.SEVERE,
"Failed to write end of report navigation menu: {0}", ex);
1214 if (navOut != null) {
1218 }
catch (IOException ex) {
1219 logger.log(Level.WARNING,
"Could not close navigation out writer.");
1224 InputStream in = null;
1225 OutputStream output = null;
1230 if (generatorLogoPath != null && !generatorLogoPath.isEmpty()) {
1231 File from =
new File(generatorLogoPath);
1232 File to =
new File(subPath);
1233 FileUtil.copyFile(FileUtil.toFileObject(from), FileUtil.toFileObject(to),
"generator_logo");
1237 if (agencyLogoPath != null && !agencyLogoPath.isEmpty()) {
1238 Path destinationPath = Paths.get(subPath);
1239 Files.copy(Files.newInputStream(Paths.get(agencyLogoPath)), destinationPath.resolve(Paths.get(agencyLogoPath).getFileName()));
1242 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/favicon.ico");
1243 output =
new FileOutputStream(
new File(subPath +
"favicon.ico"));
1244 FileUtil.copy(in, output);
1248 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/summary.png");
1249 output =
new FileOutputStream(
new File(subPath +
"summary.png"));
1250 FileUtil.copy(in, output);
1254 }
catch (IOException ex) {
1255 logger.log(Level.SEVERE,
"Failed to extract images for HTML report.", ex);
1257 if (output != null) {
1261 }
catch (IOException ex) {
1267 }
catch (IOException ex) {
1277 Writer output = null;
1279 output =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(subPath +
"summary.html"),
"UTF-8"));
1280 StringBuilder head =
new StringBuilder();
1281 head.append(
"<html>\n<head>\n<title>").append(
1282 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.title")).append(
"</title>\n");
1283 head.append(
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n");
1284 head.append(
"<style type=\"text/css\">\n");
1285 head.append(
"#pageHeaderFooter {width: 100%; padding: 10px; line-height: 25px; text-align: center; font-size: 20px;}\n");
1286 head.append(
"body { padding: 0px; margin: 0px; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353; }\n");
1287 head.append(
"#wrapper { width: 90%; margin: 0px auto; margin-top: 35px; }\n");
1288 head.append(
"h1 { color: #07A; font-size: 36px; line-height: 42px; font-weight: normal; margin: 0px; border-bottom: 1px solid #81B9DB; }\n");
1289 head.append(
"h1 span { color: #F00; display: block; font-size: 16px; font-weight: bold; line-height: 22px;}\n");
1290 head.append(
"h2 { padding: 0 0 3px 0; margin: 0px; color: #07A; font-weight: normal; border-bottom: 1px dotted #81B9DB; }\n");
1291 head.append(
"h3 { padding: 5 0 3px 0; margin: 0px; color: #07A; font-weight: normal; }\n");
1292 head.append(
"table td { padding: 5px 25px 5px 0px; vertical-align:top;}\n");
1293 head.append(
"p.subheadding { padding: 0px; margin: 0px; font-size: 11px; color: #B5B5B5; }\n");
1294 head.append(
".title { width: 660px; margin-bottom: 50px; }\n");
1295 head.append(
".left { float: left; width: 250px; margin-top: 20px; text-align: center; }\n");
1296 head.append(
".left img { max-width: 250px; max-height: 250px; min-width: 200px; min-height: 200px; }\n");
1297 head.append(
".right { float: right; width: 385px; margin-top: 25px; font-size: 14px; }\n");
1298 head.append(
".clear { clear: both; }\n");
1299 head.append(
".info { padding: 10px 0;}\n");
1300 head.append(
".info p { padding: 3px 10px; background: #e5e5e5; color: #777; font-size: 12px; font-weight: bold; text-shadow: #e9f9fd 0 1px 0; border-top: 1px solid #dedede; border-bottom: 2px solid #dedede; }\n");
1301 head.append(
".info table { margin: 10px 25px 10px 25px; }\n");
1302 head.append(
"ul {padding: 0;margin: 0;list-style-type: none;}");
1303 head.append(
"li {padding-bottom: 5px;}");
1304 head.append(
"</style>\n");
1305 head.append(
"</head>\n<body>\n");
1306 output.write(head.toString());
1308 DateFormat datetimeFormat =
new SimpleDateFormat(
"yyyy/MM/dd HH:mm:ss");
1309 Date date =
new Date();
1310 String datetime = datetimeFormat.format(date);
1312 StringBuilder summary =
new StringBuilder();
1313 boolean running =
false;
1318 List<IngestJobInfo> ingestJobs = skCase.getIngestJobs();
1323 summary.append(
"<div id=\"wrapper\">\n");
1325 summary.append(
"<h1>").append(reportTitle)
1326 .append(running ? NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.warningMsg") :
"")
1328 summary.append(
"<p class=\"subheadding\">").append(
1329 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.reportGenOn.text", datetime)).append(
"</p>\n");
1330 summary.append(
"<div class=\"title\">\n");
1335 if (generatorLogoSet) {
1336 summary.append(
"<div class=\"left\">\n");
1337 summary.append(
"<img src=\"generator_logo.png\" />\n");
1338 summary.append(
"</div>\n");
1340 summary.append(
"<div class=\"clear\"></div>\n");
1341 if (reportFooter != null) {
1342 summary.append(
"<p class=\"subheadding\">").append(reportFooter).append(
"</p>\n");
1344 summary.append(
"</div>\n");
1346 summary.append(
"</body></html>");
1347 output.write(summary.toString());
1348 }
catch (FileNotFoundException ex) {
1349 logger.log(Level.SEVERE,
"Could not find summary.html file to write to.");
1350 }
catch (UnsupportedEncodingException ex) {
1351 logger.log(Level.SEVERE,
"Did not recognize encoding when writing summary.hmtl.");
1352 }
catch (IOException ex) {
1353 logger.log(Level.SEVERE,
"Error creating Writer for summary.html.");
1355 logger.log(Level.WARNING,
"Unable to get current sleuthkit Case for the HTML report.");
1358 if (output != null) {
1362 }
catch (IOException ex) {
1368 "ReportHTML.writeSum.case=Case:",
1369 "ReportHTML.writeSum.caseNumber=Case Number:",
1370 "ReportHTML.writeSum.caseNumImages=Number of data sources in case:",
1371 "ReportHTML.writeSum.caseNotes=Notes:",
1372 "ReportHTML.writeSum.examiner=Examiner:"
1380 StringBuilder summary =
new StringBuilder();
1386 String caseNumber = currentCase.
getNumber();
1390 }
catch (TskCoreException ex) {
1399 summary.append(
"<div class=\"title\">\n");
1400 if (agencyLogoSet) {
1401 summary.append(
"<div class=\"left\">\n");
1402 summary.append(
"<img src=\"");
1403 summary.append(Paths.get(reportBranding.
getAgencyLogoPath()).getFileName().toString());
1404 summary.append(
"\" />\n");
1405 summary.append(
"</div>\n");
1407 final String align = agencyLogoSet ?
"right" :
"left";
1408 summary.append(
"<div class=\"").append(align).append(
"\">\n");
1409 summary.append(
"<table>\n");
1412 summary.append(
"<tr><td>").append(Bundle.ReportHTML_writeSum_case()).append(
"</td><td>")
1415 if (!caseNumber.isEmpty()) {
1416 summary.append(
"<tr><td>").append(Bundle.ReportHTML_writeSum_caseNumber()).append(
"</td><td>")
1420 summary.append(
"<tr><td>").append(Bundle.ReportHTML_writeSum_caseNumImages()).append(
"</td><td>")
1421 .append(imagecount).append(
"</td></tr>\n");
1423 if (!caseNotes.isEmpty()) {
1424 summary.append(
"<tr><td>").append(Bundle.ReportHTML_writeSum_caseNotes()).append(
"</td><td>")
1429 if (!examinerName.isEmpty()) {
1430 summary.append(
"<tr><td>").append(Bundle.ReportHTML_writeSum_examiner()).append(
"</td><td>")
1435 summary.append(
"</table>\n");
1436 summary.append(
"</div>\n");
1437 summary.append(
"<div class=\"clear\"></div>\n");
1438 summary.append(
"</div>\n");
1448 StringBuilder summary =
new StringBuilder();
1449 summary.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.imageInfoHeading"));
1450 summary.append(
"<div class=\"info\">\n");
1453 summary.append(
"<p>").append(c.getName()).append(
"</p>\n");
1454 if (c instanceof Image) {
1455 Image img = (Image) c;
1457 summary.append(
"<table>\n");
1458 summary.append(
"<tr><td>").append(
1459 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.timezone"))
1460 .append(
"</td><td>").append(img.getTimeZone()).append(
"</td></tr>\n");
1461 for (String imgPath : img.getPaths()) {
1462 summary.append(
"<tr><td>").append(
1463 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.path"))
1464 .append(
"</td><td>").append(imgPath).append(
"</td></tr>\n");
1466 summary.append(
"</table>\n");
1469 }
catch (TskCoreException ex) {
1470 logger.log(Level.WARNING,
"Unable to get image information for the HTML report.");
1472 summary.append(
"</div>\n");
1482 StringBuilder summary =
new StringBuilder();
1483 summary.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.softwareInfoHeading"));
1484 summary.append(
"<div class=\"info\">\n");
1485 summary.append(
"<table>\n");
1486 summary.append(
"<tr><td>").append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.autopsyVersion"))
1488 Map<Long, IngestModuleInfo> moduleInfoHashMap =
new HashMap<>();
1489 for (IngestJobInfo ingestJob : ingestJobs) {
1490 List<IngestModuleInfo> ingestModules = ingestJob.getIngestModuleInfo();
1491 for (IngestModuleInfo ingestModule : ingestModules) {
1492 if (!moduleInfoHashMap.containsKey(ingestModule.getIngestModuleId())) {
1493 moduleInfoHashMap.put(ingestModule.getIngestModuleId(), ingestModule);
1497 TreeMap<String, String> modules =
new TreeMap<>();
1498 for (IngestModuleInfo moduleinfo : moduleInfoHashMap.values()) {
1499 modules.put(moduleinfo.getDisplayName(), moduleinfo.getVersion());
1501 for (Map.Entry<String, String> module : modules.entrySet()) {
1502 summary.append(
"<tr><td>").append(module.getKey()).append(
" Module:")
1503 .append(
"</td><td>").append(module.getValue()).append(
"</td></tr>\n");
1505 summary.append(
"</table>\n");
1506 summary.append(
"</div>\n");
1507 summary.append(
"<div class=\"clear\"></div>\n");
1517 StringBuilder summary =
new StringBuilder();
1519 summary.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.ingestHistoryHeading"));
1520 summary.append(
"<div class=\"info\">\n");
1523 for (IngestJobInfo ingestJob : ingestJobs) {
1524 summary.append(
"<h3>Job ").append(jobnumber).append(
":</h3>\n");
1525 summary.append(
"<table>\n");
1526 summary.append(
"<tr><td>").append(
"Data Source:")
1527 .append(
"</td><td>").append(skCase.getContentById(ingestJob.getObjectId()).
getName()).append(
"</td></tr>\n");
1528 summary.append(
"<tr><td>").append(
"Status:")
1529 .append(
"</td><td>").append(ingestJob.getStatus()).append(
"</td></tr>\n");
1530 summary.append(
"<tr><td>").append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.modulesEnabledHeading"))
1531 .append(
"</td><td>");
1532 List<IngestModuleInfo> ingestModules = ingestJob.getIngestModuleInfo();
1533 summary.append(
"<ul>\n");
1534 for (IngestModuleInfo ingestModule : ingestModules) {
1535 summary.append(
"<li>").append(ingestModule.getDisplayName()).append(
"</li>");
1537 summary.append(
"</ul>\n");
1539 summary.append(
"</td></tr>\n");
1540 summary.append(
"</table>\n");
1542 summary.append(
"</div>\n");
1543 }
catch (TskCoreException ex) {
1544 logger.log(Level.WARNING,
"Unable to get ingest jobs for the HTML report.");
1566 File thumbFile = Paths.get(thumbsPath, fileName +
".png").toFile();
1567 if (bufferedThumb == null) {
1571 ImageIO.write(bufferedThumb,
"png", thumbFile);
1572 }
catch (IOException ex) {
1573 logger.log(Level.WARNING,
"Failed to write thumb file to report directory.", ex);
1576 if (thumbFile.exists()
1580 return THUMBS_REL_PATH
1581 + thumbFile.getName();
1593 String formattedString = StringEscapeUtils.escapeHtml4(text);
1594 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()
List< ImageTagRegion > getTaggedRegions(List< ContentTag > contentTags)
String getGeneratorLogoPath()
static synchronized IngestManager getInstance()
void addRow(List< String > row, boolean escapeText)
static HTMLReport instance
String useDataTypeIcon(String dataType)
static final String HTML_SUBDIR
JPanel getConfigurationPanel()
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)
static BufferedImage getThumbnail(Content content, int iconSize)
static String getVersion()
void startSet(String setName)