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");
384 case TSK_WEB_FORM_ADDRESS:
385 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/web-form-address.png");
388 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/gps-area.png");
390 case TSK_WEB_CATEGORIZATION:
391 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/domain-16.png");
394 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/yara_16.png");
397 logger.log(Level.WARNING,
"useDataTypeIcon: unhandled artifact type = {0}", dataType);
398 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/star.png");
399 iconFileName =
"star.png";
400 iconFilePath = subPath + File.separator + iconFileName;
403 }
else if (dataType.startsWith(ARTIFACT_TYPE.TSK_ACCOUNT.getDisplayName())) {
411 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/accounts.png");
412 iconFileName =
"accounts.png";
413 iconFilePath = subPath + File.separator + iconFileName;
415 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/star.png");
416 iconFileName =
"star.png";
417 iconFilePath = subPath + File.separator + iconFileName;
421 output =
new FileOutputStream(iconFilePath);
422 FileUtil.copy(in, output);
425 }
catch (IOException ex) {
426 logger.log(Level.SEVERE,
"Failed to extract images for HTML report.", ex);
428 if (output != null) {
432 }
catch (IOException ex) {
438 }
catch (IOException ex) {
459 logger.log(Level.SEVERE,
"Exception while getting open case.");
463 this.path = baseReportDir;
464 this.subPath = this.path + HTML_SUBDIR + File.separator;
467 FileUtil.createFolder(
new File(this.subPath));
468 FileUtil.createFolder(
new File(this.thumbsPath));
469 }
catch (IOException ex) {
470 logger.log(Level.SEVERE,
"Unable to make HTML report folder.");
488 }
catch (IOException ex) {
489 logger.log(Level.WARNING,
"Could not close the output writer when ending report.", ex);
506 out =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(subPath + title +
".html"),
"UTF-8"));
507 }
catch (FileNotFoundException ex) {
508 logger.log(Level.SEVERE,
"File not found: {0}", ex);
509 }
catch (UnsupportedEncodingException ex) {
510 logger.log(Level.SEVERE,
"Unrecognized encoding");
514 StringBuilder page =
new StringBuilder();
515 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")
517 .append(
"<div id=\"header\">").append(name).append(
"</div>\n")
518 .append(
"<div id=\"content\">\n");
519 if (!description.isEmpty()) {
520 page.append(
"<p><strong>");
521 page.append(description);
522 page.append(
"</strong></p>\n");
524 out.write(page.toString());
525 currentDataType = name;
527 }
catch (IOException ex) {
528 logger.log(Level.SEVERE,
"Failed to write page head: {0}", ex);
538 dataTypes.put(currentDataType, rowCount);
540 StringBuilder builder =
new StringBuilder();
542 builder.append(
"</div>\n</body>\n</html>\n");
543 out.write(builder.toString());
544 }
catch (IOException ex) {
545 logger.log(Level.SEVERE,
"Failed to write end of HTML report.", ex);
551 }
catch (IOException ex) {
552 logger.log(Level.WARNING,
"Could not close the output writer when ending data type.", ex);
566 StringBuilder output =
new StringBuilder();
567 String pageHeader = configPanel.getHeader();
568 if (pageHeader.isEmpty() ==
false) {
569 output.append(
"<div id=\"pageHeaderFooter\">")
570 .append(StringEscapeUtils.escapeHtml4(pageHeader))
573 return output.toString();
583 StringBuilder output =
new StringBuilder();
584 String pageFooter = configPanel.getFooter();
585 if (pageFooter.isEmpty() ==
false) {
586 output.append(
"<br/><div id=\"pageHeaderFooter\">")
587 .append(StringEscapeUtils.escapeHtml4(pageFooter))
590 return output.toString();
600 StringBuilder set =
new StringBuilder();
601 set.append(
"<h1><a name=\"").append(setName).append(
"\">").append(setName).append(
"</a></h1>\n");
602 set.append(
"<div class=\"keyword_list\">\n");
605 out.write(set.toString());
606 }
catch (IOException ex) {
607 logger.log(Level.SEVERE,
"Failed to write set: {0}", ex);
617 out.write(
"</div>\n");
618 }
catch (IOException ex) {
619 logger.log(Level.SEVERE,
"Failed to write end of set: {0}", ex);
630 StringBuilder index =
new StringBuilder();
631 index.append(
"<ul>\n");
632 for (String set : sets) {
633 index.append(
"\t<li><a href=\"#").append(set).append(
"\">").append(set).append(
"</a></li>\n");
635 index.append(
"</ul>\n");
637 out.write(index.toString());
638 }
catch (IOException ex) {
639 logger.log(Level.SEVERE,
"Failed to add set index: {0}", ex);
651 out.write(
"<h4>" + elementName +
"</h4>\n");
652 }
catch (IOException ex) {
653 logger.log(Level.SEVERE,
"Failed to write set element: {0}", ex);
664 StringBuilder ele =
new StringBuilder();
665 ele.append(
"<table>\n<thead>\n\t<tr>\n");
666 for (String title : titles) {
667 ele.append(
"\t\t<th>").append(title).append(
"</th>\n");
669 ele.append(
"\t</tr>\n</thead>\n");
672 out.write(ele.toString());
673 }
catch (IOException ex) {
674 logger.log(Level.SEVERE,
"Failed to write table start: {0}", ex);
685 StringBuilder htmlOutput =
new StringBuilder();
686 htmlOutput.append(
"<table>\n<thead>\n\t<tr>\n");
689 for (String columnHeader : columnHeaders) {
690 htmlOutput.append(
"\t\t<th>").append(columnHeader).append(
"</th>\n");
694 htmlOutput.append(
"\t\t<th></th>\n");
696 htmlOutput.append(
"\t</tr>\n</thead>\n");
699 out.write(htmlOutput.toString());
700 }
catch (IOException ex) {
701 logger.log(Level.SEVERE,
"Failed to write table start: {0}", ex);
711 out.write(
"</table>\n");
712 }
catch (IOException ex) {
713 logger.log(Level.SEVERE,
"Failed to write end of table: {0}", ex);
735 private void addRow(List<String> row,
boolean escapeText) {
736 StringBuilder builder =
new StringBuilder();
737 builder.append(
"\t<tr>\n");
738 for (String cell : row) {
740 builder.append(
"\t\t<td>").append(cellText).append(
"</td>\n");
742 builder.append(
"\t</tr>\n");
746 out.write(builder.toString());
747 }
catch (IOException ex) {
748 logger.log(Level.SEVERE,
"Failed to write row to out.", ex);
749 }
catch (NullPointerException ex) {
750 logger.log(Level.SEVERE,
"Output writer is null. Page was not initialized before writing.", ex);
762 Content content = contentTag.getContent();
763 if (content instanceof AbstractFile ==
false) {
767 AbstractFile file = (AbstractFile) content;
769 StringBuilder localFileLink =
new StringBuilder();
772 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS
773 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS)) {
774 localFileLink.append(
"<a href=\"");
776 String localFilePath =
saveContent(file, contentTag.getName().getDisplayName());
777 localFileLink.append(localFilePath);
778 localFileLink.append(
"\" target=\"_top\">");
781 StringBuilder builder =
new StringBuilder();
782 builder.append(
"\t<tr>\n");
783 int positionCounter = 0;
784 for (String cell : row) {
786 switch (positionCounter) {
789 builder.append(
"\t\t<td class=\"left_align_cell\">").append(localFileLink.toString()).append(cell).append(
"</a></td>\n");
793 builder.append(
"\t\t<td class=\"right_align_cell\">").append(cell).append(
"</td>\n");
797 builder.append(
"\t\t<td>").append(cell).append(
"</td>\n");
802 builder.append(
"\t</tr>\n");
806 out.write(builder.toString());
807 }
catch (IOException ex) {
808 logger.log(Level.SEVERE,
"Failed to write row to out.", ex);
809 }
catch (NullPointerException ex) {
810 logger.log(Level.SEVERE,
"Output writer is null. Page was not initialized before writing.", ex);
821 ArrayList<ImageTagRegion> tagRegions =
new ArrayList<>();
822 contentTags.forEach((contentTag) -> {
826 if (contentViewerTag != null) {
827 tagRegions.add(contentViewerTag.
getDetails());
830 logger.log(Level.WARNING,
"Could not get content viewer tag "
831 +
"from case db for content_tag with id %d", contentTag.getId());
843 List<String> currentRow =
new ArrayList<>();
846 for (Content content : images) {
848 addRow(currentRow,
false);
852 if (totalCount == MAX_THUMBS_PER_PAGE) {
856 rowCount = totalCount;
861 startDataType(NbBundle.getMessage(
this.getClass(),
"ReportHTML.addThumbRows.dataType.title", pages),
862 NbBundle.getMessage(
this.getClass(),
"ReportHTML.addThumbRows.dataType.msg"));
863 List<String> emptyHeaders =
new ArrayList<>();
865 emptyHeaders.add(
"");
874 AbstractFile file = (AbstractFile) content;
875 List<ContentTag> contentTags =
new ArrayList<>();
877 String thumbnailPath = null;
878 String imageWithTagsFullPath = null;
885 if (!imageTags.isEmpty()) {
895 File thumbnailImageWithTagsFile = Paths.get(thumbsPath, FilenameUtils.removeExtension(fileName) +
".png").toFile();
897 fullImageWithTagsPath = FilenameUtils.removeExtension(fullImageWithTagsPath) +
".png";
898 File fullImageWithTagsFile = Paths.get(fullImageWithTagsPath).toFile();
901 ImageIO.write(thumbnailWithTags,
"png", thumbnailImageWithTagsFile);
902 ImageIO.write(fullImageWithTags,
"png", fullImageWithTagsFile);
904 thumbnailPath = THUMBS_REL_PATH + thumbnailImageWithTagsFile.getName();
906 imageWithTagsFullPath = fullImageWithTagsPath.substring(subPath.length());
908 }
catch (TskCoreException ex) {
909 logger.log(Level.WARNING,
"Could not get tags for file.", ex);
910 }
catch (IOException | InterruptedException | ExecutionException ex) {
911 logger.log(Level.WARNING,
"Could make marked up thumbnail.", ex);
915 if (thumbnailPath == null) {
919 if (thumbnailPath == null) {
922 String contentPath =
saveContent(file,
"original");
925 nameInImage = file.getUniquePath();
926 }
catch (TskCoreException ex) {
927 nameInImage = file.getName();
930 StringBuilder linkToThumbnail =
new StringBuilder();
931 linkToThumbnail.append(
"<div id='thumbnail_link'><a href=\"")
932 .append((imageWithTagsFullPath != null) ? imageWithTagsFullPath : contentPath)
933 .append(
"\" target=\"_top\"><img src=\"")
934 .append(thumbnailPath).append(
"\" title=\"").append(nameInImage).append(
"\"/></a><br>")
935 .append(file.getName()).append(
"<br>");
936 if (imageWithTagsFullPath != null) {
937 linkToThumbnail.append(
"<a href=\"").append(contentPath).append(
"\" target=\"_top\">View Original</a><br>");
940 if (!contentTags.isEmpty()) {
941 linkToThumbnail.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.thumbLink.tags"));
943 for (
int i = 0; i < contentTags.size(); i++) {
944 ContentTag tag = contentTags.get(i);
946 linkToThumbnail.append(tag.getName().getDisplayName()).append(notableString);
947 if (i != contentTags.size() - 1) {
948 linkToThumbnail.append(
", ");
952 linkToThumbnail.append(
"</div>");
953 currentRow.add(linkToThumbnail.toString());
958 if (currentRow.isEmpty() ==
false) {
959 int extraCells = THUMBNAIL_COLUMNS - currentRow.size();
960 for (
int i = 0; i < extraCells; i++) {
964 addRow(currentRow,
false);
968 rowCount = totalCount;
972 if (c instanceof AbstractFile ==
false) {
975 AbstractFile file = (AbstractFile) c;
977 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS
978 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS;
986 StringBuilder localFilePath =
new StringBuilder();
988 localFilePath.append(subPath);
989 localFilePath.append(dirName2);
990 File localFileFolder =
new File(localFilePath.toString());
991 if (!localFileFolder.exists()) {
992 localFileFolder.mkdirs();
1003 String objectIdSuffix =
"_" + file.getId();
1004 int lastDotIndex = fileName.lastIndexOf(
".");
1005 if (lastDotIndex != -1 && lastDotIndex != 0) {
1007 fileName = fileName.substring(0, lastDotIndex) + objectIdSuffix + fileName.substring(lastDotIndex, fileName.length());
1011 fileName += objectIdSuffix;
1013 localFilePath.append(File.separator);
1014 localFilePath.append(fileName);
1016 return localFilePath.toString();
1034 File localFile =
new File(localFilePath);
1035 if (!localFile.exists()) {
1040 return localFilePath.substring(subPath.length());
1052 SimpleDateFormat sdf =
new java.text.SimpleDateFormat(
"yyyy/MM/dd HH:mm:ss");
1053 return sdf.format(
new java.util.Date(date * 1000));
1058 return "report.html";
1063 return NbBundle.getMessage(this.getClass(),
"ReportHTML.getName.text");
1068 return NbBundle.getMessage(this.getClass(),
"ReportHTML.getDesc.text");
1075 Writer cssOut = null;
1077 cssOut =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(subPath +
"index.css"),
"UTF-8"));
1078 String css =
"body {margin: 0px; padding: 0px; background: #FFFFFF; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353;}\n"
1080 "#content {padding: 30px;}\n"
1082 "#header {width:100%; padding: 10px; line-height: 25px; background: #07A; color: #FFF; font-size: 20px;}\n"
1084 "#pageHeaderFooter {width: 100%; padding: 10px; line-height: 25px; text-align: center; font-size: 20px;}\n"
1086 "h1 {font-size: 20px; font-weight: normal; color: #07A; padding: 0 0 7px 0; margin-top: 25px; border-bottom: 1px solid #D6D6D6;}\n"
1088 "h2 {font-size: 20px; font-weight: bolder; color: #07A;}\n"
1090 "h3 {font-size: 16px; color: #07A;}\n"
1092 "h4 {background: #07A; color: #FFF; font-size: 16px; margin: 0 0 0 25px; padding: 0; padding-left: 15px;}\n"
1094 "ul.nav {list-style-type: none; line-height: 35px; padding: 0px; margin-left: 15px;}\n"
1096 "ul li a {font-size: 14px; color: #444; text-decoration: none; padding-left: 25px;}\n"
1098 "ul li a:hover {text-decoration: underline;}\n"
1100 "p {margin: 0 0 20px 0;}\n"
1102 "table {white-space:nowrap; min-width: 700px; padding: 2; margin: 0; border-collapse: collapse; border-bottom: 2px solid #e5e5e5;}\n"
1104 ".keyword_list table {margin: 0 0 25px 25px; border-bottom: 2px solid #dedede;}\n"
1106 "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"
1108 "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"
1110 "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"
1112 "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"
1114 "table tr:nth-child(even) td {background: #f3f3f3;}\n"
1116 "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;}";
1118 }
catch (FileNotFoundException ex) {
1119 logger.log(Level.SEVERE,
"Could not find index.css file to write to.", ex);
1120 }
catch (UnsupportedEncodingException ex) {
1121 logger.log(Level.SEVERE,
"Did not recognize encoding when writing index.css.", ex);
1122 }
catch (IOException ex) {
1123 logger.log(Level.SEVERE,
"Error creating Writer for index.css.", ex);
1126 if (cssOut != null) {
1130 }
catch (IOException ex) {
1139 Writer indexOut = null;
1140 String indexFilePath = path +
"report.html";
1145 logger.log(Level.SEVERE,
"Exception while getting open case.", ex);
1149 indexOut =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(indexFilePath),
"UTF-8"));
1150 StringBuilder index =
new StringBuilder();
1153 if (iconPath == null) {
1155 iconPath = HTML_SUBDIR +
"favicon.ico";
1157 iconPath = Paths.get(reportBranding.
getAgencyLogoPath()).getFileName().toString();
1159 index.append(
"<head>\n<title>").append(reportTitle).append(
" ").append(
1160 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeIndex.title", currentCase.
getDisplayName())).append(
1162 index.append(
"<link rel=\"icon\" type=\"image/ico\" href=\"")
1163 .append(iconPath).append(
"\" />\n");
1164 index.append(
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n");
1165 index.append(
"</head>\n");
1166 index.append(
"<frameset cols=\"350px,*\">\n");
1167 index.append(
"<frame src=\"" + HTML_SUBDIR).append(File.separator).append(
"nav.html\" name=\"nav\">\n");
1168 index.append(
"<frame src=\"" + HTML_SUBDIR).append(File.separator).append(
"summary.html\" name=\"content\">\n");
1169 index.append(
"<noframes>").append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeIndex.noFrames.msg")).append(
"<br />\n");
1170 index.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeIndex.noFrames.seeNav")).append(
"<br />\n");
1171 index.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeIndex.seeSum")).append(
"</noframes>\n");
1172 index.append(
"</frameset>\n");
1173 index.append(
"</html>");
1174 indexOut.write(index.toString());
1175 openCase.
addReport(indexFilePath, NbBundle.getMessage(
this.getClass(),
1176 "ReportHTML.writeIndex.srcModuleName.text"),
"");
1177 }
catch (IOException ex) {
1178 logger.log(Level.SEVERE,
"Error creating Writer for report.html: {0}", ex);
1179 }
catch (TskCoreException ex) {
1180 String errorMessage = String.format(
"Error adding %s to case as a report", indexFilePath);
1181 logger.log(Level.SEVERE, errorMessage, ex);
1184 if (indexOut != null) {
1188 }
catch (IOException ex) {
1197 Writer navOut = null;
1199 navOut =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(subPath +
"nav.html"),
"UTF-8"));
1200 StringBuilder nav =
new StringBuilder();
1201 nav.append(
"<html>\n<head>\n\t<title>").append(
1202 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeNav.title"))
1203 .append(
"</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"index.css\" />\n");
1204 nav.append(
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n</head>\n<body>\n");
1205 nav.append(
"<div id=\"content\">\n<h1>").append(
1206 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeNav.h1")).append(
"</h1>\n");
1207 nav.append(
"<ul class=\"nav\">\n");
1208 nav.append(
"<li style=\"background: url(summary.png) left center no-repeat;\"><a href=\"summary.html\" target=\"content\">")
1209 .append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeNav.summary")).append(
"</a></li>\n");
1211 for (String dataType : dataTypes.keySet()) {
1214 nav.append(
"<li style=\"background: url('").append(iconFileName)
1215 .append(
"') left center no-repeat;\"><a href=\"")
1216 .append(dataTypeEsc).append(
".html\" target=\"content\">")
1217 .append(dataType).append(
" (").append(dataTypes.get(dataType))
1218 .append(
")</a></li>\n");
1220 nav.append(
"</ul>\n");
1221 nav.append(
"</div>\n</body>\n</html>");
1222 navOut.write(nav.toString());
1223 }
catch (IOException ex) {
1224 logger.log(Level.SEVERE,
"Failed to write end of report navigation menu: {0}", ex);
1226 if (navOut != null) {
1230 }
catch (IOException ex) {
1231 logger.log(Level.WARNING,
"Could not close navigation out writer.");
1236 InputStream in = null;
1237 OutputStream output = null;
1242 if (generatorLogoPath != null && !generatorLogoPath.isEmpty()) {
1243 File from =
new File(generatorLogoPath);
1244 File to =
new File(subPath);
1245 FileUtil.copyFile(FileUtil.toFileObject(from), FileUtil.toFileObject(to),
"generator_logo");
1249 if (agencyLogoPath != null && !agencyLogoPath.isEmpty()) {
1250 Path destinationPath = Paths.get(subPath);
1251 Files.copy(Files.newInputStream(Paths.get(agencyLogoPath)), destinationPath.resolve(Paths.get(agencyLogoPath).getFileName()));
1254 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/favicon.ico");
1255 output =
new FileOutputStream(
new File(subPath +
"favicon.ico"));
1256 FileUtil.copy(in, output);
1260 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/summary.png");
1261 output =
new FileOutputStream(
new File(subPath +
"summary.png"));
1262 FileUtil.copy(in, output);
1266 }
catch (IOException ex) {
1267 logger.log(Level.SEVERE,
"Failed to extract images for HTML report.", ex);
1269 if (output != null) {
1273 }
catch (IOException ex) {
1279 }
catch (IOException ex) {
1289 Writer output = null;
1291 output =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(subPath +
"summary.html"),
"UTF-8"));
1292 StringBuilder head =
new StringBuilder();
1293 head.append(
"<html>\n<head>\n<title>").append(
1294 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.title")).append(
"</title>\n");
1295 head.append(
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n");
1296 head.append(
"<style type=\"text/css\">\n");
1297 head.append(
"#pageHeaderFooter {width: 100%; padding: 10px; line-height: 25px; text-align: center; font-size: 20px;}\n");
1298 head.append(
"body { padding: 0px; margin: 0px; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353; }\n");
1299 head.append(
"#wrapper { width: 90%; margin: 0px auto; margin-top: 35px; }\n");
1300 head.append(
"h1 { color: #07A; font-size: 36px; line-height: 42px; font-weight: normal; margin: 0px; border-bottom: 1px solid #81B9DB; }\n");
1301 head.append(
"h1 span { color: #F00; display: block; font-size: 16px; font-weight: bold; line-height: 22px;}\n");
1302 head.append(
"h2 { padding: 0 0 3px 0; margin: 0px; color: #07A; font-weight: normal; border-bottom: 1px dotted #81B9DB; }\n");
1303 head.append(
"h3 { padding: 5 0 3px 0; margin: 0px; color: #07A; font-weight: normal; }\n");
1304 head.append(
"table td { padding: 5px 25px 5px 0px; vertical-align:top;}\n");
1305 head.append(
"p.subheadding { padding: 0px; margin: 0px; font-size: 11px; color: #B5B5B5; }\n");
1306 head.append(
".title { width: 660px; margin-bottom: 50px; }\n");
1307 head.append(
".left { float: left; width: 250px; margin-top: 20px; text-align: center; }\n");
1308 head.append(
".left img { max-width: 250px; max-height: 250px; min-width: 200px; min-height: 200px; }\n");
1309 head.append(
".right { float: right; width: 385px; margin-top: 25px; font-size: 14px; }\n");
1310 head.append(
".clear { clear: both; }\n");
1311 head.append(
".info { padding: 10px 0;}\n");
1312 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");
1313 head.append(
".info table { margin: 10px 25px 10px 25px; }\n");
1314 head.append(
"ul {padding: 0;margin: 0;list-style-type: none;}");
1315 head.append(
"li {padding-bottom: 5px;}");
1316 head.append(
"</style>\n");
1317 head.append(
"</head>\n<body>\n");
1318 output.write(head.toString());
1320 DateFormat datetimeFormat =
new SimpleDateFormat(
"yyyy/MM/dd HH:mm:ss");
1321 Date date =
new Date();
1322 String datetime = datetimeFormat.format(date);
1324 StringBuilder summary =
new StringBuilder();
1325 boolean running =
false;
1330 List<IngestJobInfo> ingestJobs = skCase.getIngestJobs();
1335 summary.append(
"<div id=\"wrapper\">\n");
1337 summary.append(
"<h1>").append(reportTitle)
1338 .append(running ? NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.warningMsg") :
"")
1340 summary.append(
"<p class=\"subheadding\">").append(
1341 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.reportGenOn.text", datetime)).append(
"</p>\n");
1342 summary.append(
"<div class=\"title\">\n");
1347 if (generatorLogoSet) {
1348 summary.append(
"<div class=\"left\">\n");
1349 summary.append(
"<img src=\"generator_logo.png\" />\n");
1350 summary.append(
"</div>\n");
1352 summary.append(
"<div class=\"clear\"></div>\n");
1353 if (reportFooter != null) {
1354 summary.append(
"<p class=\"subheadding\">").append(reportFooter).append(
"</p>\n");
1356 summary.append(
"</div>\n");
1358 summary.append(
"</body></html>");
1359 output.write(summary.toString());
1360 }
catch (FileNotFoundException ex) {
1361 logger.log(Level.SEVERE,
"Could not find summary.html file to write to.");
1362 }
catch (UnsupportedEncodingException ex) {
1363 logger.log(Level.SEVERE,
"Did not recognize encoding when writing summary.hmtl.");
1364 }
catch (IOException ex) {
1365 logger.log(Level.SEVERE,
"Error creating Writer for summary.html.");
1367 logger.log(Level.WARNING,
"Unable to get current sleuthkit Case for the HTML report.");
1370 if (output != null) {
1374 }
catch (IOException ex) {
1380 "ReportHTML.writeSum.case=Case:",
1381 "ReportHTML.writeSum.caseNumber=Case Number:",
1382 "ReportHTML.writeSum.caseNumImages=Number of data sources in case:",
1383 "ReportHTML.writeSum.caseNotes=Notes:",
1384 "ReportHTML.writeSum.examiner=Examiner:"
1392 StringBuilder summary =
new StringBuilder();
1398 String caseNumber = currentCase.
getNumber();
1402 }
catch (TskCoreException ex) {
1411 summary.append(
"<div class=\"title\">\n");
1412 if (agencyLogoSet) {
1413 summary.append(
"<div class=\"left\">\n");
1414 summary.append(
"<img src=\"");
1415 summary.append(Paths.get(reportBranding.
getAgencyLogoPath()).getFileName().toString());
1416 summary.append(
"\" />\n");
1417 summary.append(
"</div>\n");
1419 final String align = agencyLogoSet ?
"right" :
"left";
1420 summary.append(
"<div class=\"").append(align).append(
"\">\n");
1421 summary.append(
"<table>\n");
1424 summary.append(
"<tr><td>").append(Bundle.ReportHTML_writeSum_case()).append(
"</td><td>")
1427 if (!caseNumber.isEmpty()) {
1428 summary.append(
"<tr><td>").append(Bundle.ReportHTML_writeSum_caseNumber()).append(
"</td><td>")
1432 summary.append(
"<tr><td>").append(Bundle.ReportHTML_writeSum_caseNumImages()).append(
"</td><td>")
1433 .append(imagecount).append(
"</td></tr>\n");
1435 if (!caseNotes.isEmpty()) {
1436 summary.append(
"<tr><td>").append(Bundle.ReportHTML_writeSum_caseNotes()).append(
"</td><td>")
1441 if (!examinerName.isEmpty()) {
1442 summary.append(
"<tr><td>").append(Bundle.ReportHTML_writeSum_examiner()).append(
"</td><td>")
1447 summary.append(
"</table>\n");
1448 summary.append(
"</div>\n");
1449 summary.append(
"<div class=\"clear\"></div>\n");
1450 summary.append(
"</div>\n");
1460 StringBuilder summary =
new StringBuilder();
1461 summary.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.imageInfoHeading"));
1462 summary.append(
"<div class=\"info\">\n");
1465 summary.append(
"<p>").append(c.getName()).append(
"</p>\n");
1466 if (c instanceof Image) {
1467 Image img = (Image) c;
1469 summary.append(
"<table>\n");
1470 summary.append(
"<tr><td>").append(
1471 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.timezone"))
1472 .append(
"</td><td>").append(img.getTimeZone()).append(
"</td></tr>\n");
1473 for (String imgPath : img.getPaths()) {
1474 summary.append(
"<tr><td>").append(
1475 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.path"))
1476 .append(
"</td><td>").append(imgPath).append(
"</td></tr>\n");
1478 summary.append(
"</table>\n");
1481 }
catch (TskCoreException ex) {
1482 logger.log(Level.WARNING,
"Unable to get image information for the HTML report.");
1484 summary.append(
"</div>\n");
1494 StringBuilder summary =
new StringBuilder();
1495 summary.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.softwareInfoHeading"));
1496 summary.append(
"<div class=\"info\">\n");
1497 summary.append(
"<table>\n");
1498 summary.append(
"<tr><td>").append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.autopsyVersion"))
1500 Map<Long, IngestModuleInfo> moduleInfoHashMap =
new HashMap<>();
1501 for (IngestJobInfo ingestJob : ingestJobs) {
1502 List<IngestModuleInfo> ingestModules = ingestJob.getIngestModuleInfo();
1503 for (IngestModuleInfo ingestModule : ingestModules) {
1504 if (!moduleInfoHashMap.containsKey(ingestModule.getIngestModuleId())) {
1505 moduleInfoHashMap.put(ingestModule.getIngestModuleId(), ingestModule);
1509 TreeMap<String, String> modules =
new TreeMap<>();
1510 for (IngestModuleInfo moduleinfo : moduleInfoHashMap.values()) {
1511 modules.put(moduleinfo.getDisplayName(), moduleinfo.getVersion());
1513 for (Map.Entry<String, String> module : modules.entrySet()) {
1514 summary.append(
"<tr><td>").append(module.getKey()).append(
" Module:")
1515 .append(
"</td><td>").append(module.getValue()).append(
"</td></tr>\n");
1517 summary.append(
"</table>\n");
1518 summary.append(
"</div>\n");
1519 summary.append(
"<div class=\"clear\"></div>\n");
1529 StringBuilder summary =
new StringBuilder();
1531 summary.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.ingestHistoryHeading"));
1532 summary.append(
"<div class=\"info\">\n");
1535 for (IngestJobInfo ingestJob : ingestJobs) {
1536 summary.append(
"<h3>Job ").append(jobnumber).append(
":</h3>\n");
1537 summary.append(
"<table>\n");
1538 summary.append(
"<tr><td>").append(
"Data Source:")
1539 .append(
"</td><td>").append(skCase.getContentById(ingestJob.getObjectId()).
getName()).append(
"</td></tr>\n");
1540 summary.append(
"<tr><td>").append(
"Status:")
1541 .append(
"</td><td>").append(ingestJob.getStatus()).append(
"</td></tr>\n");
1542 summary.append(
"<tr><td>").append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.modulesEnabledHeading"))
1543 .append(
"</td><td>");
1544 List<IngestModuleInfo> ingestModules = ingestJob.getIngestModuleInfo();
1545 summary.append(
"<ul>\n");
1546 for (IngestModuleInfo ingestModule : ingestModules) {
1547 summary.append(
"<li>").append(ingestModule.getDisplayName()).append(
"</li>");
1549 summary.append(
"</ul>\n");
1551 summary.append(
"</td></tr>\n");
1552 summary.append(
"</table>\n");
1554 summary.append(
"</div>\n");
1555 }
catch (TskCoreException ex) {
1556 logger.log(Level.WARNING,
"Unable to get ingest jobs for the HTML report.");
1578 File thumbFile = Paths.get(thumbsPath, fileName +
".png").toFile();
1579 if (bufferedThumb == null) {
1583 ImageIO.write(bufferedThumb,
"png", thumbFile);
1584 }
catch (IOException ex) {
1585 logger.log(Level.WARNING,
"Failed to write thumb file to report directory.", ex);
1588 if (thumbFile.exists()
1592 return THUMBS_REL_PATH
1593 + thumbFile.getName();
1605 String formattedString = StringEscapeUtils.escapeHtml4(text);
1606 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)