Autopsy 4.22.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
HTMLReport.java
Go to the documentation of this file.
1/*
2 *
3 * Autopsy Forensic Browser
4 *
5 * Copyright 2012-2018 Basis Technology Corp.
6 *
7 * Copyright 2012 42six Solutions.
8 * Contact: aebadirad <at> 42six <dot> com
9 * Project Contact/Architect: carrier <at> sleuthkit <dot> org
10 *
11 * Licensed under the Apache License, Version 2.0 (the "License");
12 * you may not use this file except in compliance with the License.
13 * You may obtain a copy of the License at
14 *
15 * http://www.apache.org/licenses/LICENSE-2.0
16 *
17 * Unless required by applicable law or agreed to in writing, software
18 * distributed under the License is distributed on an "AS IS" BASIS,
19 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 * See the License for the specific language governing permissions and
21 * limitations under the License.
22 */
23package org.sleuthkit.autopsy.report.modules.html;
24
25import org.sleuthkit.autopsy.report.NoReportModuleSettings;
26import org.sleuthkit.autopsy.report.ReportModuleSettings;
27import java.awt.image.BufferedImage;
28import java.io.BufferedWriter;
29import java.io.File;
30import java.io.FileNotFoundException;
31import java.io.FileOutputStream;
32import java.io.IOException;
33import java.io.InputStream;
34import java.io.OutputStream;
35import java.io.OutputStreamWriter;
36import java.io.UnsupportedEncodingException;
37import java.io.Writer;
38import java.nio.file.Files;
39import java.nio.file.Path;
40import java.nio.file.Paths;
41import java.text.DateFormat;
42import java.text.SimpleDateFormat;
43import java.util.ArrayList;
44import java.util.Date;
45import java.util.HashMap;
46import java.util.List;
47import java.util.Map;
48import java.util.Set;
49import java.util.TreeMap;
50import java.util.concurrent.ExecutionException;
51import java.util.logging.Level;
52import javax.imageio.ImageIO;
53import javax.swing.JPanel;
54import org.apache.commons.io.FilenameUtils;
55import org.apache.commons.text.StringEscapeUtils;
56import org.openide.filesystems.FileUtil;
57import org.openide.util.NbBundle;
58import org.openide.util.NbBundle.Messages;
59import org.sleuthkit.autopsy.casemodule.Case;
60import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
61import org.sleuthkit.autopsy.casemodule.services.TagsManager;
62import org.sleuthkit.autopsy.casemodule.services.contentviewertags.ContentViewerTagManager;
63import org.sleuthkit.autopsy.casemodule.services.contentviewertags.ContentViewerTagManager.ContentViewerTag;
64import org.sleuthkit.autopsy.contentviewers.imagetagging.ImageTagRegion;
65import org.sleuthkit.autopsy.contentviewers.imagetagging.ImageTagsUtil;
66import org.sleuthkit.autopsy.coreutils.EscapeUtil;
67import org.sleuthkit.autopsy.coreutils.ImageUtils;
68import org.sleuthkit.autopsy.coreutils.Logger;
69import org.sleuthkit.autopsy.coreutils.Version;
70import org.sleuthkit.autopsy.datamodel.ContentUtils.ExtractFscContentVisitor;
71import org.sleuthkit.autopsy.ingest.IngestManager;
72import org.sleuthkit.autopsy.report.ReportBranding;
73import org.sleuthkit.autopsy.report.infrastructure.TableReportModule;
74import org.sleuthkit.datamodel.AbstractFile;
75import org.sleuthkit.datamodel.BlackboardArtifact;
76import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
77import org.sleuthkit.datamodel.Content;
78import org.sleuthkit.datamodel.ContentTag;
79import org.sleuthkit.datamodel.Image;
80import org.sleuthkit.datamodel.IngestJobInfo;
81import org.sleuthkit.datamodel.IngestModuleInfo;
82import org.sleuthkit.datamodel.SleuthkitCase;
83import org.sleuthkit.datamodel.TskCoreException;
84import org.sleuthkit.datamodel.TskData;
85import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM;
86
87public class HTMLReport implements TableReportModule {
88
89 private static final Logger logger = Logger.getLogger(HTMLReport.class.getName());
90 private static final String THUMBS_REL_PATH = "thumbs" + File.separator; //NON-NLS
91 private static HTMLReport instance;
92 private static final int MAX_THUMBS_PER_PAGE = 1000;
93 private static final String HTML_SUBDIR = "content";
95 public static Integer THUMBNAIL_COLUMNS = 5;
96
97 private Map<String, Integer> dataTypes;
98 private String path;
99 private String thumbsPath;
100 private String subPath;
101 private String currentDataType; // name of current data type
102 private Integer rowCount; // number of rows (aka artifacts or tags) for the current data type
103 private Writer out;
104
105 private HTMLReportConfigurationPanel configPanel;
106
108
109 // Get the default instance of this report
110 public static synchronized HTMLReport getDefault() {
111 if (instance == null) {
112 instance = new HTMLReport();
113 }
114 return instance;
115 }
116
117 // Hidden constructor
118 private HTMLReport() {
120 }
121
122 @Override
123 public JPanel getConfigurationPanel() {
125 return configPanel;
126 }
127
128 private void initializePanel() {
129 if (configPanel == null) {
130 configPanel = new HTMLReportConfigurationPanel();
131 }
132 }
133
139 @Override
141 return new HTMLReportModuleSettings();
142 }
143
149 @Override
152 return configPanel.getConfiguration();
153 }
154
160 @Override
163 if (settings == null || settings instanceof NoReportModuleSettings) {
164 configPanel.setConfiguration((HTMLReportModuleSettings) getDefaultConfiguration());
165 return;
166 }
167
168 if (settings instanceof HTMLReportModuleSettings) {
169 configPanel.setConfiguration((HTMLReportModuleSettings) settings);
170 return;
171 }
172
173 throw new IllegalArgumentException("Expected settings argument to be an instance of HTMLReportModuleSettings");
174 }
175
176 // Refesh the member variables
177 private void refresh() throws NoCurrentCaseException {
179
180 dataTypes = new TreeMap<>();
181
182 path = "";
183 thumbsPath = "";
184 subPath = "";
185 currentDataType = "";
186 rowCount = 0;
187
188 if (out != null) {
189 try {
190 out.close();
191 } catch (IOException ex) {
192 }
193 }
194 out = null;
195 }
196
203 private String dataTypeToFileName(String dataType) {
204
205 String fileName = org.sleuthkit.autopsy.coreutils.FileUtil.escapeFileName(dataType);
206 // replace all ' ' with '_'
207 fileName = fileName.replaceAll(" ", "_");
208
209 return fileName;
210 }
211
216 @SuppressWarnings("deprecation")
217 private String useDataTypeIcon(String dataType) {
218 String iconFilePath;
219 String iconFileName;
220 InputStream in;
221 OutputStream output = null;
222
223 logger.log(Level.INFO, "useDataTypeIcon: dataType = {0}", dataType); //NON-NLS
224
225 // find the artifact with matching display name
226 BlackboardArtifact.ARTIFACT_TYPE artifactType = null;
227 for (ARTIFACT_TYPE v : ARTIFACT_TYPE.values()) {
228 if (v.getDisplayName().equals(dataType)) {
229 artifactType = v;
230 }
231 }
232
233 if (null != artifactType) {
234 // set the icon file name
235 iconFileName = dataTypeToFileName(artifactType.getDisplayName()) + ".png"; //NON-NLS
236 iconFilePath = subPath + File.separator + iconFileName;
237
238 // determine the source image to use
239 switch (artifactType) {
240 case TSK_WEB_BOOKMARK:
241 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/bookmarks.png"); //NON-NLS
242 break;
243 case TSK_WEB_COOKIE:
244 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/cookies.png"); //NON-NLS
245 break;
246 case TSK_WEB_HISTORY:
247 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/history.png"); //NON-NLS
248 break;
249 case TSK_WEB_DOWNLOAD:
250 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/downloads.png"); //NON-NLS
251 break;
252 case TSK_RECENT_OBJECT:
253 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/recent.png"); //NON-NLS
254 break;
255 case TSK_INSTALLED_PROG:
256 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/installed.png"); //NON-NLS
257 break;
258 case TSK_KEYWORD_HIT:
259 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/keywords.png"); //NON-NLS
260 break;
261 case TSK_HASHSET_HIT:
262 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/hash.png"); //NON-NLS
263 break;
264 case TSK_DEVICE_ATTACHED:
265 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/devices.png"); //NON-NLS
266 break;
267 case TSK_WEB_SEARCH_QUERY:
268 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/search.png"); //NON-NLS
269 break;
270 case TSK_METADATA_EXIF:
271 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/exif.png"); //NON-NLS
272 break;
273 case TSK_TAG_FILE:
274 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/userbookmarks.png"); //NON-NLS
275 break;
276 case TSK_TAG_ARTIFACT:
277 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/userbookmarks.png"); //NON-NLS
278 break;
279 case TSK_SERVICE_ACCOUNT:
280 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/account-icon-16.png"); //NON-NLS
281 break;
282 case TSK_CONTACT:
283 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/contact.png"); //NON-NLS
284 break;
285 case TSK_MESSAGE:
286 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/message.png"); //NON-NLS
287 break;
288 case TSK_CALLLOG:
289 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/calllog.png"); //NON-NLS
290 break;
291 case TSK_CALENDAR_ENTRY:
292 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/calendar.png"); //NON-NLS
293 break;
294 case TSK_SPEED_DIAL_ENTRY:
295 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/speeddialentry.png"); //NON-NLS
296 break;
297 case TSK_BLUETOOTH_PAIRING:
298 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/bluetooth.png"); //NON-NLS
299 break;
300 case TSK_GPS_BOOKMARK:
301 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/gpsfav.png"); //NON-NLS
302 break;
303 case TSK_GPS_LAST_KNOWN_LOCATION:
304 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/gps-lastlocation.png"); //NON-NLS
305 break;
306 case TSK_GPS_SEARCH:
307 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/gps-search.png"); //NON-NLS
308 break;
309 case TSK_OS_INFO:
310 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/computer.png"); //NON-NLS
311 break;
312 case TSK_GPS_TRACKPOINT:
313 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/gps_trackpoint.png"); //NON-NLS
314 break;
315 case TSK_GPS_ROUTE:
316 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/gps_trackpoint.png"); //NON-NLS
317 break;
318 case TSK_EMAIL_MSG:
319 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/mail-icon-16.png"); //NON-NLS
320 break;
321 case TSK_ENCRYPTION_SUSPECTED:
322 case TSK_ENCRYPTION_DETECTED:
323 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/encrypted-file.png"); //NON-NLS
324 break;
325 case TSK_EXT_MISMATCH_DETECTED:
326 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/mismatch-16.png"); //NON-NLS
327 break;
328 case TSK_INTERESTING_ARTIFACT_HIT:
329 //fall through deprecated type to TSK_INTERESTING_ITEM
330 case TSK_INTERESTING_FILE_HIT:
331 //fall through deprecated type to TSK_INTERESTING_ITEM
332 case TSK_INTERESTING_ITEM:
333 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/interesting_item.png"); //NON-NLS
334 break;
335 case TSK_PROG_RUN:
336 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/installed.png"); //NON-NLS
337 break;
338 case TSK_REMOTE_DRIVE:
339 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/drive_network.png"); //NON-NLS
340 break;
341 case TSK_OS_ACCOUNT:
342 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/os-account.png"); //NON-NLS
343 break;
344 case TSK_OBJECT_DETECTED:
345 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/objects.png"); //NON-NLS
346 break;
347 case TSK_WEB_FORM_AUTOFILL:
348 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/web-form.png"); //NON-NLS
349 break;
350 case TSK_WEB_CACHE:
351 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/cache.png"); //NON-NLS
352 break;
353 case TSK_USER_CONTENT_SUSPECTED:
354 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/user-content.png"); //NON-NLS
355 break;
356 case TSK_METADATA:
357 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/metadata.png"); //NON-NLS
358 break;
359 case TSK_CLIPBOARD_CONTENT:
360 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/clipboard.png"); //NON-NLS
361 break;
362 case TSK_ACCOUNT:
363 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/accounts.png"); //NON-NLS
364 break;
365 case TSK_WIFI_NETWORK:
366 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/network-wifi.png"); //NON-NLS
367 break;
368 case TSK_WIFI_NETWORK_ADAPTER:
369 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/network-wifi.png"); //NON-NLS
370 break;
371 case TSK_SIM_ATTACHED:
372 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/sim_card.png"); //NON-NLS
373 break;
374 case TSK_BLUETOOTH_ADAPTER:
375 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/Bluetooth.png"); //NON-NLS
376 break;
377 case TSK_DEVICE_INFO:
378 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/devices.png"); //NON-NLS
379 break;
380 case TSK_VERIFICATION_FAILED:
381 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/validationFailed.png"); //NON-NLS
382 break;
383 case TSK_WEB_ACCOUNT_TYPE:
384 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/web-account-type.png"); //NON-NLS
385 break;
386 case TSK_WEB_FORM_ADDRESS:
387 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/web-form-address.png"); //NON-NLS
388 break;
389 case TSK_GPS_AREA:
390 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/gps-area.png"); //NON-NLS
391 break;
392 case TSK_WEB_CATEGORIZATION:
393 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/domain-16.png"); //NON-NLS
394 break;
395 case TSK_YARA_HIT:
396 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/yara_16.png"); //NON-NLS
397 break;
398 case TSK_PREVIOUSLY_SEEN:
399 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/previously-seen.png"); //NON-NLS
400 break;
401 case TSK_PREVIOUSLY_UNSEEN:
402 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/previously-unseen.png"); //NON-NLS
403 break;
404 case TSK_PREVIOUSLY_NOTABLE:
405 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/red-circle-exclamation.png"); //NON-NLS
406 break;
407 default:
408 logger.log(Level.WARNING, "useDataTypeIcon: unhandled artifact type = {0}", dataType); //NON-NLS
409 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/star.png"); //NON-NLS
410 iconFileName = "star.png"; //NON-NLS
411 iconFilePath = subPath + File.separator + iconFileName;
412 break;
413 }
414 } else if (dataType.startsWith(ARTIFACT_TYPE.TSK_ACCOUNT.getDisplayName())) {
415 /*
416 * TSK_ACCOUNT artifacts get separated by their TSK_ACCOUNT_TYPE
417 * attribute, with a synthetic compound dataType name, so they are
418 * not caught by the switch statement above. For now we just give
419 * them all the general account icon, but we could do something else
420 * in the future.
421 */
422 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/accounts.png"); //NON-NLS
423 iconFileName = "accounts.png"; //NON-NLS
424 iconFilePath = subPath + File.separator + iconFileName;
425 } else { // no defined artifact found for this dataType
426 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/star.png"); //NON-NLS
427 iconFileName = "star.png"; //NON-NLS
428 iconFilePath = subPath + File.separator + iconFileName;
429 }
430
431 try {
432 output = new FileOutputStream(iconFilePath);
433 FileUtil.copy(in, output);
434 in.close();
435 output.close();
436 } catch (IOException ex) {
437 logger.log(Level.SEVERE, "Failed to extract images for HTML report.", ex); //NON-NLS
438 } finally {
439 if (output != null) {
440 try {
441 output.flush();
442 output.close();
443 } catch (IOException ex) {
444 }
445 }
446 if (in != null) {
447 try {
448 in.close();
449 } catch (IOException ex) {
450 }
451 }
452 }
453
454 return iconFileName;
455 }
456
463 @Override
464 public void startReport(String baseReportDir) {
465
466 // Refresh the HTML report
467 try {
468 refresh();
469 } catch (NoCurrentCaseException ex) {
470 logger.log(Level.SEVERE, "Exception while getting open case."); //NON-NLS
471 return;
472 }
473 // Setup the path for the HTML report
474 this.path = baseReportDir; //NON-NLS
475 this.subPath = this.path + HTML_SUBDIR + File.separator;
476 this.thumbsPath = this.subPath + THUMBS_REL_PATH; //NON-NLS
477 try {
478 FileUtil.createFolder(new File(this.subPath));
479 FileUtil.createFolder(new File(this.thumbsPath));
480 } catch (IOException ex) {
481 logger.log(Level.SEVERE, "Unable to make HTML report folder."); //NON-NLS
482 }
483 // Write the basic files
484 writeCss();
485 writeIndex();
486 writeSummary();
487 }
488
493 @Override
494 public void endReport() {
495 writeNav();
496 if (out != null) {
497 try {
498 out.close();
499 } catch (IOException ex) {
500 logger.log(Level.WARNING, "Could not close the output writer when ending report.", ex); //NON-NLS
501 }
502 }
503 }
504
513 @Override
514 public void startDataType(String name, String description) {
515 String title = dataTypeToFileName(name);
516 try {
517 out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(subPath + title + ".html"), "UTF-8")); //NON-NLS
518 } catch (FileNotFoundException ex) {
519 logger.log(Level.SEVERE, "File not found: {0}", ex); //NON-NLS
520 } catch (UnsupportedEncodingException ex) {
521 logger.log(Level.SEVERE, "Unrecognized encoding"); //NON-NLS
522 }
523
524 try {
525 StringBuilder page = new StringBuilder();
526 page.append("<html>\n<head>\n\t<title>").append(name).append("</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"index.css\" />\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n</head>\n<body>\n") //NON-NLS
527 .append(writePageHeader())
528 .append("<div id=\"header\">").append(name).append("</div>\n")
529 .append("<div id=\"content\">\n"); //NON-NLS
530 if (!description.isEmpty()) {
531 page.append("<p><strong>"); //NON-NLS
532 page.append(description);
533 page.append("</strong></p>\n"); //NON-NLS
534 }
535 out.write(page.toString());
536 currentDataType = name;
537 rowCount = 0;
538 } catch (IOException ex) {
539 logger.log(Level.SEVERE, "Failed to write page head: {0}", ex); //NON-NLS
540 }
541 }
542
547 @Override
548 public void endDataType() {
550 try {
551 StringBuilder builder = new StringBuilder();
552 builder.append(writePageFooter());
553 builder.append("</div>\n</body>\n</html>\n"); //NON-NLS
554 out.write(builder.toString());
555 } catch (IOException ex) {
556 logger.log(Level.SEVERE, "Failed to write end of HTML report.", ex); //NON-NLS
557 } finally {
558 if (out != null) {
559 try {
560 out.flush();
561 out.close();
562 } catch (IOException ex) {
563 logger.log(Level.WARNING, "Could not close the output writer when ending data type.", ex); //NON-NLS
564 }
565 out = null;
566 }
567 }
568 }
569
576 private String writePageHeader() {
577 StringBuilder output = new StringBuilder();
578 String pageHeader = configPanel.getHeader();
579 if (pageHeader.isEmpty() == false) {
580 output.append("<div id=\"pageHeaderFooter\">")
581 .append(StringEscapeUtils.escapeHtml4(pageHeader))
582 .append("</div>\n"); //NON-NLS
583 }
584 return output.toString();
585 }
586
593 private String writePageFooter() {
594 StringBuilder output = new StringBuilder();
595 String pageFooter = configPanel.getFooter();
596 if (pageFooter.isEmpty() == false) {
597 output.append("<br/><div id=\"pageHeaderFooter\">")
598 .append(StringEscapeUtils.escapeHtml4(pageFooter))
599 .append("</div>"); //NON-NLS
600 }
601 return output.toString();
602 }
603
609 @Override
610 public void startSet(String setName) {
611 StringBuilder set = new StringBuilder();
612 set.append("<h1><a name=\"").append(setName).append("\">").append(setName).append("</a></h1>\n"); //NON-NLS
613 set.append("<div class=\"keyword_list\">\n"); //NON-NLS
614
615 try {
616 out.write(set.toString());
617 } catch (IOException ex) {
618 logger.log(Level.SEVERE, "Failed to write set: {0}", ex); //NON-NLS
619 }
620 }
621
625 @Override
626 public void endSet() {
627 try {
628 out.write("</div>\n"); //NON-NLS
629 } catch (IOException ex) {
630 logger.log(Level.SEVERE, "Failed to write end of set: {0}", ex); //NON-NLS
631 }
632 }
633
639 @Override
640 public void addSetIndex(List<String> sets) {
641 StringBuilder index = new StringBuilder();
642 index.append("<ul>\n"); //NON-NLS
643 for (String set : sets) {
644 index.append("\t<li><a href=\"#").append(set).append("\">").append(set).append("</a></li>\n"); //NON-NLS
645 }
646 index.append("</ul>\n"); //NON-NLS
647 try {
648 out.write(index.toString());
649 } catch (IOException ex) {
650 logger.log(Level.SEVERE, "Failed to add set index: {0}", ex); //NON-NLS
651 }
652 }
653
659 @Override
660 public void addSetElement(String elementName) {
661 try {
662 out.write("<h4>" + elementName + "</h4>\n"); //NON-NLS
663 } catch (IOException ex) {
664 logger.log(Level.SEVERE, "Failed to write set element: {0}", ex); //NON-NLS
665 }
666 }
667
673 @Override
674 public void startTable(List<String> titles) {
675 StringBuilder ele = new StringBuilder();
676 ele.append("<table>\n<thead>\n\t<tr>\n"); //NON-NLS
677 for (String title : titles) {
678 ele.append("\t\t<th>").append(title).append("</th>\n"); //NON-NLS
679 }
680 ele.append("\t</tr>\n</thead>\n"); //NON-NLS
681
682 try {
683 out.write(ele.toString());
684 } catch (IOException ex) {
685 logger.log(Level.SEVERE, "Failed to write table start: {0}", ex); //NON-NLS
686 }
687 }
688
695 public void startContentTagsTable(List<String> columnHeaders) {
696 StringBuilder htmlOutput = new StringBuilder();
697 htmlOutput.append("<table>\n<thead>\n\t<tr>\n"); //NON-NLS
698
699 // Add the specified columns.
700 for (String columnHeader : columnHeaders) {
701 htmlOutput.append("\t\t<th>").append(columnHeader).append("</th>\n"); //NON-NLS
702 }
703
704 // Add a column for a hyperlink to a local copy of the tagged content.
705 htmlOutput.append("\t\t<th></th>\n"); //NON-NLS
706
707 htmlOutput.append("\t</tr>\n</thead>\n"); //NON-NLS
708
709 try {
710 out.write(htmlOutput.toString());
711 } catch (IOException ex) {
712 logger.log(Level.SEVERE, "Failed to write table start: {0}", ex); //NON-NLS
713 }
714 }
715
719 @Override
720 public void endTable() {
721 try {
722 out.write("</table>\n"); //NON-NLS
723 } catch (IOException ex) {
724 logger.log(Level.SEVERE, "Failed to write end of table: {0}", ex); //NON-NLS
725 }
726 }
727
734 @Override
735 public void addRow(List<String> row) {
736 addRow(row, true);
737 }
738
746 private void addRow(List<String> row, boolean escapeText) {
747 StringBuilder builder = new StringBuilder();
748 builder.append("\t<tr>\n"); //NON-NLS
749 for (String cell : row) {
750 String cellText = escapeText ? EscapeUtil.escapeHtml(cell) : cell;
751 builder.append("\t\t<td>").append(cellText).append("</td>\n"); //NON-NLS
752 }
753 builder.append("\t</tr>\n"); //NON-NLS
754 rowCount++;
755
756 try {
757 out.write(builder.toString());
758 } catch (IOException ex) {
759 logger.log(Level.SEVERE, "Failed to write row to out.", ex); //NON-NLS
760 } catch (NullPointerException ex) {
761 logger.log(Level.SEVERE, "Output writer is null. Page was not initialized before writing.", ex); //NON-NLS
762 }
763 }
764
772 public void addRowWithTaggedContentHyperlink(List<String> row, ContentTag contentTag) {
773 Content content = contentTag.getContent();
774 if (content instanceof AbstractFile == false) {
775 addRow(row, true);
776 return;
777 }
778 AbstractFile file = (AbstractFile) content;
779 // Add the hyperlink to the row. A column header for it was created in startTable().
780 StringBuilder localFileLink = new StringBuilder();
781 // Don't make a local copy of the file if it is a directory or unallocated space.
782 if (!(file.isDir()
783 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS
784 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS)) {
785 localFileLink.append("<a href=\""); //NON-NLS
786 // save it in a folder based on the tag name
787 String localFilePath = saveContent(file, contentTag.getName().getDisplayName());
788 localFileLink.append(localFilePath);
789 localFileLink.append("\" target=\"_top\">");
790 }
791
792 StringBuilder builder = new StringBuilder();
793 builder.append("\t<tr>\n"); //NON-NLS
794 int positionCounter = 0;
795 for (String cell : row) {
796 // position-dependent code used to format this report. Not great, but understandable for formatting.
797 switch (positionCounter) {
798 case 1:
799 // Convert the file name to a hyperlink and left-align it
800 builder.append("\t\t<td class=\"left_align_cell\">").append(localFileLink.toString()).append(cell).append("</a></td>\n"); //NON-NLS
801 break;
802 case 7:
803 // Right-align the bytes column.
804 builder.append("\t\t<td class=\"right_align_cell\">").append(cell).append("</td>\n"); //NON-NLS
805 break;
806 default:
807 // Regular case, not a file name nor a byte count
808 builder.append("\t\t<td>").append(cell).append("</td>\n"); //NON-NLS
809 break;
810 }
811 ++positionCounter;
812 }
813 builder.append("\t</tr>\n"); //NON-NLS
814 rowCount++;
815
816 try {
817 out.write(builder.toString());
818 } catch (IOException ex) {
819 logger.log(Level.SEVERE, "Failed to write row to out.", ex); //NON-NLS
820 } catch (NullPointerException ex) {
821 logger.log(Level.SEVERE, "Output writer is null. Page was not initialized before writing.", ex); //NON-NLS
822 }
823 }
824
832 private List<ImageTagRegion> getTaggedRegions(List<ContentTag> contentTags) {
833 ArrayList<ImageTagRegion> tagRegions = new ArrayList<>();
834 contentTags.forEach((contentTag) -> {
835 try {
837 .getTag(contentTag, ImageTagRegion.class);
838 if (contentViewerTag != null) {
839 tagRegions.add(contentViewerTag.getDetails());
840 }
841 } catch (TskCoreException | NoCurrentCaseException ex) {
842 logger.log(Level.WARNING, "Could not get content viewer tag "
843 + "from case db for content_tag with id %d", contentTag.getId());
844 }
845 });
846 return tagRegions;
847 }
848
854 public void addThumbnailRows(Set<Content> images) {
855 List<String> currentRow = new ArrayList<>();
856 int totalCount = 0;
857 int pages = 1;
858 for (Content content : images) {
859 if (currentRow.size() == THUMBNAIL_COLUMNS) {
860 addRow(currentRow, false);
861 currentRow.clear();
862 }
863
864 if (totalCount == MAX_THUMBS_PER_PAGE) {
865 // manually set the row count so the count of items shown in the
866 // navigation page reflects the number of thumbnails instead of
867 // the number of rows.
868 rowCount = totalCount;
869 totalCount = 0;
870 pages++;
871 endTable();
872 endDataType();
873 startDataType(NbBundle.getMessage(this.getClass(), "ReportHTML.addThumbRows.dataType.title", pages),
874 NbBundle.getMessage(this.getClass(), "ReportHTML.addThumbRows.dataType.msg"));
875 List<String> emptyHeaders = new ArrayList<>();
876 for (int i = 0; i < THUMBNAIL_COLUMNS; i++) {
877 emptyHeaders.add("");
878 }
879 startTable(emptyHeaders);
880 }
881
882 if (failsContentCheck(content)) {
883 continue;
884 }
885
886 AbstractFile file = (AbstractFile) content;
887 List<ContentTag> contentTags = new ArrayList<>();
888
889 String thumbnailPath = null;
890 String imageWithTagsFullPath = null;
891 try {
892 //Get content tags and all image tags
893 contentTags = Case.getCurrentCase().getServices()
895 List<ImageTagRegion> imageTags = getTaggedRegions(contentTags);
896
897 if (!imageTags.isEmpty()) {
898 //Write the tags to the fullsize and thumbnail images
899 BufferedImage fullImageWithTags = ImageTagsUtil.getImageWithTags(file, imageTags);
900
901 BufferedImage thumbnailWithTags = ImageTagsUtil.getThumbnailWithTags(file,
902 imageTags, ImageTagsUtil.IconSize.MEDIUM);
903
904 String fileName = org.sleuthkit.autopsy.coreutils.FileUtil.escapeFileName(file.getName());
905
906 //Create paths in report to write tagged images
907 File thumbnailImageWithTagsFile = Paths.get(thumbsPath, FilenameUtils.removeExtension(fileName) + ".png").toFile();
908 String fullImageWithTagsPath = makeCustomUniqueFilePath(file, "thumbs_fullsize");
909 fullImageWithTagsPath = FilenameUtils.removeExtension(fullImageWithTagsPath) + ".png";
910 File fullImageWithTagsFile = Paths.get(fullImageWithTagsPath).toFile();
911
912 //Save images
913 ImageIO.write(thumbnailWithTags, "png", thumbnailImageWithTagsFile);
914 ImageIO.write(fullImageWithTags, "png", fullImageWithTagsFile);
915
916 thumbnailPath = THUMBS_REL_PATH + thumbnailImageWithTagsFile.getName();
917 //Relative path
918 imageWithTagsFullPath = fullImageWithTagsPath.substring(subPath.length());
919 }
920 } catch (TskCoreException ex) {
921 logger.log(Level.WARNING, "Could not get tags for file.", ex); //NON-NLS
922 } catch (IOException | InterruptedException | ExecutionException ex) {
923 logger.log(Level.WARNING, "Could make marked up thumbnail.", ex); //NON-NLS
924 }
925
926 // save copies of the orginal image and thumbnail image
927 if (thumbnailPath == null) {
928 thumbnailPath = prepareThumbnail(file);
929 }
930
931 if (thumbnailPath == null) {
932 continue;
933 }
934 String contentPath = saveContent(file, "original"); //NON-NLS
935 String nameInImage;
936 try {
937 nameInImage = file.getUniquePath();
938 } catch (TskCoreException ex) {
939 nameInImage = file.getName();
940 }
941
942 StringBuilder linkToThumbnail = new StringBuilder();
943 linkToThumbnail.append("<div id='thumbnail_link'><a href=\"")
944 .append((imageWithTagsFullPath != null) ? imageWithTagsFullPath : contentPath)
945 .append("\" target=\"_top\"><img src=\"")
946 .append(thumbnailPath).append("\" title=\"").append(nameInImage).append("\"/></a><br>") //NON-NLS
947 .append(file.getName()).append("<br>"); //NON-NLS
948 if (imageWithTagsFullPath != null) {
949 linkToThumbnail.append("<a href=\"").append(contentPath).append("\" target=\"_top\">View Original</a><br>");
950 }
951
952 if (!contentTags.isEmpty()) {
953 linkToThumbnail.append(NbBundle.getMessage(this.getClass(), "ReportHTML.thumbLink.tags"));
954 }
955 for (int i = 0; i < contentTags.size(); i++) {
956 ContentTag tag = contentTags.get(i);
957 String notableString = tag.getName().getTagType() == TskData.TagType.BAD ? TagsManager.getNotableTagLabel() : "";
958 linkToThumbnail.append(tag.getName().getDisplayName()).append(notableString);
959 if (i != contentTags.size() - 1) {
960 linkToThumbnail.append(", ");
961 }
962 }
963
964 linkToThumbnail.append("</div>");
965 currentRow.add(linkToThumbnail.toString());
966
967 totalCount++;
968 }
969
970 if (currentRow.isEmpty() == false) {
971 int extraCells = THUMBNAIL_COLUMNS - currentRow.size();
972 for (int i = 0; i < extraCells; i++) {
973 // Finish out the row.
974 currentRow.add("");
975 }
976 addRow(currentRow, false);
977 }
978
979 // manually set rowCount to be the total number of images.
980 rowCount = totalCount;
981 }
982
983 private boolean failsContentCheck(Content c) {
984 if (c instanceof AbstractFile == false) {
985 return true;
986 }
987 AbstractFile file = (AbstractFile) c;
988 return file.isDir()
989 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS
990 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS;
991 }
992
993 private String makeCustomUniqueFilePath(AbstractFile file, String dirName) {
994 // clean up the dir name passed in
995 String dirName2 = org.sleuthkit.autopsy.coreutils.FileUtil.escapeFileName(dirName);
996
997 // Make a folder for the local file with the same tagName as the tag.
998 StringBuilder localFilePath = new StringBuilder(); // full path
999
1000 localFilePath.append(subPath);
1001 localFilePath.append(dirName2);
1002 File localFileFolder = new File(localFilePath.toString());
1003 if (!localFileFolder.exists()) {
1004 localFileFolder.mkdirs();
1005 }
1006
1007 /*
1008 * Construct a file tagName for the local file that incorporates the
1009 * file ID to ensure uniqueness.
1010 *
1011 * Note: File name is normalized to account for possible attribute name
1012 * which will be separated by a ':' character.
1013 */
1014 String fileName = org.sleuthkit.autopsy.coreutils.FileUtil.escapeFileName(file.getName());
1015 String objectIdSuffix = "_" + file.getId();
1016 int lastDotIndex = fileName.lastIndexOf(".");
1017 if (lastDotIndex != -1 && lastDotIndex != 0) {
1018 // The file tagName has a conventional extension. Insert the object id before the '.' of the extension.
1019 fileName = fileName.substring(0, lastDotIndex) + objectIdSuffix + fileName.substring(lastDotIndex, fileName.length());
1020 } else {
1021 // The file has no extension or the only '.' in the file is an initial '.', as in a hidden file.
1022 // Add the object id to the end of the file tagName.
1023 fileName += objectIdSuffix;
1024 }
1025 localFilePath.append(File.separator);
1026 localFilePath.append(fileName);
1027
1028 return localFilePath.toString();
1029 }
1030
1040 public String saveContent(AbstractFile file, String dirName) {
1041
1042 String localFilePath = makeCustomUniqueFilePath(file, dirName);
1043
1044 // If the local file doesn't already exist, create it now.
1045 // The existence check is necessary because it is possible to apply multiple tags with the same tagName to a file.
1046 File localFile = new File(localFilePath);
1047 if (!localFile.exists()) {
1048 ExtractFscContentVisitor.extract(file, localFile, null, null);
1049 }
1050
1051 // get the relative path
1052 return localFilePath.substring(subPath.length());
1053 }
1054
1062 @Override
1063 public String dateToString(long date) {
1064 SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
1065 return sdf.format(new java.util.Date(date * 1000));
1066 }
1067
1068 @Override
1069 public String getRelativeFilePath() {
1070 return "report.html"; //NON-NLS
1071 }
1072
1073 @Override
1074 public String getName() {
1075 return NbBundle.getMessage(this.getClass(), "ReportHTML.getName.text");
1076 }
1077
1078 @Override
1079 public String getDescription() {
1080 return NbBundle.getMessage(this.getClass(), "ReportHTML.getDesc.text");
1081 }
1082
1086 private void writeCss() {
1087 Writer cssOut = null;
1088 try {
1089 cssOut = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(subPath + "index.css"), "UTF-8")); //NON-NLS NON-NLS
1090 String css = "body {margin: 0px; padding: 0px; background: #FFFFFF; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353;}\n"
1091 + //NON-NLS
1092 "#content {padding: 30px;}\n"
1093 + //NON-NLS
1094 "#header {width:100%; padding: 10px; line-height: 25px; background: #07A; color: #FFF; font-size: 20px;}\n"
1095 + //NON-NLS
1096 "#pageHeaderFooter {width: 100%; padding: 10px; line-height: 25px; text-align: center; font-size: 20px;}\n"
1097 + //NON-NLS
1098 "h1 {font-size: 20px; font-weight: normal; color: #07A; padding: 0 0 7px 0; margin-top: 25px; border-bottom: 1px solid #D6D6D6;}\n"
1099 + //NON-NLS
1100 "h2 {font-size: 20px; font-weight: bolder; color: #07A;}\n"
1101 + //NON-NLS
1102 "h3 {font-size: 16px; color: #07A;}\n"
1103 + //NON-NLS
1104 "h4 {background: #07A; color: #FFF; font-size: 16px; margin: 0 0 0 25px; padding: 0; padding-left: 15px;}\n"
1105 + //NON-NLS
1106 "ul.nav {list-style-type: none; line-height: 35px; padding: 0px; margin-left: 15px;}\n"
1107 + //NON-NLS
1108 "ul li a {font-size: 14px; color: #444; text-decoration: none; padding-left: 25px;}\n"
1109 + //NON-NLS
1110 "ul li a:hover {text-decoration: underline;}\n"
1111 + //NON-NLS
1112 "p {margin: 0 0 20px 0;}\n"
1113 + //NON-NLS
1114 "table {white-space:nowrap; min-width: 700px; padding: 2; margin: 0; border-collapse: collapse; border-bottom: 2px solid #e5e5e5;}\n"
1115 + //NON-NLS
1116 ".keyword_list table {margin: 0 0 25px 25px; border-bottom: 2px solid #dedede;}\n"
1117 + //NON-NLS
1118 "table th {white-space:nowrap; display: table-cell; text-align: center; padding: 2px 4px; background: #e5e5e5; color: #777; font-size: 11px; text-shadow: #e9f9fd 0 1px 0; border-top: 1px solid #dedede; border-bottom: 2px solid #e5e5e5;}\n"
1119 + //NON-NLS
1120 "table .left_align_cell{display: table-cell; padding: 2px 4px; font: 13px/20px Arial, Helvetica, sans-serif; min-width: 125px; overflow: auto; text-align: left; }\n"
1121 + //NON-NLS
1122 "table .right_align_cell{display: table-cell; padding: 2px 4px; font: 13px/20px Arial, Helvetica, sans-serif; min-width: 125px; overflow: auto; text-align: right; }\n"
1123 + //NON-NLS
1124 "table td {white-space:nowrap; display: table-cell; padding: 2px 3px; font: 13px/20px Arial, Helvetica, sans-serif; min-width: 125px; overflow: auto; text-align:left; vertical-align: text-top;}\n"
1125 + //NON-NLS
1126 "table tr:nth-child(even) td {background: #f3f3f3;}\n"
1127 + //NON-NLS
1128 "div#thumbnail_link {max-width: 200px; white-space: pre-wrap; white-space: -moz-pre-wrap; white-space: -pre-wrap; white-space: -o-pre-wrap; word-wrap: break-word;}";
1129 cssOut.write(css);
1130 } catch (FileNotFoundException ex) {
1131 logger.log(Level.SEVERE, "Could not find index.css file to write to.", ex); //NON-NLS
1132 } catch (UnsupportedEncodingException ex) {
1133 logger.log(Level.SEVERE, "Did not recognize encoding when writing index.css.", ex); //NON-NLS
1134 } catch (IOException ex) {
1135 logger.log(Level.SEVERE, "Error creating Writer for index.css.", ex); //NON-NLS
1136 } finally {
1137 try {
1138 if (cssOut != null) {
1139 cssOut.flush();
1140 cssOut.close();
1141 }
1142 } catch (IOException ex) {
1143 }
1144 }
1145 }
1146
1150 private void writeIndex() {
1151 Writer indexOut = null;
1152 String indexFilePath = path + "report.html"; //NON-NLS
1153 Case openCase;
1154 try {
1155 openCase = Case.getCurrentCaseThrows();
1156 } catch (NoCurrentCaseException ex) {
1157 logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS
1158 return;
1159 }
1160 try {
1161 indexOut = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(indexFilePath), "UTF-8")); //NON-NLS
1162 StringBuilder index = new StringBuilder();
1163 final String reportTitle = reportBranding.getReportTitle();
1164 String iconPath = reportBranding.getAgencyLogoPath();
1165 if (iconPath == null) {
1166 // use default Autopsy icon if custom icon is not set
1167 iconPath = HTML_SUBDIR + "favicon.ico";
1168 } else {
1169 iconPath = Paths.get(reportBranding.getAgencyLogoPath()).getFileName().toString(); //ref to writeNav() for agency_logo
1170 }
1171 index.append("<head>\n<title>").append(reportTitle).append(" ").append(
1172 NbBundle.getMessage(this.getClass(), "ReportHTML.writeIndex.title", currentCase.getDisplayName())).append(
1173 "</title>\n"); //NON-NLS
1174 index.append("<link rel=\"icon\" type=\"image/ico\" href=\"")
1175 .append(iconPath).append("\" />\n"); //NON-NLS
1176 index.append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"); //NON-NLS
1177 index.append("</head>\n"); //NON-NLS
1178 index.append("<frameset cols=\"350px,*\">\n"); //NON-NLS
1179 index.append("<frame src=\"" + HTML_SUBDIR).append(File.separator).append("nav.html\" name=\"nav\">\n"); //NON-NLS
1180 index.append("<frame src=\"" + HTML_SUBDIR).append(File.separator).append("summary.html\" name=\"content\">\n"); //NON-NLS
1181 index.append("<noframes>").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeIndex.noFrames.msg")).append("<br />\n"); //NON-NLS
1182 index.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeIndex.noFrames.seeNav")).append("<br />\n"); //NON-NLS
1183 index.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeIndex.seeSum")).append("</noframes>\n"); //NON-NLS
1184 index.append("</frameset>\n"); //NON-NLS
1185 index.append("</html>"); //NON-NLS
1186 indexOut.write(index.toString());
1187 openCase.addReport(indexFilePath, NbBundle.getMessage(this.getClass(),
1188 "ReportHTML.writeIndex.srcModuleName.text"), "");
1189 } catch (IOException ex) {
1190 logger.log(Level.SEVERE, "Error creating Writer for report.html: {0}", ex); //NON-NLS
1191 } catch (TskCoreException ex) {
1192 String errorMessage = String.format("Error adding %s to case as a report", indexFilePath); //NON-NLS
1193 logger.log(Level.SEVERE, errorMessage, ex);
1194 } finally {
1195 try {
1196 if (indexOut != null) {
1197 indexOut.flush();
1198 indexOut.close();
1199 }
1200 } catch (IOException ex) {
1201 }
1202 }
1203 }
1204
1208 private void writeNav() {
1209 Writer navOut = null;
1210 try {
1211 navOut = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(subPath + "nav.html"), "UTF-8")); //NON-NLS
1212 StringBuilder nav = new StringBuilder();
1213 nav.append("<html>\n<head>\n\t<title>").append( //NON-NLS
1214 NbBundle.getMessage(this.getClass(), "ReportHTML.writeNav.title"))
1215 .append("</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"index.css\" />\n"); //NON-NLS
1216 nav.append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n</head>\n<body>\n"); //NON-NLS
1217 nav.append("<div id=\"content\">\n<h1>").append( //NON-NLS
1218 NbBundle.getMessage(this.getClass(), "ReportHTML.writeNav.h1")).append("</h1>\n"); //NON-NLS
1219 nav.append("<ul class=\"nav\">\n"); //NON-NLS
1220 nav.append("<li style=\"background: url(summary.png) left center no-repeat;\"><a href=\"summary.html\" target=\"content\">") //NON-NLS
1221 .append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeNav.summary")).append("</a></li>\n"); //NON-NLS
1222
1223 for (String dataType : dataTypes.keySet()) {
1224 String dataTypeEsc = dataTypeToFileName(dataType);
1225 String iconFileName = useDataTypeIcon(dataType);
1226 nav.append("<li style=\"background: url('").append(iconFileName) //NON-NLS
1227 .append("') left center no-repeat;\"><a href=\"") //NON-NLS
1228 .append(dataTypeEsc).append(".html\" target=\"content\">") //NON-NLS
1229 .append(dataType).append(" (").append(dataTypes.get(dataType))
1230 .append(")</a></li>\n"); //NON-NLS
1231 }
1232 nav.append("</ul>\n"); //NON-NLS
1233 nav.append("</div>\n</body>\n</html>"); //NON-NLS
1234 navOut.write(nav.toString());
1235 } catch (IOException ex) {
1236 logger.log(Level.SEVERE, "Failed to write end of report navigation menu: {0}", ex); //NON-NLS
1237 } finally {
1238 if (navOut != null) {
1239 try {
1240 navOut.flush();
1241 navOut.close();
1242 } catch (IOException ex) {
1243 logger.log(Level.WARNING, "Could not close navigation out writer."); //NON-NLS
1244 }
1245 }
1246 }
1247
1248 InputStream in = null;
1249 OutputStream output = null;
1250 try {
1251
1252 //pull generator and agency logo from branding, and the remaining resources from the core jar
1253 String generatorLogoPath = reportBranding.getGeneratorLogoPath();
1254 if (generatorLogoPath != null && !generatorLogoPath.isEmpty()) {
1255 File from = new File(generatorLogoPath);
1256 File to = new File(subPath);
1257 FileUtil.copyFile(FileUtil.toFileObject(from), FileUtil.toFileObject(to), "generator_logo"); //NON-NLS
1258 }
1259
1260 String agencyLogoPath = reportBranding.getAgencyLogoPath();
1261 if (agencyLogoPath != null && !agencyLogoPath.isEmpty()) {
1262 Path destinationPath = Paths.get(subPath);
1263 Files.copy(Files.newInputStream(Paths.get(agencyLogoPath)), destinationPath.resolve(Paths.get(agencyLogoPath).getFileName())); //NON-NLS
1264 }
1265
1266 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/favicon.ico"); //NON-NLS
1267 output = new FileOutputStream(new File(subPath + "favicon.ico"));
1268 FileUtil.copy(in, output);
1269 in.close();
1270 output.close();
1271
1272 in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/summary.png"); //NON-NLS
1273 output = new FileOutputStream(new File(subPath + "summary.png"));
1274 FileUtil.copy(in, output);
1275 in.close();
1276 output.close();
1277
1278 } catch (IOException ex) {
1279 logger.log(Level.SEVERE, "Failed to extract images for HTML report.", ex); //NON-NLS
1280 } finally {
1281 if (output != null) {
1282 try {
1283 output.flush();
1284 output.close();
1285 } catch (IOException ex) {
1286 }
1287 }
1288 if (in != null) {
1289 try {
1290 in.close();
1291 } catch (IOException ex) {
1292 }
1293 }
1294 }
1295 }
1296
1300 private void writeSummary() {
1301 Writer output = null;
1302 try {
1303 output = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(subPath + "summary.html"), "UTF-8")); //NON-NLS
1304 StringBuilder head = new StringBuilder();
1305 head.append("<html>\n<head>\n<title>").append( //NON-NLS
1306 NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.title")).append("</title>\n"); //NON-NLS
1307 head.append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"); //NON-NLS
1308 head.append("<style type=\"text/css\">\n"); //NON-NLS
1309 head.append("#pageHeaderFooter {width: 100%; padding: 10px; line-height: 25px; text-align: center; font-size: 20px;}\n"); //NON-NLS
1310 head.append("body { padding: 0px; margin: 0px; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353; }\n"); //NON-NLS
1311 head.append("#wrapper { width: 90%; margin: 0px auto; margin-top: 35px; }\n"); //NON-NLS
1312 head.append("h1 { color: #07A; font-size: 36px; line-height: 42px; font-weight: normal; margin: 0px; border-bottom: 1px solid #81B9DB; }\n"); //NON-NLS
1313 head.append("h1 span { color: #F00; display: block; font-size: 16px; font-weight: bold; line-height: 22px;}\n"); //NON-NLS
1314 head.append("h2 { padding: 0 0 3px 0; margin: 0px; color: #07A; font-weight: normal; border-bottom: 1px dotted #81B9DB; }\n"); //NON-NLS
1315 head.append("h3 { padding: 5 0 3px 0; margin: 0px; color: #07A; font-weight: normal; }\n");
1316 head.append("table td { padding: 5px 25px 5px 0px; vertical-align:top;}\n"); //NON-NLS
1317 head.append("p.subheadding { padding: 0px; margin: 0px; font-size: 11px; color: #B5B5B5; }\n"); //NON-NLS
1318 head.append(".title { width: 660px; margin-bottom: 50px; }\n"); //NON-NLS
1319 head.append(".left { float: left; width: 250px; margin-top: 20px; text-align: center; }\n"); //NON-NLS
1320 head.append(".left img { max-width: 250px; max-height: 250px; min-width: 200px; min-height: 200px; }\n"); //NON-NLS
1321 head.append(".right { float: right; width: 385px; margin-top: 25px; font-size: 14px; }\n"); //NON-NLS
1322 head.append(".clear { clear: both; }\n"); //NON-NLS
1323 head.append(".info { padding: 10px 0;}\n");
1324 head.append(".info p { padding: 3px 10px; background: #e5e5e5; color: #777; font-size: 12px; font-weight: bold; text-shadow: #e9f9fd 0 1px 0; border-top: 1px solid #dedede; border-bottom: 2px solid #dedede; }\n"); //NON-NLS
1325 head.append(".info table { margin: 10px 25px 10px 25px; }\n"); //NON-NLS
1326 head.append("ul {padding: 0;margin: 0;list-style-type: none;}");
1327 head.append("li {padding-bottom: 5px;}");
1328 head.append("</style>\n"); //NON-NLS
1329 head.append("</head>\n<body>\n"); //NON-NLS
1330 output.write(head.toString());
1331
1332 DateFormat datetimeFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
1333 Date date = new Date();
1334 String datetime = datetimeFormat.format(date);
1335
1336 StringBuilder summary = new StringBuilder();
1337 boolean running = false;
1339 running = true;
1340 }
1341 SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
1342 List<IngestJobInfo> ingestJobs = skCase.getIngestJobs();
1343 final String reportTitle = reportBranding.getReportTitle();
1344 final String reportFooter = reportBranding.getReportFooter();
1345 final boolean generatorLogoSet = reportBranding.getGeneratorLogoPath() != null && !reportBranding.getGeneratorLogoPath().isEmpty();
1346
1347 summary.append("<div id=\"wrapper\">\n"); //NON-NLS
1348 summary.append(writePageHeader());
1349 summary.append("<h1>").append(reportTitle) //NON-NLS
1350 .append(running ? NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.warningMsg") : "")
1351 .append("</h1>\n"); //NON-NLS
1352 summary.append("<p class=\"subheadding\">").append( //NON-NLS
1353 NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.reportGenOn.text", datetime)).append("</p>\n"); //NON-NLS
1354 summary.append("<div class=\"title\">\n"); //NON-NLS
1355 summary.append(writeSummaryCaseDetails());
1356 summary.append(writeSummaryImageInfo());
1357 summary.append(writeSummarySoftwareInfo(skCase, ingestJobs));
1358 summary.append(writeSummaryIngestHistoryInfo(skCase, ingestJobs));
1359 if (generatorLogoSet) {
1360 summary.append("<div class=\"left\">\n"); //NON-NLS
1361 summary.append("<img src=\"generator_logo.png\" />\n"); //NON-NLS
1362 summary.append("</div>\n"); //NON-NLS
1363 }
1364 summary.append("<div class=\"clear\"></div>\n"); //NON-NLS
1365 if (reportFooter != null) {
1366 summary.append("<p class=\"subheadding\">").append(reportFooter).append("</p>\n"); //NON-NLS
1367 }
1368 summary.append("</div>\n"); //NON-NLS
1369 summary.append(writePageFooter());
1370 summary.append("</body></html>"); //NON-NLS
1371 output.write(summary.toString());
1372 } catch (FileNotFoundException ex) {
1373 logger.log(Level.SEVERE, "Could not find summary.html file to write to."); //NON-NLS
1374 } catch (UnsupportedEncodingException ex) {
1375 logger.log(Level.SEVERE, "Did not recognize encoding when writing summary.hmtl."); //NON-NLS
1376 } catch (IOException ex) {
1377 logger.log(Level.SEVERE, "Error creating Writer for summary.html."); //NON-NLS
1378 } catch (NoCurrentCaseException | TskCoreException ex) {
1379 logger.log(Level.WARNING, "Unable to get current sleuthkit Case for the HTML report.");
1380 } finally {
1381 try {
1382 if (output != null) {
1383 output.flush();
1384 output.close();
1385 }
1386 } catch (IOException ex) {
1387 }
1388 }
1389 }
1390
1391 @Messages({
1392 "ReportHTML.writeSum.case=Case:",
1393 "ReportHTML.writeSum.caseNumber=Case Number:",
1394 "ReportHTML.writeSum.caseNumImages=Number of data sources in case:",
1395 "ReportHTML.writeSum.caseNotes=Notes:",
1396 "ReportHTML.writeSum.examiner=Examiner:"
1397 })
1403 private StringBuilder writeSummaryCaseDetails() {
1404 StringBuilder summary = new StringBuilder();
1405
1406 final boolean agencyLogoSet = reportBranding.getAgencyLogoPath() != null && !reportBranding.getAgencyLogoPath().isEmpty();
1407
1408 // Case
1409 String caseName = currentCase.getDisplayName();
1410 String caseNumber = currentCase.getNumber();
1411 int imagecount;
1412 try {
1413 imagecount = currentCase.getDataSources().size();
1414 } catch (TskCoreException ex) {
1415 imagecount = 0;
1416 }
1417 String caseNotes = currentCase.getCaseNotes();
1418
1419 // Examiner
1420 String examinerName = currentCase.getExaminer();
1421
1422 // Start the layout.
1423 summary.append("<div class=\"title\">\n"); //NON-NLS
1424 if (agencyLogoSet) {
1425 summary.append("<div class=\"left\">\n"); //NON-NLS
1426 summary.append("<img src=\"");
1427 summary.append(Paths.get(reportBranding.getAgencyLogoPath()).getFileName().toString());
1428 summary.append("\" />\n"); //NON-NLS
1429 summary.append("</div>\n"); //NON-NLS
1430 }
1431 final String align = agencyLogoSet ? "right" : "left"; //NON-NLS NON-NLS
1432 summary.append("<div class=\"").append(align).append("\">\n"); //NON-NLS
1433 summary.append("<table>\n"); //NON-NLS
1434
1435 // Case details
1436 summary.append("<tr><td>").append(Bundle.ReportHTML_writeSum_case()).append("</td><td>") //NON-NLS
1437 .append(formatHtmlString(caseName)).append("</td></tr>\n"); //NON-NLS
1438
1439 if (!caseNumber.isEmpty()) {
1440 summary.append("<tr><td>").append(Bundle.ReportHTML_writeSum_caseNumber()).append("</td><td>") //NON-NLS
1441 .append(formatHtmlString(caseNumber)).append("</td></tr>\n"); //NON-NLS
1442 }
1443
1444 summary.append("<tr><td>").append(Bundle.ReportHTML_writeSum_caseNumImages()).append("</td><td>") //NON-NLS
1445 .append(imagecount).append("</td></tr>\n"); //NON-NLS
1446
1447 if (!caseNotes.isEmpty()) {
1448 summary.append("<tr><td>").append(Bundle.ReportHTML_writeSum_caseNotes()).append("</td><td>") //NON-NLS
1449 .append(formatHtmlString(caseNotes)).append("</td></tr>\n"); //NON-NLS
1450 }
1451
1452 // Examiner details
1453 if (!examinerName.isEmpty()) {
1454 summary.append("<tr><td>").append(Bundle.ReportHTML_writeSum_examiner()).append("</td><td>") //NON-NLS
1455 .append(formatHtmlString(examinerName)).append("</td></tr>\n"); //NON-NLS
1456 }
1457
1458 // End the layout.
1459 summary.append("</table>\n"); //NON-NLS
1460 summary.append("</div>\n"); //NON-NLS
1461 summary.append("<div class=\"clear\"></div>\n"); //NON-NLS
1462 summary.append("</div>\n"); //NON-NLS
1463 return summary;
1464 }
1465
1471 private StringBuilder writeSummaryImageInfo() {
1472 StringBuilder summary = new StringBuilder();
1473 summary.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.imageInfoHeading"));
1474 summary.append("<div class=\"info\">\n"); //NON-NLS
1475 try {
1476 for (Content c : currentCase.getDataSources()) {
1477 summary.append("<p>").append(c.getName()).append("</p>\n"); //NON-NLS
1478 if (c instanceof Image) {
1479 Image img = (Image) c;
1480
1481 summary.append("<table>\n"); //NON-NLS
1482 summary.append("<tr><td>").append( //NON-NLS
1483 NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.timezone"))
1484 .append("</td><td>").append(img.getTimeZone()).append("</td></tr>\n"); //NON-NLS
1485 for (String imgPath : img.getPaths()) {
1486 summary.append("<tr><td>").append( //NON-NLS
1487 NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.path"))
1488 .append("</td><td>").append(imgPath).append("</td></tr>\n"); //NON-NLS
1489 }
1490 summary.append("</table>\n"); //NON-NLS
1491 }
1492 }
1493 } catch (TskCoreException ex) {
1494 logger.log(Level.WARNING, "Unable to get image information for the HTML report."); //NON-NLS
1495 }
1496 summary.append("</div>\n"); //NON-NLS
1497 return summary;
1498 }
1499
1505 private StringBuilder writeSummarySoftwareInfo(SleuthkitCase skCase, List<IngestJobInfo> ingestJobs) {
1506 StringBuilder summary = new StringBuilder();
1507 summary.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.softwareInfoHeading"));
1508 summary.append("<div class=\"info\">\n");
1509 summary.append("<table>\n");
1510 summary.append("<tr><td>").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.autopsyVersion"))
1511 .append("</td><td>").append(Version.getVersion()).append("</td></tr>\n");
1512 Map<Long, IngestModuleInfo> moduleInfoHashMap = new HashMap<>();
1513 for (IngestJobInfo ingestJob : ingestJobs) {
1514 List<IngestModuleInfo> ingestModules = ingestJob.getIngestModuleInfo();
1515 for (IngestModuleInfo ingestModule : ingestModules) {
1516 if (!moduleInfoHashMap.containsKey(ingestModule.getIngestModuleId())) {
1517 moduleInfoHashMap.put(ingestModule.getIngestModuleId(), ingestModule);
1518 }
1519 }
1520 }
1521 TreeMap<String, String> modules = new TreeMap<>();
1522 for (IngestModuleInfo moduleinfo : moduleInfoHashMap.values()) {
1523 modules.put(moduleinfo.getDisplayName(), moduleinfo.getVersion());
1524 }
1525 for (Map.Entry<String, String> module : modules.entrySet()) {
1526 summary.append("<tr><td>").append(module.getKey()).append(" Module:")
1527 .append("</td><td>").append(module.getValue()).append("</td></tr>\n");
1528 }
1529 summary.append("</table>\n");
1530 summary.append("</div>\n");
1531 summary.append("<div class=\"clear\"></div>\n"); //NON-NLS
1532 return summary;
1533 }
1534
1540 private StringBuilder writeSummaryIngestHistoryInfo(SleuthkitCase skCase, List<IngestJobInfo> ingestJobs) {
1541 StringBuilder summary = new StringBuilder();
1542 try {
1543 summary.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.ingestHistoryHeading"));
1544 summary.append("<div class=\"info\">\n");
1545 int jobnumber = 1;
1546
1547 for (IngestJobInfo ingestJob : ingestJobs) {
1548 summary.append("<h3>Job ").append(jobnumber).append(":</h3>\n");
1549 summary.append("<table>\n");
1550 summary.append("<tr><td>").append("Data Source:")
1551 .append("</td><td>").append(skCase.getContentById(ingestJob.getObjectId()).getName()).append("</td></tr>\n");
1552 summary.append("<tr><td>").append("Status:")
1553 .append("</td><td>").append(ingestJob.getStatus()).append("</td></tr>\n");
1554 summary.append("<tr><td>").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.modulesEnabledHeading"))
1555 .append("</td><td>");
1556 List<IngestModuleInfo> ingestModules = ingestJob.getIngestModuleInfo();
1557 summary.append("<ul>\n");
1558 for (IngestModuleInfo ingestModule : ingestModules) {
1559 summary.append("<li>").append(ingestModule.getDisplayName()).append("</li>");
1560 }
1561 summary.append("</ul>\n");
1562 jobnumber++;
1563 summary.append("</td></tr>\n");
1564 summary.append("</table>\n");
1565 }
1566 summary.append("</div>\n");
1567 } catch (TskCoreException ex) {
1568 logger.log(Level.WARNING, "Unable to get ingest jobs for the HTML report.");
1569 }
1570 return summary;
1571 }
1572
1581 private String prepareThumbnail(AbstractFile file) {
1582 BufferedImage bufferedThumb = ImageUtils.getThumbnail(file, ImageUtils.ICON_SIZE_MEDIUM);
1583
1584 /*
1585 * File name is normalized to account for possible attribute name which
1586 * will be separated by a ':' character.
1587 */
1588 String fileName = org.sleuthkit.autopsy.coreutils.FileUtil.escapeFileName(file.getName());
1589
1590 File thumbFile = Paths.get(thumbsPath, fileName + ".png").toFile();
1591 if (bufferedThumb == null) {
1592 return null;
1593 }
1594 try {
1595 ImageIO.write(bufferedThumb, "png", thumbFile);
1596 } catch (IOException ex) {
1597 logger.log(Level.WARNING, "Failed to write thumb file to report directory.", ex); //NON-NLS
1598 return null;
1599 }
1600 if (thumbFile.exists()
1601 == false) {
1602 return null;
1603 }
1604 return THUMBS_REL_PATH
1605 + thumbFile.getName();
1606 }
1607
1616 private String formatHtmlString(String text) {
1617 String formattedString = StringEscapeUtils.escapeHtml4(text);
1618 return formattedString.replaceAll("(\r\n|\r|\n|\n\r)", "<br>");
1619 }
1620
1621}
void addReport(String localPath, String srcModuleName, String reportName)
Definition Case.java:1929
List< ContentTag > getContentTagsByContent(Content content)
static< T > ContentViewerTag< T > getTag(ContentTag contentTag, Class< T > clazz)
static BufferedImage getImageWithTags(AbstractFile file, Collection< ImageTagRegion > tagRegions)
static BufferedImage getThumbnailWithTags(AbstractFile file, Collection< ImageTagRegion > tagRegions, IconSize iconSize)
static String escapeHtml(String toEscape)
static BufferedImage getThumbnail(Content content, int iconSize)
synchronized static Logger getLogger(String name)
Definition Logger.java:124
static< T, V > void extract(Content cntnt, java.io.File dest, ProgressHandle progress, SwingWorker< T, V > worker)
static synchronized IngestManager getInstance()
void setConfiguration(ReportModuleSettings settings)
void startContentTagsTable(List< String > columnHeaders)
void addRowWithTaggedContentHyperlink(List< String > row, ContentTag contentTag)
void startDataType(String name, String description)
String saveContent(AbstractFile file, String dirName)
void addRow(List< String > row, boolean escapeText)
List< ImageTagRegion > getTaggedRegions(List< ContentTag > contentTags)
StringBuilder writeSummarySoftwareInfo(SleuthkitCase skCase, List< IngestJobInfo > ingestJobs)
StringBuilder writeSummaryIngestHistoryInfo(SleuthkitCase skCase, List< IngestJobInfo > ingestJobs)
String makeCustomUniqueFilePath(AbstractFile file, String dirName)

Copyright © 2012-2024 Sleuth Kit Labs. Generated on:
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.