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(
" ",
"_");
216 @SuppressWarnings(
"deprecation" )
221 OutputStream output = null;
223 logger.log(Level.INFO,
"useDataTypeIcon: dataType = {0}", dataType);
226 BlackboardArtifact.ARTIFACT_TYPE artifactType = null;
227 for (ARTIFACT_TYPE v : ARTIFACT_TYPE.values()) {
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:
329 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/interesting_item.png");
331 case TSK_INTERESTING_FILE_HIT:
332 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/interesting_item.png");
335 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/installed.png");
337 case TSK_REMOTE_DRIVE:
338 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/drive_network.png");
341 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/os-account.png");
343 case TSK_OBJECT_DETECTED:
344 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/objects.png");
346 case TSK_WEB_FORM_AUTOFILL:
347 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/web-form.png");
350 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/cache.png");
352 case TSK_USER_CONTENT_SUSPECTED:
353 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/user-content.png");
356 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/metadata.png");
358 case TSK_CLIPBOARD_CONTENT:
359 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/clipboard.png");
362 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/accounts.png");
364 case TSK_WIFI_NETWORK:
365 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/network-wifi.png");
367 case TSK_WIFI_NETWORK_ADAPTER:
368 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/network-wifi.png");
370 case TSK_SIM_ATTACHED:
371 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/sim_card.png");
373 case TSK_BLUETOOTH_ADAPTER:
374 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/Bluetooth.png");
376 case TSK_DEVICE_INFO:
377 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/devices.png");
379 case TSK_VERIFICATION_FAILED:
380 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/validationFailed.png");
382 case TSK_WEB_ACCOUNT_TYPE:
383 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/web-account-type.png");
385 case TSK_WEB_FORM_ADDRESS:
386 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/web-form-address.png");
389 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/gps-area.png");
391 case TSK_WEB_CATEGORIZATION:
392 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/domain-16.png");
395 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/yara_16.png");
398 logger.log(Level.WARNING,
"useDataTypeIcon: unhandled artifact type = {0}", dataType);
399 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/star.png");
400 iconFileName =
"star.png";
401 iconFilePath = subPath + File.separator + iconFileName;
404 }
else if (dataType.startsWith(ARTIFACT_TYPE.TSK_ACCOUNT.getDisplayName())) {
412 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/accounts.png");
413 iconFileName =
"accounts.png";
414 iconFilePath = subPath + File.separator + iconFileName;
416 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/star.png");
417 iconFileName =
"star.png";
418 iconFilePath = subPath + File.separator + iconFileName;
422 output =
new FileOutputStream(iconFilePath);
423 FileUtil.copy(in, output);
426 }
catch (IOException ex) {
427 logger.log(Level.SEVERE,
"Failed to extract images for HTML report.", ex);
429 if (output != null) {
433 }
catch (IOException ex) {
439 }
catch (IOException ex) {
460 logger.log(Level.SEVERE,
"Exception while getting open case.");
464 this.path = baseReportDir;
465 this.subPath = this.path + HTML_SUBDIR + File.separator;
468 FileUtil.createFolder(
new File(this.subPath));
469 FileUtil.createFolder(
new File(this.thumbsPath));
470 }
catch (IOException ex) {
471 logger.log(Level.SEVERE,
"Unable to make HTML report folder.");
489 }
catch (IOException ex) {
490 logger.log(Level.WARNING,
"Could not close the output writer when ending report.", ex);
507 out =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(subPath + title +
".html"),
"UTF-8"));
508 }
catch (FileNotFoundException ex) {
509 logger.log(Level.SEVERE,
"File not found: {0}", ex);
510 }
catch (UnsupportedEncodingException ex) {
511 logger.log(Level.SEVERE,
"Unrecognized encoding");
515 StringBuilder page =
new StringBuilder();
516 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")
518 .append(
"<div id=\"header\">").append(name).append(
"</div>\n")
519 .append(
"<div id=\"content\">\n");
520 if (!description.isEmpty()) {
521 page.append(
"<p><strong>");
522 page.append(description);
523 page.append(
"</strong></p>\n");
525 out.write(page.toString());
526 currentDataType = name;
528 }
catch (IOException ex) {
529 logger.log(Level.SEVERE,
"Failed to write page head: {0}", ex);
539 dataTypes.put(currentDataType, rowCount);
541 StringBuilder builder =
new StringBuilder();
543 builder.append(
"</div>\n</body>\n</html>\n");
544 out.write(builder.toString());
545 }
catch (IOException ex) {
546 logger.log(Level.SEVERE,
"Failed to write end of HTML report.", ex);
552 }
catch (IOException ex) {
553 logger.log(Level.WARNING,
"Could not close the output writer when ending data type.", ex);
567 StringBuilder output =
new StringBuilder();
568 String pageHeader = configPanel.getHeader();
569 if (pageHeader.isEmpty() ==
false) {
570 output.append(
"<div id=\"pageHeaderFooter\">")
571 .append(StringEscapeUtils.escapeHtml4(pageHeader))
574 return output.toString();
584 StringBuilder output =
new StringBuilder();
585 String pageFooter = configPanel.getFooter();
586 if (pageFooter.isEmpty() ==
false) {
587 output.append(
"<br/><div id=\"pageHeaderFooter\">")
588 .append(StringEscapeUtils.escapeHtml4(pageFooter))
591 return output.toString();
601 StringBuilder set =
new StringBuilder();
602 set.append(
"<h1><a name=\"").append(setName).append(
"\">").append(setName).append(
"</a></h1>\n");
603 set.append(
"<div class=\"keyword_list\">\n");
606 out.write(set.toString());
607 }
catch (IOException ex) {
608 logger.log(Level.SEVERE,
"Failed to write set: {0}", ex);
618 out.write(
"</div>\n");
619 }
catch (IOException ex) {
620 logger.log(Level.SEVERE,
"Failed to write end of set: {0}", ex);
631 StringBuilder index =
new StringBuilder();
632 index.append(
"<ul>\n");
633 for (String set : sets) {
634 index.append(
"\t<li><a href=\"#").append(set).append(
"\">").append(set).append(
"</a></li>\n");
636 index.append(
"</ul>\n");
638 out.write(index.toString());
639 }
catch (IOException ex) {
640 logger.log(Level.SEVERE,
"Failed to add set index: {0}", ex);
652 out.write(
"<h4>" + elementName +
"</h4>\n");
653 }
catch (IOException ex) {
654 logger.log(Level.SEVERE,
"Failed to write set element: {0}", ex);
665 StringBuilder ele =
new StringBuilder();
666 ele.append(
"<table>\n<thead>\n\t<tr>\n");
667 for (String title : titles) {
668 ele.append(
"\t\t<th>").append(title).append(
"</th>\n");
670 ele.append(
"\t</tr>\n</thead>\n");
673 out.write(ele.toString());
674 }
catch (IOException ex) {
675 logger.log(Level.SEVERE,
"Failed to write table start: {0}", ex);
686 StringBuilder htmlOutput =
new StringBuilder();
687 htmlOutput.append(
"<table>\n<thead>\n\t<tr>\n");
690 for (String columnHeader : columnHeaders) {
691 htmlOutput.append(
"\t\t<th>").append(columnHeader).append(
"</th>\n");
695 htmlOutput.append(
"\t\t<th></th>\n");
697 htmlOutput.append(
"\t</tr>\n</thead>\n");
700 out.write(htmlOutput.toString());
701 }
catch (IOException ex) {
702 logger.log(Level.SEVERE,
"Failed to write table start: {0}", ex);
712 out.write(
"</table>\n");
713 }
catch (IOException ex) {
714 logger.log(Level.SEVERE,
"Failed to write end of table: {0}", ex);
736 private void addRow(List<String> row,
boolean escapeText) {
737 StringBuilder builder =
new StringBuilder();
738 builder.append(
"\t<tr>\n");
739 for (String cell : row) {
741 builder.append(
"\t\t<td>").append(cellText).append(
"</td>\n");
743 builder.append(
"\t</tr>\n");
747 out.write(builder.toString());
748 }
catch (IOException ex) {
749 logger.log(Level.SEVERE,
"Failed to write row to out.", ex);
750 }
catch (NullPointerException ex) {
751 logger.log(Level.SEVERE,
"Output writer is null. Page was not initialized before writing.", ex);
763 Content content = contentTag.getContent();
764 if (content instanceof AbstractFile ==
false) {
768 AbstractFile file = (AbstractFile) content;
770 StringBuilder localFileLink =
new StringBuilder();
773 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS
774 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS)) {
775 localFileLink.append(
"<a href=\"");
777 String localFilePath =
saveContent(file, contentTag.getName().getDisplayName());
778 localFileLink.append(localFilePath);
779 localFileLink.append(
"\" target=\"_top\">");
782 StringBuilder builder =
new StringBuilder();
783 builder.append(
"\t<tr>\n");
784 int positionCounter = 0;
785 for (String cell : row) {
787 switch (positionCounter) {
790 builder.append(
"\t\t<td class=\"left_align_cell\">").append(localFileLink.toString()).append(cell).append(
"</a></td>\n");
794 builder.append(
"\t\t<td class=\"right_align_cell\">").append(cell).append(
"</td>\n");
798 builder.append(
"\t\t<td>").append(cell).append(
"</td>\n");
803 builder.append(
"\t</tr>\n");
807 out.write(builder.toString());
808 }
catch (IOException ex) {
809 logger.log(Level.SEVERE,
"Failed to write row to out.", ex);
810 }
catch (NullPointerException ex) {
811 logger.log(Level.SEVERE,
"Output writer is null. Page was not initialized before writing.", ex);
822 ArrayList<ImageTagRegion> tagRegions =
new ArrayList<>();
823 contentTags.forEach((contentTag) -> {
827 if (contentViewerTag != null) {
828 tagRegions.add(contentViewerTag.
getDetails());
831 logger.log(Level.WARNING,
"Could not get content viewer tag "
832 +
"from case db for content_tag with id %d", contentTag.getId());
844 List<String> currentRow =
new ArrayList<>();
847 for (Content content : images) {
849 addRow(currentRow,
false);
853 if (totalCount == MAX_THUMBS_PER_PAGE) {
857 rowCount = totalCount;
862 startDataType(NbBundle.getMessage(
this.getClass(),
"ReportHTML.addThumbRows.dataType.title", pages),
863 NbBundle.getMessage(
this.getClass(),
"ReportHTML.addThumbRows.dataType.msg"));
864 List<String> emptyHeaders =
new ArrayList<>();
866 emptyHeaders.add(
"");
875 AbstractFile file = (AbstractFile) content;
876 List<ContentTag> contentTags =
new ArrayList<>();
878 String thumbnailPath = null;
879 String imageWithTagsFullPath = null;
886 if (!imageTags.isEmpty()) {
896 File thumbnailImageWithTagsFile = Paths.get(thumbsPath, FilenameUtils.removeExtension(fileName) +
".png").toFile();
898 fullImageWithTagsPath = FilenameUtils.removeExtension(fullImageWithTagsPath) +
".png";
899 File fullImageWithTagsFile = Paths.get(fullImageWithTagsPath).toFile();
902 ImageIO.write(thumbnailWithTags,
"png", thumbnailImageWithTagsFile);
903 ImageIO.write(fullImageWithTags,
"png", fullImageWithTagsFile);
905 thumbnailPath = THUMBS_REL_PATH + thumbnailImageWithTagsFile.getName();
907 imageWithTagsFullPath = fullImageWithTagsPath.substring(subPath.length());
909 }
catch (TskCoreException ex) {
910 logger.log(Level.WARNING,
"Could not get tags for file.", ex);
911 }
catch (IOException | InterruptedException | ExecutionException ex) {
912 logger.log(Level.WARNING,
"Could make marked up thumbnail.", ex);
916 if (thumbnailPath == null) {
920 if (thumbnailPath == null) {
923 String contentPath =
saveContent(file,
"original");
926 nameInImage = file.getUniquePath();
927 }
catch (TskCoreException ex) {
928 nameInImage = file.getName();
931 StringBuilder linkToThumbnail =
new StringBuilder();
932 linkToThumbnail.append(
"<div id='thumbnail_link'><a href=\"")
933 .append((imageWithTagsFullPath != null) ? imageWithTagsFullPath : contentPath)
934 .append(
"\" target=\"_top\"><img src=\"")
935 .append(thumbnailPath).append(
"\" title=\"").append(nameInImage).append(
"\"/></a><br>")
936 .append(file.getName()).append(
"<br>");
937 if (imageWithTagsFullPath != null) {
938 linkToThumbnail.append(
"<a href=\"").append(contentPath).append(
"\" target=\"_top\">View Original</a><br>");
941 if (!contentTags.isEmpty()) {
942 linkToThumbnail.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.thumbLink.tags"));
944 for (
int i = 0; i < contentTags.size(); i++) {
945 ContentTag tag = contentTags.get(i);
947 linkToThumbnail.append(tag.getName().getDisplayName()).append(notableString);
948 if (i != contentTags.size() - 1) {
949 linkToThumbnail.append(
", ");
953 linkToThumbnail.append(
"</div>");
954 currentRow.add(linkToThumbnail.toString());
959 if (currentRow.isEmpty() ==
false) {
960 int extraCells = THUMBNAIL_COLUMNS - currentRow.size();
961 for (
int i = 0; i < extraCells; i++) {
965 addRow(currentRow,
false);
969 rowCount = totalCount;
973 if (c instanceof AbstractFile ==
false) {
976 AbstractFile file = (AbstractFile) c;
978 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS
979 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS;
987 StringBuilder localFilePath =
new StringBuilder();
989 localFilePath.append(subPath);
990 localFilePath.append(dirName2);
991 File localFileFolder =
new File(localFilePath.toString());
992 if (!localFileFolder.exists()) {
993 localFileFolder.mkdirs();
1004 String objectIdSuffix =
"_" + file.getId();
1005 int lastDotIndex = fileName.lastIndexOf(
".");
1006 if (lastDotIndex != -1 && lastDotIndex != 0) {
1008 fileName = fileName.substring(0, lastDotIndex) + objectIdSuffix + fileName.substring(lastDotIndex, fileName.length());
1012 fileName += objectIdSuffix;
1014 localFilePath.append(File.separator);
1015 localFilePath.append(fileName);
1017 return localFilePath.toString();
1035 File localFile =
new File(localFilePath);
1036 if (!localFile.exists()) {
1041 return localFilePath.substring(subPath.length());
1053 SimpleDateFormat sdf =
new java.text.SimpleDateFormat(
"yyyy/MM/dd HH:mm:ss");
1054 return sdf.format(
new java.util.Date(date * 1000));
1059 return "report.html";
1064 return NbBundle.getMessage(this.getClass(),
"ReportHTML.getName.text");
1069 return NbBundle.getMessage(this.getClass(),
"ReportHTML.getDesc.text");
1076 Writer cssOut = null;
1078 cssOut =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(subPath +
"index.css"),
"UTF-8"));
1079 String css =
"body {margin: 0px; padding: 0px; background: #FFFFFF; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353;}\n"
1081 "#content {padding: 30px;}\n"
1083 "#header {width:100%; padding: 10px; line-height: 25px; background: #07A; color: #FFF; font-size: 20px;}\n"
1085 "#pageHeaderFooter {width: 100%; padding: 10px; line-height: 25px; text-align: center; font-size: 20px;}\n"
1087 "h1 {font-size: 20px; font-weight: normal; color: #07A; padding: 0 0 7px 0; margin-top: 25px; border-bottom: 1px solid #D6D6D6;}\n"
1089 "h2 {font-size: 20px; font-weight: bolder; color: #07A;}\n"
1091 "h3 {font-size: 16px; color: #07A;}\n"
1093 "h4 {background: #07A; color: #FFF; font-size: 16px; margin: 0 0 0 25px; padding: 0; padding-left: 15px;}\n"
1095 "ul.nav {list-style-type: none; line-height: 35px; padding: 0px; margin-left: 15px;}\n"
1097 "ul li a {font-size: 14px; color: #444; text-decoration: none; padding-left: 25px;}\n"
1099 "ul li a:hover {text-decoration: underline;}\n"
1101 "p {margin: 0 0 20px 0;}\n"
1103 "table {white-space:nowrap; min-width: 700px; padding: 2; margin: 0; border-collapse: collapse; border-bottom: 2px solid #e5e5e5;}\n"
1105 ".keyword_list table {margin: 0 0 25px 25px; border-bottom: 2px solid #dedede;}\n"
1107 "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"
1109 "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"
1111 "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"
1113 "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"
1115 "table tr:nth-child(even) td {background: #f3f3f3;}\n"
1117 "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;}";
1119 }
catch (FileNotFoundException ex) {
1120 logger.log(Level.SEVERE,
"Could not find index.css file to write to.", ex);
1121 }
catch (UnsupportedEncodingException ex) {
1122 logger.log(Level.SEVERE,
"Did not recognize encoding when writing index.css.", ex);
1123 }
catch (IOException ex) {
1124 logger.log(Level.SEVERE,
"Error creating Writer for index.css.", ex);
1127 if (cssOut != null) {
1131 }
catch (IOException ex) {
1140 Writer indexOut = null;
1141 String indexFilePath = path +
"report.html";
1146 logger.log(Level.SEVERE,
"Exception while getting open case.", ex);
1150 indexOut =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(indexFilePath),
"UTF-8"));
1151 StringBuilder index =
new StringBuilder();
1154 if (iconPath == null) {
1156 iconPath = HTML_SUBDIR +
"favicon.ico";
1158 iconPath = Paths.get(reportBranding.
getAgencyLogoPath()).getFileName().toString();
1160 index.append(
"<head>\n<title>").append(reportTitle).append(
" ").append(
1161 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeIndex.title", currentCase.
getDisplayName())).append(
1163 index.append(
"<link rel=\"icon\" type=\"image/ico\" href=\"")
1164 .append(iconPath).append(
"\" />\n");
1165 index.append(
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n");
1166 index.append(
"</head>\n");
1167 index.append(
"<frameset cols=\"350px,*\">\n");
1168 index.append(
"<frame src=\"" + HTML_SUBDIR).append(File.separator).append(
"nav.html\" name=\"nav\">\n");
1169 index.append(
"<frame src=\"" + HTML_SUBDIR).append(File.separator).append(
"summary.html\" name=\"content\">\n");
1170 index.append(
"<noframes>").append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeIndex.noFrames.msg")).append(
"<br />\n");
1171 index.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeIndex.noFrames.seeNav")).append(
"<br />\n");
1172 index.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeIndex.seeSum")).append(
"</noframes>\n");
1173 index.append(
"</frameset>\n");
1174 index.append(
"</html>");
1175 indexOut.write(index.toString());
1176 openCase.
addReport(indexFilePath, NbBundle.getMessage(
this.getClass(),
1177 "ReportHTML.writeIndex.srcModuleName.text"),
"");
1178 }
catch (IOException ex) {
1179 logger.log(Level.SEVERE,
"Error creating Writer for report.html: {0}", ex);
1180 }
catch (TskCoreException ex) {
1181 String errorMessage = String.format(
"Error adding %s to case as a report", indexFilePath);
1182 logger.log(Level.SEVERE, errorMessage, ex);
1185 if (indexOut != null) {
1189 }
catch (IOException ex) {
1198 Writer navOut = null;
1200 navOut =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(subPath +
"nav.html"),
"UTF-8"));
1201 StringBuilder nav =
new StringBuilder();
1202 nav.append(
"<html>\n<head>\n\t<title>").append(
1203 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeNav.title"))
1204 .append(
"</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"index.css\" />\n");
1205 nav.append(
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n</head>\n<body>\n");
1206 nav.append(
"<div id=\"content\">\n<h1>").append(
1207 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeNav.h1")).append(
"</h1>\n");
1208 nav.append(
"<ul class=\"nav\">\n");
1209 nav.append(
"<li style=\"background: url(summary.png) left center no-repeat;\"><a href=\"summary.html\" target=\"content\">")
1210 .append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeNav.summary")).append(
"</a></li>\n");
1212 for (String dataType : dataTypes.keySet()) {
1215 nav.append(
"<li style=\"background: url('").append(iconFileName)
1216 .append(
"') left center no-repeat;\"><a href=\"")
1217 .append(dataTypeEsc).append(
".html\" target=\"content\">")
1218 .append(dataType).append(
" (").append(dataTypes.get(dataType))
1219 .append(
")</a></li>\n");
1221 nav.append(
"</ul>\n");
1222 nav.append(
"</div>\n</body>\n</html>");
1223 navOut.write(nav.toString());
1224 }
catch (IOException ex) {
1225 logger.log(Level.SEVERE,
"Failed to write end of report navigation menu: {0}", ex);
1227 if (navOut != null) {
1231 }
catch (IOException ex) {
1232 logger.log(Level.WARNING,
"Could not close navigation out writer.");
1237 InputStream in = null;
1238 OutputStream output = null;
1243 if (generatorLogoPath != null && !generatorLogoPath.isEmpty()) {
1244 File from =
new File(generatorLogoPath);
1245 File to =
new File(subPath);
1246 FileUtil.copyFile(FileUtil.toFileObject(from), FileUtil.toFileObject(to),
"generator_logo");
1250 if (agencyLogoPath != null && !agencyLogoPath.isEmpty()) {
1251 Path destinationPath = Paths.get(subPath);
1252 Files.copy(Files.newInputStream(Paths.get(agencyLogoPath)), destinationPath.resolve(Paths.get(agencyLogoPath).getFileName()));
1255 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/favicon.ico");
1256 output =
new FileOutputStream(
new File(subPath +
"favicon.ico"));
1257 FileUtil.copy(in, output);
1261 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/summary.png");
1262 output =
new FileOutputStream(
new File(subPath +
"summary.png"));
1263 FileUtil.copy(in, output);
1267 }
catch (IOException ex) {
1268 logger.log(Level.SEVERE,
"Failed to extract images for HTML report.", ex);
1270 if (output != null) {
1274 }
catch (IOException ex) {
1280 }
catch (IOException ex) {
1290 Writer output = null;
1292 output =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(subPath +
"summary.html"),
"UTF-8"));
1293 StringBuilder head =
new StringBuilder();
1294 head.append(
"<html>\n<head>\n<title>").append(
1295 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.title")).append(
"</title>\n");
1296 head.append(
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n");
1297 head.append(
"<style type=\"text/css\">\n");
1298 head.append(
"#pageHeaderFooter {width: 100%; padding: 10px; line-height: 25px; text-align: center; font-size: 20px;}\n");
1299 head.append(
"body { padding: 0px; margin: 0px; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353; }\n");
1300 head.append(
"#wrapper { width: 90%; margin: 0px auto; margin-top: 35px; }\n");
1301 head.append(
"h1 { color: #07A; font-size: 36px; line-height: 42px; font-weight: normal; margin: 0px; border-bottom: 1px solid #81B9DB; }\n");
1302 head.append(
"h1 span { color: #F00; display: block; font-size: 16px; font-weight: bold; line-height: 22px;}\n");
1303 head.append(
"h2 { padding: 0 0 3px 0; margin: 0px; color: #07A; font-weight: normal; border-bottom: 1px dotted #81B9DB; }\n");
1304 head.append(
"h3 { padding: 5 0 3px 0; margin: 0px; color: #07A; font-weight: normal; }\n");
1305 head.append(
"table td { padding: 5px 25px 5px 0px; vertical-align:top;}\n");
1306 head.append(
"p.subheadding { padding: 0px; margin: 0px; font-size: 11px; color: #B5B5B5; }\n");
1307 head.append(
".title { width: 660px; margin-bottom: 50px; }\n");
1308 head.append(
".left { float: left; width: 250px; margin-top: 20px; text-align: center; }\n");
1309 head.append(
".left img { max-width: 250px; max-height: 250px; min-width: 200px; min-height: 200px; }\n");
1310 head.append(
".right { float: right; width: 385px; margin-top: 25px; font-size: 14px; }\n");
1311 head.append(
".clear { clear: both; }\n");
1312 head.append(
".info { padding: 10px 0;}\n");
1313 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");
1314 head.append(
".info table { margin: 10px 25px 10px 25px; }\n");
1315 head.append(
"ul {padding: 0;margin: 0;list-style-type: none;}");
1316 head.append(
"li {padding-bottom: 5px;}");
1317 head.append(
"</style>\n");
1318 head.append(
"</head>\n<body>\n");
1319 output.write(head.toString());
1321 DateFormat datetimeFormat =
new SimpleDateFormat(
"yyyy/MM/dd HH:mm:ss");
1322 Date date =
new Date();
1323 String datetime = datetimeFormat.format(date);
1325 StringBuilder summary =
new StringBuilder();
1326 boolean running =
false;
1331 List<IngestJobInfo> ingestJobs = skCase.getIngestJobs();
1336 summary.append(
"<div id=\"wrapper\">\n");
1338 summary.append(
"<h1>").append(reportTitle)
1339 .append(running ? NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.warningMsg") :
"")
1341 summary.append(
"<p class=\"subheadding\">").append(
1342 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.reportGenOn.text", datetime)).append(
"</p>\n");
1343 summary.append(
"<div class=\"title\">\n");
1348 if (generatorLogoSet) {
1349 summary.append(
"<div class=\"left\">\n");
1350 summary.append(
"<img src=\"generator_logo.png\" />\n");
1351 summary.append(
"</div>\n");
1353 summary.append(
"<div class=\"clear\"></div>\n");
1354 if (reportFooter != null) {
1355 summary.append(
"<p class=\"subheadding\">").append(reportFooter).append(
"</p>\n");
1357 summary.append(
"</div>\n");
1359 summary.append(
"</body></html>");
1360 output.write(summary.toString());
1361 }
catch (FileNotFoundException ex) {
1362 logger.log(Level.SEVERE,
"Could not find summary.html file to write to.");
1363 }
catch (UnsupportedEncodingException ex) {
1364 logger.log(Level.SEVERE,
"Did not recognize encoding when writing summary.hmtl.");
1365 }
catch (IOException ex) {
1366 logger.log(Level.SEVERE,
"Error creating Writer for summary.html.");
1368 logger.log(Level.WARNING,
"Unable to get current sleuthkit Case for the HTML report.");
1371 if (output != null) {
1375 }
catch (IOException ex) {
1381 "ReportHTML.writeSum.case=Case:",
1382 "ReportHTML.writeSum.caseNumber=Case Number:",
1383 "ReportHTML.writeSum.caseNumImages=Number of data sources in case:",
1384 "ReportHTML.writeSum.caseNotes=Notes:",
1385 "ReportHTML.writeSum.examiner=Examiner:"
1393 StringBuilder summary =
new StringBuilder();
1399 String caseNumber = currentCase.
getNumber();
1403 }
catch (TskCoreException ex) {
1412 summary.append(
"<div class=\"title\">\n");
1413 if (agencyLogoSet) {
1414 summary.append(
"<div class=\"left\">\n");
1415 summary.append(
"<img src=\"");
1416 summary.append(Paths.get(reportBranding.
getAgencyLogoPath()).getFileName().toString());
1417 summary.append(
"\" />\n");
1418 summary.append(
"</div>\n");
1420 final String align = agencyLogoSet ?
"right" :
"left";
1421 summary.append(
"<div class=\"").append(align).append(
"\">\n");
1422 summary.append(
"<table>\n");
1425 summary.append(
"<tr><td>").append(Bundle.ReportHTML_writeSum_case()).append(
"</td><td>")
1428 if (!caseNumber.isEmpty()) {
1429 summary.append(
"<tr><td>").append(Bundle.ReportHTML_writeSum_caseNumber()).append(
"</td><td>")
1433 summary.append(
"<tr><td>").append(Bundle.ReportHTML_writeSum_caseNumImages()).append(
"</td><td>")
1434 .append(imagecount).append(
"</td></tr>\n");
1436 if (!caseNotes.isEmpty()) {
1437 summary.append(
"<tr><td>").append(Bundle.ReportHTML_writeSum_caseNotes()).append(
"</td><td>")
1442 if (!examinerName.isEmpty()) {
1443 summary.append(
"<tr><td>").append(Bundle.ReportHTML_writeSum_examiner()).append(
"</td><td>")
1448 summary.append(
"</table>\n");
1449 summary.append(
"</div>\n");
1450 summary.append(
"<div class=\"clear\"></div>\n");
1451 summary.append(
"</div>\n");
1461 StringBuilder summary =
new StringBuilder();
1462 summary.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.imageInfoHeading"));
1463 summary.append(
"<div class=\"info\">\n");
1466 summary.append(
"<p>").append(c.getName()).append(
"</p>\n");
1467 if (c instanceof Image) {
1468 Image img = (Image) c;
1470 summary.append(
"<table>\n");
1471 summary.append(
"<tr><td>").append(
1472 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.timezone"))
1473 .append(
"</td><td>").append(img.getTimeZone()).append(
"</td></tr>\n");
1474 for (String imgPath : img.getPaths()) {
1475 summary.append(
"<tr><td>").append(
1476 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.path"))
1477 .append(
"</td><td>").append(imgPath).append(
"</td></tr>\n");
1479 summary.append(
"</table>\n");
1482 }
catch (TskCoreException ex) {
1483 logger.log(Level.WARNING,
"Unable to get image information for the HTML report.");
1485 summary.append(
"</div>\n");
1495 StringBuilder summary =
new StringBuilder();
1496 summary.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.softwareInfoHeading"));
1497 summary.append(
"<div class=\"info\">\n");
1498 summary.append(
"<table>\n");
1499 summary.append(
"<tr><td>").append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.autopsyVersion"))
1501 Map<Long, IngestModuleInfo> moduleInfoHashMap =
new HashMap<>();
1502 for (IngestJobInfo ingestJob : ingestJobs) {
1503 List<IngestModuleInfo> ingestModules = ingestJob.getIngestModuleInfo();
1504 for (IngestModuleInfo ingestModule : ingestModules) {
1505 if (!moduleInfoHashMap.containsKey(ingestModule.getIngestModuleId())) {
1506 moduleInfoHashMap.put(ingestModule.getIngestModuleId(), ingestModule);
1510 TreeMap<String, String> modules =
new TreeMap<>();
1511 for (IngestModuleInfo moduleinfo : moduleInfoHashMap.values()) {
1512 modules.put(moduleinfo.getDisplayName(), moduleinfo.getVersion());
1514 for (Map.Entry<String, String> module : modules.entrySet()) {
1515 summary.append(
"<tr><td>").append(module.getKey()).append(
" Module:")
1516 .append(
"</td><td>").append(module.getValue()).append(
"</td></tr>\n");
1518 summary.append(
"</table>\n");
1519 summary.append(
"</div>\n");
1520 summary.append(
"<div class=\"clear\"></div>\n");
1530 StringBuilder summary =
new StringBuilder();
1532 summary.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.ingestHistoryHeading"));
1533 summary.append(
"<div class=\"info\">\n");
1536 for (IngestJobInfo ingestJob : ingestJobs) {
1537 summary.append(
"<h3>Job ").append(jobnumber).append(
":</h3>\n");
1538 summary.append(
"<table>\n");
1539 summary.append(
"<tr><td>").append(
"Data Source:")
1540 .append(
"</td><td>").append(skCase.getContentById(ingestJob.getObjectId()).
getName()).append(
"</td></tr>\n");
1541 summary.append(
"<tr><td>").append(
"Status:")
1542 .append(
"</td><td>").append(ingestJob.getStatus()).append(
"</td></tr>\n");
1543 summary.append(
"<tr><td>").append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.modulesEnabledHeading"))
1544 .append(
"</td><td>");
1545 List<IngestModuleInfo> ingestModules = ingestJob.getIngestModuleInfo();
1546 summary.append(
"<ul>\n");
1547 for (IngestModuleInfo ingestModule : ingestModules) {
1548 summary.append(
"<li>").append(ingestModule.getDisplayName()).append(
"</li>");
1550 summary.append(
"</ul>\n");
1552 summary.append(
"</td></tr>\n");
1553 summary.append(
"</table>\n");
1555 summary.append(
"</div>\n");
1556 }
catch (TskCoreException ex) {
1557 logger.log(Level.WARNING,
"Unable to get ingest jobs for the HTML report.");
1579 File thumbFile = Paths.get(thumbsPath, fileName +
".png").toFile();
1580 if (bufferedThumb == null) {
1584 ImageIO.write(bufferedThumb,
"png", thumbFile);
1585 }
catch (IOException ex) {
1586 logger.log(Level.WARNING,
"Failed to write thumb file to report directory.", ex);
1589 if (thumbFile.exists()
1593 return THUMBS_REL_PATH
1594 + thumbFile.getName();
1606 String formattedString = StringEscapeUtils.escapeHtml4(text);
1607 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)