23 package org.sleuthkit.autopsy.report;
25 import java.awt.image.BufferedImage;
26 import java.io.BufferedWriter;
28 import java.io.FileNotFoundException;
29 import java.io.FileOutputStream;
30 import java.io.IOException;
31 import java.io.InputStream;
32 import java.io.OutputStream;
33 import java.io.OutputStreamWriter;
34 import java.io.UnsupportedEncodingException;
35 import java.io.Writer;
36 import java.nio.file.Files;
37 import java.nio.file.Path;
38 import java.nio.file.Paths;
39 import java.text.DateFormat;
40 import java.text.SimpleDateFormat;
41 import java.util.ArrayList;
42 import java.util.Date;
43 import java.util.HashMap;
44 import java.util.List;
47 import java.util.TreeMap;
48 import java.util.logging.Level;
49 import javax.imageio.ImageIO;
50 import javax.swing.JPanel;
51 import org.apache.commons.lang3.StringEscapeUtils;
52 import org.openide.filesystems.FileUtil;
53 import org.openide.util.NbBundle;
54 import org.openide.util.NbBundle.Messages;
72 import org.
sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
81 import org.
sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM;
83 class ReportHTML
implements TableReportModule {
85 private static final Logger logger = Logger.getLogger(ReportHTML.class.getName());
86 private static final String THUMBS_REL_PATH =
"thumbs" + File.separator;
87 private static ReportHTML instance;
88 private static final int MAX_THUMBS_PER_PAGE = 1000;
89 private static final String HTML_SUBDIR =
"content";
90 private Case currentCase;
91 static Integer THUMBNAIL_COLUMNS = 5;
93 private Map<String, Integer> dataTypes;
95 private String thumbsPath;
96 private String subPath;
97 private String currentDataType;
98 private Integer rowCount;
101 private ReportHTMLConfigurationPanel configPanel;
103 private final ReportBranding reportBranding;
106 public static synchronized ReportHTML getDefault() {
107 if (instance == null) {
108 instance =
new ReportHTML();
114 private ReportHTML() {
115 reportBranding =
new ReportBranding();
119 public JPanel getConfigurationPanel() {
120 if (configPanel == null) {
121 configPanel =
new ReportHTMLConfigurationPanel();
127 private void refresh() throws NoCurrentCaseException {
128 currentCase = Case.getCurrentCaseThrows();
130 dataTypes =
new TreeMap<>();
135 currentDataType =
"";
141 }
catch (IOException ex) {
153 private String dataTypeToFileName(String dataType) {
157 fileName = fileName.replaceAll(
" ",
"_");
166 private String useDataTypeIcon(String dataType) {
170 OutputStream output = null;
172 logger.log(Level.INFO,
"useDataTypeIcon: dataType = {0}", dataType);
175 BlackboardArtifact.ARTIFACT_TYPE artifactType = null;
176 for (ARTIFACT_TYPE v : ARTIFACT_TYPE.values()) {
177 if (v.getDisplayName().equals(dataType)) {
182 if (null != artifactType) {
184 iconFileName = dataTypeToFileName(artifactType.getDisplayName()) +
".png";
185 iconFilePath = subPath + File.separator + iconFileName;
188 switch (artifactType) {
189 case TSK_WEB_BOOKMARK:
190 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/bookmarks.png");
193 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/cookies.png");
195 case TSK_WEB_HISTORY:
196 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/history.png");
198 case TSK_WEB_DOWNLOAD:
199 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/downloads.png");
201 case TSK_RECENT_OBJECT:
202 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/recent.png");
204 case TSK_INSTALLED_PROG:
205 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/installed.png");
207 case TSK_KEYWORD_HIT:
208 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/keywords.png");
210 case TSK_HASHSET_HIT:
211 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/hash.png");
213 case TSK_DEVICE_ATTACHED:
214 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/devices.png");
216 case TSK_WEB_SEARCH_QUERY:
217 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/search.png");
219 case TSK_METADATA_EXIF:
220 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/exif.png");
223 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/userbookmarks.png");
225 case TSK_TAG_ARTIFACT:
226 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/userbookmarks.png");
228 case TSK_SERVICE_ACCOUNT:
229 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/account-icon-16.png");
232 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/contact.png");
235 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/message.png");
238 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/calllog.png");
240 case TSK_CALENDAR_ENTRY:
241 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/calendar.png");
243 case TSK_SPEED_DIAL_ENTRY:
244 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/speeddialentry.png");
246 case TSK_BLUETOOTH_PAIRING:
247 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/bluetooth.png");
249 case TSK_GPS_BOOKMARK:
250 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gpsfav.png");
252 case TSK_GPS_LAST_KNOWN_LOCATION:
253 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gps-lastlocation.png");
256 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gps-search.png");
259 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/computer.png");
261 case TSK_GPS_TRACKPOINT:
262 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gps_trackpoint.png");
265 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gps_trackpoint.png");
268 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/mail-icon-16.png");
270 case TSK_ENCRYPTION_SUSPECTED:
271 case TSK_ENCRYPTION_DETECTED:
272 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/encrypted-file.png");
274 case TSK_EXT_MISMATCH_DETECTED:
275 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/mismatch-16.png");
277 case TSK_INTERESTING_ARTIFACT_HIT:
278 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/interesting_item.png");
280 case TSK_INTERESTING_FILE_HIT:
281 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/interesting_item.png");
284 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/installed.png");
286 case TSK_REMOTE_DRIVE:
287 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/drive_network.png");
290 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/accounts.png");
292 case TSK_WIFI_NETWORK:
293 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/network-wifi.png");
295 case TSK_WIFI_NETWORK_ADAPTER:
296 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/network-wifi.png");
298 case TSK_SIM_ATTACHED:
299 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/sim_card.png");
301 case TSK_BLUETOOTH_ADAPTER:
302 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/Bluetooth.png");
304 case TSK_DEVICE_INFO:
305 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/devices.png");
307 case TSK_VERIFICATION_FAILED:
308 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/validationFailed.png");
311 logger.log(Level.WARNING,
"useDataTypeIcon: unhandled artifact type = {0}", dataType);
312 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/star.png");
313 iconFileName =
"star.png";
314 iconFilePath = subPath + File.separator + iconFileName;
317 }
else if (dataType.startsWith(ARTIFACT_TYPE.TSK_ACCOUNT.getDisplayName())) {
325 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/accounts.png");
326 iconFileName =
"accounts.png";
327 iconFilePath = subPath + File.separator + iconFileName;
329 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/star.png");
330 iconFileName =
"star.png";
331 iconFilePath = subPath + File.separator + iconFileName;
335 output =
new FileOutputStream(iconFilePath);
336 FileUtil.copy(in, output);
339 }
catch (IOException ex) {
340 logger.log(Level.SEVERE,
"Failed to extract images for HTML report.", ex);
342 if (output != null) {
346 }
catch (IOException ex) {
352 }
catch (IOException ex) {
367 public void startReport(String baseReportDir) {
369 ModuleSettings.setConfigSetting(
"HTMLReport",
"header", configPanel.getHeader());
370 ModuleSettings.setConfigSetting(
"HTMLReport",
"footer", configPanel.getFooter());
375 }
catch (NoCurrentCaseException ex) {
376 logger.log(Level.SEVERE,
"Exception while getting open case.");
380 this.path = baseReportDir;
381 this.subPath = this.path + HTML_SUBDIR + File.separator;
382 this.thumbsPath = this.subPath + THUMBS_REL_PATH;
384 FileUtil.createFolder(
new File(this.subPath));
385 FileUtil.createFolder(
new File(this.thumbsPath));
386 }
catch (IOException ex) {
387 logger.log(Level.SEVERE,
"Unable to make HTML report folder.");
400 public void endReport() {
405 }
catch (IOException ex) {
406 logger.log(Level.WARNING,
"Could not close the output writer when ending report.", ex);
420 public void startDataType(String name, String description) {
421 String title = dataTypeToFileName(name);
423 out =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(subPath + title +
".html"),
"UTF-8"));
424 }
catch (FileNotFoundException ex) {
425 logger.log(Level.SEVERE,
"File not found: {0}", ex);
426 }
catch (UnsupportedEncodingException ex) {
427 logger.log(Level.SEVERE,
"Unrecognized encoding");
431 StringBuilder page =
new StringBuilder();
432 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")
433 .append(writePageHeader())
434 .append(
"<div id=\"header\">").append(name).append(
"</div>\n")
435 .append(
"<div id=\"content\">\n");
436 if (!description.isEmpty()) {
437 page.append(
"<p><strong>");
438 page.append(description);
439 page.append(
"</strong></p>\n");
441 out.write(page.toString());
442 currentDataType = name;
444 }
catch (IOException ex) {
445 logger.log(Level.SEVERE,
"Failed to write page head: {0}", ex);
454 public void endDataType() {
455 dataTypes.put(currentDataType, rowCount);
457 StringBuilder builder =
new StringBuilder();
458 builder.append(writePageFooter());
459 builder.append(
"</div>\n</body>\n</html>\n");
460 out.write(builder.toString());
461 }
catch (IOException ex) {
462 logger.log(Level.SEVERE,
"Failed to write end of HTML report.", ex);
468 }
catch (IOException ex) {
469 logger.log(Level.WARNING,
"Could not close the output writer when ending data type.", ex);
482 private String writePageHeader() {
483 StringBuilder output =
new StringBuilder();
484 String pageHeader = configPanel.getHeader();
485 if (pageHeader.isEmpty() ==
false) {
486 output.append(
"<div id=\"pageHeaderFooter\">")
487 .append(StringEscapeUtils.escapeHtml4(pageHeader))
490 return output.toString();
499 private String writePageFooter() {
500 StringBuilder output =
new StringBuilder();
501 String pageFooter = configPanel.getFooter();
502 if (pageFooter.isEmpty() ==
false) {
503 output.append(
"<br/><div id=\"pageHeaderFooter\">")
504 .append(StringEscapeUtils.escapeHtml4(pageFooter))
507 return output.toString();
516 public void startSet(String setName) {
517 StringBuilder set =
new StringBuilder();
518 set.append(
"<h1><a name=\"").append(setName).append(
"\">").append(setName).append(
"</a></h1>\n");
519 set.append(
"<div class=\"keyword_list\">\n");
522 out.write(set.toString());
523 }
catch (IOException ex) {
524 logger.log(Level.SEVERE,
"Failed to write set: {0}", ex);
532 public void endSet() {
534 out.write(
"</div>\n");
535 }
catch (IOException ex) {
536 logger.log(Level.SEVERE,
"Failed to write end of set: {0}", ex);
546 public void addSetIndex(List<String> sets) {
547 StringBuilder index =
new StringBuilder();
548 index.append(
"<ul>\n");
549 for (String set : sets) {
550 index.append(
"\t<li><a href=\"#").append(set).append(
"\">").append(set).append(
"</a></li>\n");
552 index.append(
"</ul>\n");
554 out.write(index.toString());
555 }
catch (IOException ex) {
556 logger.log(Level.SEVERE,
"Failed to add set index: {0}", ex);
566 public void addSetElement(String elementName) {
568 out.write(
"<h4>" + elementName +
"</h4>\n");
569 }
catch (IOException ex) {
570 logger.log(Level.SEVERE,
"Failed to write set element: {0}", ex);
580 public void startTable(List<String> titles) {
581 StringBuilder ele =
new StringBuilder();
582 ele.append(
"<table>\n<thead>\n\t<tr>\n");
583 for (String title : titles) {
584 ele.append(
"\t\t<th>").append(title).append(
"</th>\n");
586 ele.append(
"\t</tr>\n</thead>\n");
589 out.write(ele.toString());
590 }
catch (IOException ex) {
591 logger.log(Level.SEVERE,
"Failed to write table start: {0}", ex);
602 public void startContentTagsTable(List<String> columnHeaders) {
603 StringBuilder htmlOutput =
new StringBuilder();
604 htmlOutput.append(
"<table>\n<thead>\n\t<tr>\n");
607 for (String columnHeader : columnHeaders) {
608 htmlOutput.append(
"\t\t<th>").append(columnHeader).append(
"</th>\n");
612 htmlOutput.append(
"\t\t<th></th>\n");
614 htmlOutput.append(
"\t</tr>\n</thead>\n");
617 out.write(htmlOutput.toString());
618 }
catch (IOException ex) {
619 logger.log(Level.SEVERE,
"Failed to write table start: {0}", ex);
627 public void endTable() {
629 out.write(
"</table>\n");
630 }
catch (IOException ex) {
631 logger.log(Level.SEVERE,
"Failed to write end of table: {0}", ex);
642 public void addRow(List<String> row) {
653 private void addRow(List<String> row,
boolean escapeText) {
654 StringBuilder builder =
new StringBuilder();
655 builder.append(
"\t<tr>\n");
656 for (String cell : row) {
657 String cellText = escapeText ? EscapeUtil.escapeHtml(cell) : cell;
658 builder.append(
"\t\t<td>").append(cellText).append(
"</td>\n");
660 builder.append(
"\t</tr>\n");
664 out.write(builder.toString());
665 }
catch (IOException ex) {
666 logger.log(Level.SEVERE,
"Failed to write row to out.", ex);
667 }
catch (NullPointerException ex) {
668 logger.log(Level.SEVERE,
"Output writer is null. Page was not initialized before writing.", ex);
682 public void addRowWithTaggedContentHyperlink(List<String> row, ContentTag contentTag) {
683 Content content = contentTag.getContent();
684 if (content instanceof AbstractFile ==
false) {
688 AbstractFile file = (AbstractFile) content;
690 StringBuilder localFileLink =
new StringBuilder();
693 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS
694 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS)) {
695 localFileLink.append(
"<a href=\"");
697 String localFilePath = saveContent(file, contentTag.getName().getDisplayName());
698 localFileLink.append(localFilePath);
699 localFileLink.append(
"\" target=\"_top\">");
702 StringBuilder builder =
new StringBuilder();
703 builder.append(
"\t<tr>\n");
704 int positionCounter = 0;
705 for (String cell : row) {
707 switch (positionCounter) {
710 builder.append(
"\t\t<td class=\"left_align_cell\">").append(localFileLink.toString()).append(cell).append(
"</a></td>\n");
714 builder.append(
"\t\t<td class=\"right_align_cell\">").append(cell).append(
"</td>\n");
718 builder.append(
"\t\t<td>").append(cell).append(
"</td>\n");
723 builder.append(
"\t</tr>\n");
727 out.write(builder.toString());
728 }
catch (IOException ex) {
729 logger.log(Level.SEVERE,
"Failed to write row to out.", ex);
730 }
catch (NullPointerException ex) {
731 logger.log(Level.SEVERE,
"Output writer is null. Page was not initialized before writing.", ex);
740 public void addThumbnailRows(Set<Content> images) {
741 List<String> currentRow =
new ArrayList<>();
744 for (Content content : images) {
745 if (currentRow.size() == THUMBNAIL_COLUMNS) {
746 addRow(currentRow,
false);
750 if (totalCount == MAX_THUMBS_PER_PAGE) {
754 rowCount = totalCount;
759 startDataType(NbBundle.getMessage(
this.getClass(),
"ReportHTML.addThumbRows.dataType.title", pages),
760 NbBundle.getMessage(
this.getClass(),
"ReportHTML.addThumbRows.dataType.msg"));
761 List<String> emptyHeaders =
new ArrayList<>();
762 for (
int i = 0; i < THUMBNAIL_COLUMNS; i++) {
763 emptyHeaders.add(
"");
765 startTable(emptyHeaders);
768 if (failsContentCheck(content)) {
772 AbstractFile file = (AbstractFile) content;
775 String thumbnailPath = prepareThumbnail(file);
776 if (thumbnailPath == null) {
779 String contentPath = saveContent(file,
"thumbs_fullsize");
782 nameInImage = file.getUniquePath();
783 }
catch (TskCoreException ex) {
784 nameInImage = file.getName();
787 StringBuilder linkToThumbnail =
new StringBuilder();
788 linkToThumbnail.append(
"<div id='thumbnail_link'>");
789 linkToThumbnail.append(
"<a href=\"");
790 linkToThumbnail.append(contentPath);
791 linkToThumbnail.append(
"\" target=\"_top\">");
792 linkToThumbnail.append(
"<img src=\"").append(thumbnailPath).append(
"\" title=\"").append(nameInImage).append(
"\"/>");
793 linkToThumbnail.append(
"</a><br>");
794 linkToThumbnail.append(file.getName()).append(
"<br>");
796 Services services = currentCase.getServices();
797 TagsManager tagsManager = services.getTagsManager();
799 List<ContentTag> tags = tagsManager.getContentTagsByContent(content);
800 if (tags.size() > 0) {
801 linkToThumbnail.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.thumbLink.tags"));
803 for (
int i = 0; i < tags.size(); i++) {
804 ContentTag tag = tags.get(i);
805 String notableString = tag.getName().getKnownStatus() == TskData.FileKnown.BAD ? TagsManager.getNotableTagLabel() :
"";
806 linkToThumbnail.append(tag.getName().getDisplayName()).append(notableString);
807 if (i != tags.size() - 1) {
808 linkToThumbnail.append(
", ");
811 }
catch (TskCoreException ex) {
812 logger.log(Level.WARNING,
"Could not find get tags for file.", ex);
814 linkToThumbnail.append(
"</div>");
815 currentRow.add(linkToThumbnail.toString());
820 if (currentRow.isEmpty() ==
false) {
821 int extraCells = THUMBNAIL_COLUMNS - currentRow.size();
822 for (
int i = 0; i < extraCells; i++) {
826 addRow(currentRow,
false);
830 rowCount = totalCount;
833 private boolean failsContentCheck(Content c) {
834 if (c instanceof AbstractFile ==
false) {
837 AbstractFile file = (AbstractFile) c;
839 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS
840 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS;
852 public String saveContent(AbstractFile file, String dirName) {
857 StringBuilder localFilePath =
new StringBuilder();
859 localFilePath.append(subPath);
860 localFilePath.append(dirName2);
861 File localFileFolder =
new File(localFilePath.toString());
862 if (!localFileFolder.exists()) {
863 localFileFolder.mkdirs();
874 String objectIdSuffix =
"_" + file.getId();
875 int lastDotIndex = fileName.lastIndexOf(
".");
876 if (lastDotIndex != -1 && lastDotIndex != 0) {
878 fileName = fileName.substring(0, lastDotIndex) + objectIdSuffix + fileName.substring(lastDotIndex, fileName.length());
882 fileName += objectIdSuffix;
884 localFilePath.append(File.separator);
885 localFilePath.append(fileName);
889 File localFile =
new File(localFilePath.toString());
890 if (!localFile.exists()) {
891 ExtractFscContentVisitor.extract(file, localFile, null, null);
895 return localFilePath.toString().substring(subPath.length());
906 public String dateToString(
long date) {
907 SimpleDateFormat sdf =
new java.text.SimpleDateFormat(
"yyyy/MM/dd HH:mm:ss");
908 return sdf.format(
new java.util.Date(date * 1000));
912 public String getRelativeFilePath() {
913 return "report.html";
917 public String getName() {
918 return NbBundle.getMessage(this.getClass(),
"ReportHTML.getName.text");
922 public String getDescription() {
923 return NbBundle.getMessage(this.getClass(),
"ReportHTML.getDesc.text");
929 private void writeCss() {
930 Writer cssOut = null;
932 cssOut =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(subPath +
"index.css"),
"UTF-8"));
933 String css =
"body {margin: 0px; padding: 0px; background: #FFFFFF; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353;}\n"
935 "#content {padding: 30px;}\n"
937 "#header {width:100%; padding: 10px; line-height: 25px; background: #07A; color: #FFF; font-size: 20px;}\n"
939 "#pageHeaderFooter {width: 100%; padding: 10px; line-height: 25px; text-align: center; font-size: 20px;}\n"
941 "h1 {font-size: 20px; font-weight: normal; color: #07A; padding: 0 0 7px 0; margin-top: 25px; border-bottom: 1px solid #D6D6D6;}\n"
943 "h2 {font-size: 20px; font-weight: bolder; color: #07A;}\n"
945 "h3 {font-size: 16px; color: #07A;}\n"
947 "h4 {background: #07A; color: #FFF; font-size: 16px; margin: 0 0 0 25px; padding: 0; padding-left: 15px;}\n"
949 "ul.nav {list-style-type: none; line-height: 35px; padding: 0px; margin-left: 15px;}\n"
951 "ul li a {font-size: 14px; color: #444; text-decoration: none; padding-left: 25px;}\n"
953 "ul li a:hover {text-decoration: underline;}\n"
955 "p {margin: 0 0 20px 0;}\n"
957 "table {white-space:nowrap; min-width: 700px; padding: 2; margin: 0; border-collapse: collapse; border-bottom: 2px solid #e5e5e5;}\n"
959 ".keyword_list table {margin: 0 0 25px 25px; border-bottom: 2px solid #dedede;}\n"
961 "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"
963 "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"
965 "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"
967 "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"
969 "table tr:nth-child(even) td {background: #f3f3f3;}\n"
971 "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;}";
973 }
catch (FileNotFoundException ex) {
974 logger.log(Level.SEVERE,
"Could not find index.css file to write to.", ex);
975 }
catch (UnsupportedEncodingException ex) {
976 logger.log(Level.SEVERE,
"Did not recognize encoding when writing index.css.", ex);
977 }
catch (IOException ex) {
978 logger.log(Level.SEVERE,
"Error creating Writer for index.css.", ex);
981 if (cssOut != null) {
985 }
catch (IOException ex) {
993 private void writeIndex() {
994 Writer indexOut = null;
995 String indexFilePath = path +
"report.html";
998 openCase = Case.getCurrentCaseThrows();
999 }
catch (NoCurrentCaseException ex) {
1000 logger.log(Level.SEVERE,
"Exception while getting open case.", ex);
1004 indexOut =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(indexFilePath),
"UTF-8"));
1005 StringBuilder index =
new StringBuilder();
1006 final String reportTitle = reportBranding.getReportTitle();
1007 String iconPath = reportBranding.getAgencyLogoPath();
1008 if (iconPath == null) {
1010 iconPath = HTML_SUBDIR +
"favicon.ico";
1012 iconPath = Paths.get(reportBranding.getAgencyLogoPath()).getFileName().toString();
1014 index.append(
"<head>\n<title>").append(reportTitle).append(
" ").append(
1015 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeIndex.title", currentCase.getDisplayName())).append(
1017 index.append(
"<link rel=\"icon\" type=\"image/ico\" href=\"")
1018 .append(iconPath).append(
"\" />\n");
1019 index.append(
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n");
1020 index.append(
"</head>\n");
1021 index.append(
"<frameset cols=\"350px,*\">\n");
1022 index.append(
"<frame src=\"" + HTML_SUBDIR).append(File.separator).append(
"nav.html\" name=\"nav\">\n");
1023 index.append(
"<frame src=\"" + HTML_SUBDIR).append(File.separator).append(
"summary.html\" name=\"content\">\n");
1024 index.append(
"<noframes>").append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeIndex.noFrames.msg")).append(
"<br />\n");
1025 index.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeIndex.noFrames.seeNav")).append(
"<br />\n");
1026 index.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeIndex.seeSum")).append(
"</noframes>\n");
1027 index.append(
"</frameset>\n");
1028 index.append(
"</html>");
1029 indexOut.write(index.toString());
1030 openCase.addReport(indexFilePath, NbBundle.getMessage(
this.getClass(),
1031 "ReportHTML.writeIndex.srcModuleName.text"),
"");
1032 }
catch (IOException ex) {
1033 logger.log(Level.SEVERE,
"Error creating Writer for report.html: {0}", ex);
1034 }
catch (TskCoreException ex) {
1035 String errorMessage = String.format(
"Error adding %s to case as a report", indexFilePath);
1036 logger.log(Level.SEVERE, errorMessage, ex);
1039 if (indexOut != null) {
1043 }
catch (IOException ex) {
1051 private void writeNav() {
1052 Writer navOut = null;
1054 navOut =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(subPath +
"nav.html"),
"UTF-8"));
1055 StringBuilder nav =
new StringBuilder();
1056 nav.append(
"<html>\n<head>\n\t<title>").append(
1057 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeNav.title"))
1058 .append(
"</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"index.css\" />\n");
1059 nav.append(
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n</head>\n<body>\n");
1060 nav.append(
"<div id=\"content\">\n<h1>").append(
1061 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeNav.h1")).append(
"</h1>\n");
1062 nav.append(
"<ul class=\"nav\">\n");
1063 nav.append(
"<li style=\"background: url(summary.png) left center no-repeat;\"><a href=\"summary.html\" target=\"content\">")
1064 .append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeNav.summary")).append(
"</a></li>\n");
1066 for (String dataType : dataTypes.keySet()) {
1067 String dataTypeEsc = dataTypeToFileName(dataType);
1068 String iconFileName = useDataTypeIcon(dataType);
1069 nav.append(
"<li style=\"background: url('").append(iconFileName)
1070 .append(
"') left center no-repeat;\"><a href=\"")
1071 .append(dataTypeEsc).append(
".html\" target=\"content\">")
1072 .append(dataType).append(
" (").append(dataTypes.get(dataType))
1073 .append(
")</a></li>\n");
1075 nav.append(
"</ul>\n");
1076 nav.append(
"</div>\n</body>\n</html>");
1077 navOut.write(nav.toString());
1078 }
catch (IOException ex) {
1079 logger.log(Level.SEVERE,
"Failed to write end of report navigation menu: {0}", ex);
1081 if (navOut != null) {
1085 }
catch (IOException ex) {
1086 logger.log(Level.WARNING,
"Could not close navigation out writer.");
1091 InputStream in = null;
1092 OutputStream output = null;
1096 String generatorLogoPath = reportBranding.getGeneratorLogoPath();
1097 if (generatorLogoPath != null && !generatorLogoPath.isEmpty()) {
1098 File from =
new File(generatorLogoPath);
1099 File to =
new File(subPath);
1100 FileUtil.copyFile(FileUtil.toFileObject(from), FileUtil.toFileObject(to),
"generator_logo");
1103 String agencyLogoPath = reportBranding.getAgencyLogoPath();
1104 if (agencyLogoPath != null && !agencyLogoPath.isEmpty()) {
1105 Path destinationPath = Paths.get(subPath);
1106 Files.copy(Files.newInputStream(Paths.get(agencyLogoPath)), destinationPath.resolve(Paths.get(agencyLogoPath).getFileName()));
1109 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/favicon.ico");
1110 output =
new FileOutputStream(
new File(subPath +
"favicon.ico"));
1111 FileUtil.copy(in, output);
1115 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/summary.png");
1116 output =
new FileOutputStream(
new File(subPath +
"summary.png"));
1117 FileUtil.copy(in, output);
1121 }
catch (IOException ex) {
1122 logger.log(Level.SEVERE,
"Failed to extract images for HTML report.", ex);
1124 if (output != null) {
1128 }
catch (IOException ex) {
1134 }
catch (IOException ex) {
1143 private void writeSummary() {
1144 Writer output = null;
1146 output =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(subPath +
"summary.html"),
"UTF-8"));
1147 StringBuilder head =
new StringBuilder();
1148 head.append(
"<html>\n<head>\n<title>").append(
1149 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.title")).append(
"</title>\n");
1150 head.append(
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n");
1151 head.append(
"<style type=\"text/css\">\n");
1152 head.append(
"#pageHeaderFooter {width: 100%; padding: 10px; line-height: 25px; text-align: center; font-size: 20px;}\n");
1153 head.append(
"body { padding: 0px; margin: 0px; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353; }\n");
1154 head.append(
"#wrapper { width: 90%; margin: 0px auto; margin-top: 35px; }\n");
1155 head.append(
"h1 { color: #07A; font-size: 36px; line-height: 42px; font-weight: normal; margin: 0px; border-bottom: 1px solid #81B9DB; }\n");
1156 head.append(
"h1 span { color: #F00; display: block; font-size: 16px; font-weight: bold; line-height: 22px;}\n");
1157 head.append(
"h2 { padding: 0 0 3px 0; margin: 0px; color: #07A; font-weight: normal; border-bottom: 1px dotted #81B9DB; }\n");
1158 head.append(
"h3 { padding: 5 0 3px 0; margin: 0px; color: #07A; font-weight: normal; }\n");
1159 head.append(
"table td { padding: 5px 25px 5px 0px; vertical-align:top;}\n");
1160 head.append(
"p.subheadding { padding: 0px; margin: 0px; font-size: 11px; color: #B5B5B5; }\n");
1161 head.append(
".title { width: 660px; margin-bottom: 50px; }\n");
1162 head.append(
".left { float: left; width: 250px; margin-top: 20px; text-align: center; }\n");
1163 head.append(
".left img { max-width: 250px; max-height: 250px; min-width: 200px; min-height: 200px; }\n");
1164 head.append(
".right { float: right; width: 385px; margin-top: 25px; font-size: 14px; }\n");
1165 head.append(
".clear { clear: both; }\n");
1166 head.append(
".info { padding: 10px 0;}\n");
1167 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");
1168 head.append(
".info table { margin: 10px 25px 10px 25px; }\n");
1169 head.append(
"ul {padding: 0;margin: 0;list-style-type: none;}");
1170 head.append(
"li {padding-bottom: 5px;}");
1171 head.append(
"</style>\n");
1172 head.append(
"</head>\n<body>\n");
1173 output.write(head.toString());
1175 DateFormat datetimeFormat =
new SimpleDateFormat(
"yyyy/MM/dd HH:mm:ss");
1176 Date date =
new Date();
1177 String datetime = datetimeFormat.format(date);
1179 StringBuilder summary =
new StringBuilder();
1180 boolean running =
false;
1181 if (IngestManager.getInstance().isIngestRunning()) {
1184 SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
1185 List<IngestJobInfo> ingestJobs = skCase.getIngestJobs();
1186 final String reportTitle = reportBranding.getReportTitle();
1187 final String reportFooter = reportBranding.getReportFooter();
1188 final boolean generatorLogoSet = reportBranding.getGeneratorLogoPath() != null && !reportBranding.getGeneratorLogoPath().isEmpty();
1190 summary.append(
"<div id=\"wrapper\">\n");
1191 summary.append(writePageHeader());
1192 summary.append(
"<h1>").append(reportTitle)
1193 .append(running ? NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.warningMsg") :
"")
1195 summary.append(
"<p class=\"subheadding\">").append(
1196 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.reportGenOn.text", datetime)).append(
"</p>\n");
1197 summary.append(
"<div class=\"title\">\n");
1198 summary.append(writeSummaryCaseDetails());
1199 summary.append(writeSummaryImageInfo());
1200 summary.append(writeSummarySoftwareInfo(skCase, ingestJobs));
1201 summary.append(writeSummaryIngestHistoryInfo(skCase, ingestJobs));
1202 if (generatorLogoSet) {
1203 summary.append(
"<div class=\"left\">\n");
1204 summary.append(
"<img src=\"generator_logo.png\" />\n");
1205 summary.append(
"</div>\n");
1207 summary.append(
"<div class=\"clear\"></div>\n");
1208 if (reportFooter != null) {
1209 summary.append(
"<p class=\"subheadding\">").append(reportFooter).append(
"</p>\n");
1211 summary.append(
"</div>\n");
1212 summary.append(writePageFooter());
1213 summary.append(
"</body></html>");
1214 output.write(summary.toString());
1215 }
catch (FileNotFoundException ex) {
1216 logger.log(Level.SEVERE,
"Could not find summary.html file to write to.");
1217 }
catch (UnsupportedEncodingException ex) {
1218 logger.log(Level.SEVERE,
"Did not recognize encoding when writing summary.hmtl.");
1219 }
catch (IOException ex) {
1220 logger.log(Level.SEVERE,
"Error creating Writer for summary.html.");
1221 }
catch (NoCurrentCaseException | TskCoreException ex) {
1222 logger.log(Level.WARNING,
"Unable to get current sleuthkit Case for the HTML report.");
1225 if (output != null) {
1229 }
catch (IOException ex) {
1235 "ReportHTML.writeSum.case=Case:",
1236 "ReportHTML.writeSum.caseNumber=Case Number:",
1237 "ReportHTML.writeSum.caseNumImages=Number of Images:",
1238 "ReportHTML.writeSum.caseNotes=Notes:",
1239 "ReportHTML.writeSum.examiner=Examiner:"
1246 private StringBuilder writeSummaryCaseDetails() {
1247 StringBuilder summary =
new StringBuilder();
1249 final boolean agencyLogoSet = reportBranding.getAgencyLogoPath() != null && !reportBranding.getAgencyLogoPath().isEmpty();
1252 String caseName = currentCase.getDisplayName();
1253 String caseNumber = currentCase.getNumber();
1256 imagecount = currentCase.getDataSources().size();
1257 }
catch (TskCoreException ex) {
1260 String caseNotes = currentCase.getCaseNotes();
1263 String examinerName = currentCase.getExaminer();
1266 summary.append(
"<div class=\"title\">\n");
1267 if (agencyLogoSet) {
1268 summary.append(
"<div class=\"left\">\n");
1269 summary.append(
"<img src=\"");
1270 summary.append(Paths.get(reportBranding.getAgencyLogoPath()).getFileName().toString());
1271 summary.append(
"\" />\n");
1272 summary.append(
"</div>\n");
1274 final String align = agencyLogoSet ?
"right" :
"left";
1275 summary.append(
"<div class=\"").append(align).append(
"\">\n");
1276 summary.append(
"<table>\n");
1279 summary.append(
"<tr><td>").append(Bundle.ReportHTML_writeSum_case()).append(
"</td><td>")
1280 .append(formatHtmlString(caseName)).append(
"</td></tr>\n");
1282 if (!caseNumber.isEmpty()) {
1283 summary.append(
"<tr><td>").append(Bundle.ReportHTML_writeSum_caseNumber()).append(
"</td><td>")
1284 .append(formatHtmlString(caseNumber)).append(
"</td></tr>\n");
1287 summary.append(
"<tr><td>").append(Bundle.ReportHTML_writeSum_caseNumImages()).append(
"</td><td>")
1288 .append(imagecount).append(
"</td></tr>\n");
1290 if (!caseNotes.isEmpty()) {
1291 summary.append(
"<tr><td>").append(Bundle.ReportHTML_writeSum_caseNotes()).append(
"</td><td>")
1292 .append(formatHtmlString(caseNotes)).append(
"</td></tr>\n");
1296 if (!examinerName.isEmpty()) {
1297 summary.append(
"<tr><td>").append(Bundle.ReportHTML_writeSum_examiner()).append(
"</td><td>")
1298 .append(formatHtmlString(examinerName)).append(
"</td></tr>\n");
1302 summary.append(
"</table>\n");
1303 summary.append(
"</div>\n");
1304 summary.append(
"<div class=\"clear\"></div>\n");
1305 summary.append(
"</div>\n");
1314 private StringBuilder writeSummaryImageInfo() {
1315 StringBuilder summary =
new StringBuilder();
1316 summary.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.imageInfoHeading"));
1317 summary.append(
"<div class=\"info\">\n");
1319 for (Content c : currentCase.getDataSources()) {
1320 summary.append(
"<p>").append(c.getName()).append(
"</p>\n");
1321 if (c instanceof Image) {
1322 Image img = (Image) c;
1324 summary.append(
"<table>\n");
1325 summary.append(
"<tr><td>").append(
1326 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.timezone"))
1327 .append(
"</td><td>").append(img.getTimeZone()).append(
"</td></tr>\n");
1328 for (String imgPath : img.getPaths()) {
1329 summary.append(
"<tr><td>").append(
1330 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.path"))
1331 .append(
"</td><td>").append(imgPath).append(
"</td></tr>\n");
1333 summary.append(
"</table>\n");
1336 }
catch (TskCoreException ex) {
1337 logger.log(Level.WARNING,
"Unable to get image information for the HTML report.");
1339 summary.append(
"</div>\n");
1348 private StringBuilder writeSummarySoftwareInfo(SleuthkitCase skCase, List<IngestJobInfo> ingestJobs) {
1349 StringBuilder summary =
new StringBuilder();
1350 summary.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.softwareInfoHeading"));
1351 summary.append(
"<div class=\"info\">\n");
1352 summary.append(
"<table>\n");
1353 summary.append(
"<tr><td>").append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.autopsyVersion"))
1354 .append(
"</td><td>").append(Version.getVersion()).append(
"</td></tr>\n");
1355 Map<Long, IngestModuleInfo> moduleInfoHashMap =
new HashMap<>();
1356 for (IngestJobInfo ingestJob : ingestJobs) {
1357 List<IngestModuleInfo> ingestModules = ingestJob.getIngestModuleInfo();
1358 for (IngestModuleInfo ingestModule : ingestModules) {
1359 if (!moduleInfoHashMap.containsKey(ingestModule.getIngestModuleId())) {
1360 moduleInfoHashMap.put(ingestModule.getIngestModuleId(), ingestModule);
1364 TreeMap<String, String> modules =
new TreeMap<>();
1365 for (IngestModuleInfo moduleinfo : moduleInfoHashMap.values()) {
1366 modules.put(moduleinfo.getDisplayName(), moduleinfo.getVersion());
1368 for (Map.Entry<String, String> module : modules.entrySet()) {
1369 summary.append(
"<tr><td>").append(module.getKey()).append(
" Module:")
1370 .append(
"</td><td>").append(module.getValue()).append(
"</td></tr>\n");
1372 summary.append(
"</table>\n");
1373 summary.append(
"</div>\n");
1374 summary.append(
"<div class=\"clear\"></div>\n");
1383 private StringBuilder writeSummaryIngestHistoryInfo(SleuthkitCase skCase, List<IngestJobInfo> ingestJobs) {
1384 StringBuilder summary =
new StringBuilder();
1386 summary.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.ingestHistoryHeading"));
1387 summary.append(
"<div class=\"info\">\n");
1390 for (IngestJobInfo ingestJob : ingestJobs) {
1391 summary.append(
"<h3>Job ").append(jobnumber).append(
":</h3>\n");
1392 summary.append(
"<table>\n");
1393 summary.append(
"<tr><td>").append(
"Data Source:")
1394 .append(
"</td><td>").append(skCase.getContentById(ingestJob.getObjectId()).getName()).append(
"</td></tr>\n");
1395 summary.append(
"<tr><td>").append(
"Status:")
1396 .append(
"</td><td>").append(ingestJob.getStatus()).append(
"</td></tr>\n");
1397 summary.append(
"<tr><td>").append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.modulesEnabledHeading"))
1398 .append(
"</td><td>");
1399 List<IngestModuleInfo> ingestModules = ingestJob.getIngestModuleInfo();
1400 summary.append(
"<ul>\n");
1401 for (IngestModuleInfo ingestModule : ingestModules) {
1402 summary.append(
"<li>").append(ingestModule.getDisplayName()).append(
"</li>");
1404 summary.append(
"</ul>\n");
1406 summary.append(
"</td></tr>\n");
1407 summary.append(
"</table>\n");
1409 summary.append(
"</div>\n");
1410 }
catch (TskCoreException ex) {
1411 logger.log(Level.WARNING,
"Unable to get ingest jobs for the HTML report.");
1424 private String prepareThumbnail(AbstractFile file) {
1425 BufferedImage bufferedThumb = ImageUtils.getThumbnail(file, ImageUtils.ICON_SIZE_MEDIUM);
1433 File thumbFile = Paths.get(thumbsPath, fileName +
".png").toFile();
1434 if (bufferedThumb == null) {
1438 ImageIO.write(bufferedThumb,
"png", thumbFile);
1439 }
catch (IOException ex) {
1440 logger.log(Level.WARNING,
"Failed to write thumb file to report directory.", ex);
1443 if (thumbFile.exists()
1447 return THUMBS_REL_PATH
1448 + thumbFile.getName();
1459 private String formatHtmlString(String text) {
1460 String formattedString = StringEscapeUtils.escapeHtml4(text);
1461 return formattedString.replaceAll(
"(\r\n|\r|\n|\n\r)",
"<br>");
static String escapeFileName(String fileName)