Autopsy 4.22.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
DiscoveryUiUtils.java
Go to the documentation of this file.
1/*
2 * Autopsy
3 *
4 * Copyright 2020 Basis Technology Corp.
5 * Contact: carrier <at> sleuthkit <dot> org
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 */
19package org.sleuthkit.autopsy.discovery.ui;
20
21import com.google.common.io.Files;
22import java.awt.Component;
23import java.awt.Dimension;
24import java.awt.Image;
25import java.awt.Point;
26import java.awt.image.BufferedImage;
27import java.io.IOException;
28import java.nio.file.Paths;
29import java.util.ArrayList;
30import java.util.Collections;
31import java.util.HashMap;
32import java.util.List;
33import java.util.Map;
34import java.util.logging.Level;
35import javax.imageio.ImageIO;
36import javax.swing.ImageIcon;
37import javax.swing.JComponent;
38import javax.swing.JOptionPane;
39import javax.swing.JScrollPane;
40import javax.swing.JTextPane;
41import org.apache.commons.io.FileUtils;
42import org.apache.commons.io.FilenameUtils;
43import org.imgscalr.Scalr;
44import org.netbeans.api.progress.ProgressHandle;
45import org.opencv.core.Mat;
46import org.opencv.highgui.VideoCapture;
47import org.openide.util.ImageUtilities;
48import org.openide.util.NbBundle;
49import org.sleuthkit.autopsy.casemodule.Case;
50import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
51import org.sleuthkit.autopsy.corelibs.ScalrWrapper;
52import org.sleuthkit.autopsy.coreutils.ImageUtils;
53import org.sleuthkit.autopsy.coreutils.Logger;
54import org.sleuthkit.autopsy.coreutils.ThreadConfined;
55import static org.sleuthkit.autopsy.coreutils.VideoUtils.getVideoFileInTempDir;
56import org.sleuthkit.autopsy.datamodel.ContentUtils;
57import org.sleuthkit.autopsy.discovery.search.ResultFile;
58import org.sleuthkit.datamodel.AbstractFile;
59import org.sleuthkit.datamodel.BlackboardArtifact;
60import org.sleuthkit.datamodel.BlackboardAttribute;
61import org.sleuthkit.datamodel.DataSource;
62import org.sleuthkit.datamodel.IngestJobInfo;
63import org.sleuthkit.datamodel.Score;
64import org.sleuthkit.datamodel.SleuthkitCase;
65import org.sleuthkit.datamodel.TskCoreException;
66
70final class DiscoveryUiUtils {
71
72 private final static Logger logger = Logger.getLogger(DiscoveryUiUtils.class.getName());
73 private static final int BYTE_UNIT_CONVERSION = 1000;
74 private static final int ICON_SIZE = 16;
75 private static final String RED_CIRCLE_ICON_PATH = "org/sleuthkit/autopsy/images/red-circle-exclamation.png";
76 private static final String YELLOW_CIRCLE_ICON_PATH = "org/sleuthkit/autopsy/images/yellow-circle-yield.png";
77 private static final String DELETE_ICON_PATH = "org/sleuthkit/autopsy/images/file-icon-deleted.png";
78 private static final String UNSUPPORTED_DOC_PATH = "org/sleuthkit/autopsy/images/image-extraction-not-supported.png";
79 private static final ImageIcon INTERESTING_SCORE_ICON = new ImageIcon(ImageUtilities.loadImage(YELLOW_CIRCLE_ICON_PATH, false));
80 private static final ImageIcon NOTABLE_SCORE_ICON = new ImageIcon(ImageUtilities.loadImage(RED_CIRCLE_ICON_PATH, false));
81 private static final ImageIcon DELETED_ICON = new ImageIcon(ImageUtilities.loadImage(DELETE_ICON_PATH, false));
82 private static final ImageIcon UNSUPPORTED_DOCUMENT_THUMBNAIL = new ImageIcon(ImageUtilities.loadImage(UNSUPPORTED_DOC_PATH, false));
83 private static final String THUMBNAIL_FORMAT = "png"; //NON-NLS
84 private static final String VIDEO_THUMBNAIL_DIR = "video-thumbnails"; //NON-NLS
85 private static final BufferedImage VIDEO_DEFAULT_IMAGE = getDefaultVideoThumbnail();
86
87 @NbBundle.Messages({"# {0} - fileSize",
88 "# {1} - units",
89 "DiscoveryUiUtility.sizeLabel.text=Size: {0} {1}",
90 "DiscoveryUiUtility.bytes.text=bytes",
91 "DiscoveryUiUtility.kiloBytes.text=KB",
92 "DiscoveryUiUtility.megaBytes.text=MB",
93 "DiscoveryUiUtility.gigaBytes.text=GB",
94 "DiscoveryUiUtility.terraBytes.text=TB"})
103 static String getFileSizeString(long bytes) {
104 long size = bytes;
105 int unitsSwitchValue = 0;
106 while (size > BYTE_UNIT_CONVERSION && unitsSwitchValue < 4) {
107 size /= BYTE_UNIT_CONVERSION;
108 unitsSwitchValue++;
109 }
110 String units;
111 switch (unitsSwitchValue) {
112 case 1:
113 units = Bundle.DiscoveryUiUtility_kiloBytes_text();
114 break;
115 case 2:
116 units = Bundle.DiscoveryUiUtility_megaBytes_text();
117 break;
118 case 3:
119 units = Bundle.DiscoveryUiUtility_gigaBytes_text();
120 break;
121 case 4:
122 units = Bundle.DiscoveryUiUtility_terraBytes_text();
123 break;
124 default:
125 units = Bundle.DiscoveryUiUtility_bytes_text();
126 break;
127 }
128 return Bundle.DiscoveryUiUtility_sizeLabel_text(size, units);
129 }
130
137 static ImageIcon getUnsupportedImageThumbnail() {
138 return UNSUPPORTED_DOCUMENT_THUMBNAIL;
139 }
140
153 static List<String> getSetNames(BlackboardArtifact.ARTIFACT_TYPE artifactType, BlackboardAttribute.ATTRIBUTE_TYPE setNameAttribute) throws TskCoreException {
154 List<BlackboardArtifact> arts = Case.getCurrentCase().getSleuthkitCase().getBlackboardArtifacts(artifactType);
155 List<String> setNames = new ArrayList<>();
156 for (BlackboardArtifact art : arts) {
157 for (BlackboardAttribute attr : art.getAttributes()) {
158 if (attr.getAttributeType().getTypeID() == setNameAttribute.getTypeID()) {
159 String setName = attr.getValueString();
160 if (!setNames.contains(setName)) {
161 setNames.add(setName);
162 }
163 }
164 }
165 }
166 Collections.sort(setNames);
167 return setNames;
168 }
169
178 @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
179 static boolean isPointOnIcon(Component comp, Point point) {
180 return comp instanceof JComponent && point.x >= comp.getX() && point.x <= comp.getX() + ICON_SIZE && point.y >= comp.getY() && point.y <= comp.getY() + ICON_SIZE;
181 }
182
191 @NbBundle.Messages({"DiscoveryUiUtils.isDeleted.text=All instances of file are deleted."})
192 @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
193 static void setDeletedIcon(boolean isDeleted, javax.swing.JLabel isDeletedLabel) {
194 if (isDeleted) {
195 isDeletedLabel.setIcon(DELETED_ICON);
196 isDeletedLabel.setToolTipText(Bundle.DiscoveryUiUtils_isDeleted_text());
197 } else {
198 isDeletedLabel.setIcon(null);
199 isDeletedLabel.setToolTipText(null);
200 }
201 }
202
210 @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
211 static void setScoreIcon(ResultFile resultFile, javax.swing.JLabel scoreLabel) {
212 ImageIcon icon = null;
213
214 Score score = resultFile.getScore();
215 if (score != null && score.getSignificance() != null) {
216 switch (score.getSignificance()) {
217 case NOTABLE:
218 icon = NOTABLE_SCORE_ICON;
219 break;
220 case LIKELY_NOTABLE:
221 icon = INTERESTING_SCORE_ICON;
222 break;
223 case LIKELY_NONE:
224 case NONE:
225 case UNKNOWN:
226 default:
227 icon = null;
228 break;
229 }
230 }
231
232 scoreLabel.setIcon(icon);
233 scoreLabel.setToolTipText(resultFile.getScoreDescription());
234 }
235
236
242 static int getIconSize() {
243 return ICON_SIZE;
244 }
245
251 @NbBundle.Messages({"DiscoveryUiUtils.resultsIncomplete.text=Discovery results may be incomplete"})
252 static void displayErrorMessage(DiscoveryDialog dialog) {
253 //check if modules run and assemble message
254 try {
255 SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
256 Map<Long, DataSourceModulesWrapper> dataSourceIngestModules = new HashMap<>();
257 for (DataSource dataSource : skCase.getDataSources()) {
258 dataSourceIngestModules.put(dataSource.getId(), new DataSourceModulesWrapper(dataSource.getName()));
259 }
260
261 for (IngestJobInfo jobInfo : skCase.getIngestJobs()) {
262 dataSourceIngestModules.get(jobInfo.getObjectId()).updateModulesRun(jobInfo);
263 }
264 String message = "";
265 for (DataSourceModulesWrapper dsmodulesWrapper : dataSourceIngestModules.values()) {
266 message += dsmodulesWrapper.getMessage();
267 }
268 if (!message.isEmpty()) {
269 JScrollPane messageScrollPane = new JScrollPane();
270 JTextPane messageTextPane = new JTextPane();
271 messageTextPane.setText(message);
272 messageTextPane.setVisible(true);
273 messageTextPane.setEditable(false);
274 messageTextPane.setCaretPosition(0);
275 messageScrollPane.setMaximumSize(new Dimension(600, 100));
276 messageScrollPane.setPreferredSize(new Dimension(600, 100));
277 messageScrollPane.setViewportView(messageTextPane);
278 JOptionPane.showMessageDialog(dialog, messageScrollPane, Bundle.DiscoveryUiUtils_resultsIncomplete_text(), JOptionPane.PLAIN_MESSAGE);
279 }
280 } catch (NoCurrentCaseException | TskCoreException ex) {
281 logger.log(Level.WARNING, "Exception while determining which modules have been run for Discovery", ex);
282 }
283 dialog.validateDialog();
284 }
285
295 @NbBundle.Messages({"# {0} - file name",
296 "DiscoveryUiUtils.genVideoThumb.progress.text=extracting temporary file {0}"})
297 static void getVideoThumbnails(VideoThumbnailsWrapper thumbnailWrapper) {
298 AbstractFile file = thumbnailWrapper.getResultFile().getFirstInstance();
299 String cacheDirectory;
300 try {
301 cacheDirectory = Case.getCurrentCaseThrows().getCacheDirectory();
302 } catch (NoCurrentCaseException ex) {
303 cacheDirectory = null;
304 logger.log(Level.WARNING, "Unable to get cache directory, video thumbnails will not be saved", ex);
305 }
306 if (cacheDirectory == null || file.getMd5Hash() == null || !Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash()).toFile().exists()) {
307 java.io.File tempFile;
308 try {
309 tempFile = getVideoFileInTempDir(file);
310 } catch (NoCurrentCaseException ex) {
311 logger.log(Level.WARNING, "Exception while getting open case.", ex); //NON-NLS
312 int[] framePositions = new int[]{
313 0,
314 0,
315 0,
316 0};
317 thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
318 return;
319 }
320 if (tempFile.exists() == false || tempFile.length() < file.getSize()) {
321 ProgressHandle progress = ProgressHandle.createHandle(Bundle.DiscoveryUiUtils_genVideoThumb_progress_text(file.getName()));
322 progress.start(100);
323 try {
324 Files.createParentDirs(tempFile);
325 if (Thread.interrupted()) {
326 int[] framePositions = new int[]{
327 0,
328 0,
329 0,
330 0};
331 thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
332 return;
333 }
334 ContentUtils.writeToFile(file, tempFile, progress, null, true);
335 } catch (IOException ex) {
336 logger.log(Level.WARNING, "Error extracting temporary file for " + file.getParentPath() + "/" + file.getName(), ex); //NON-NLS
337 } finally {
338 progress.finish();
339 }
340 }
341 VideoCapture videoFile = new VideoCapture(); // will contain the video
342 BufferedImage bufferedImage = null;
343
344 try {
345 if (!videoFile.open(tempFile.toString())) {
346 logger.log(Level.WARNING, "Error opening {0} for preview generation.", file.getParentPath() + "/" + file.getName()); //NON-NLS
347 int[] framePositions = new int[]{
348 0,
349 0,
350 0,
351 0};
352 thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
353 return;
354 }
355 double fps = videoFile.get(5); // gets frame per second
356 double totalFrames = videoFile.get(7); // gets total frames
357 if (fps <= 0 || totalFrames <= 0) {
358 logger.log(Level.WARNING, "Error getting fps or total frames for {0}", file.getParentPath() + "/" + file.getName()); //NON-NLS
359 int[] framePositions = new int[]{
360 0,
361 0,
362 0,
363 0};
364 thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
365 return;
366 }
367 if (Thread.interrupted()) {
368 int[] framePositions = new int[]{
369 0,
370 0,
371 0,
372 0};
373 thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
374 return;
375 }
376
377 double duration = 1000 * (totalFrames / fps); //total milliseconds
378
379 int[] framePositions = new int[]{
380 (int) (duration * .01),
381 (int) (duration * .25),
382 (int) (duration * .5),
383 (int) (duration * .75),};
384
385 Mat imageMatrix = new Mat();
386 List<Image> videoThumbnails = new ArrayList<>();
387 if (cacheDirectory == null || file.getMd5Hash() == null) {
388 cacheDirectory = null;
389 } else {
390 try {
391 FileUtils.forceMkdir(Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash()).toFile());
392 } catch (IOException ex) {
393 cacheDirectory = null;
394 logger.log(Level.WARNING, "Unable to make video thumbnails directory, thumbnails will not be saved", ex);
395 }
396 }
397 for (int i = 0; i < framePositions.length; i++) {
398 if (!videoFile.set(0, framePositions[i])) {
399 logger.log(Level.WARNING, "Error seeking to " + framePositions[i] + "ms in {0}", file.getParentPath() + "/" + file.getName()); //NON-NLS
400 // If we can't set the time, continue to the next frame position and try again.
401
402 videoThumbnails.add(VIDEO_DEFAULT_IMAGE);
403 if (cacheDirectory != null) {
404 try {
405 ImageIO.write(VIDEO_DEFAULT_IMAGE, THUMBNAIL_FORMAT,
406 Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash(), i + "-" + framePositions[i] + "." + THUMBNAIL_FORMAT).toFile()); //NON-NLS)
407 } catch (IOException ex) {
408 logger.log(Level.WARNING, "Unable to save default video thumbnail for " + file.getMd5Hash() + " at frame position " + framePositions[i], ex);
409 }
410 }
411 continue;
412 }
413 // Read the frame into the image/matrix.
414 if (!videoFile.read(imageMatrix)) {
415 logger.log(Level.WARNING, "Error reading frame at " + framePositions[i] + "ms from {0}", file.getParentPath() + "/" + file.getName()); //NON-NLS
416 // If the image is bad for some reason, continue to the next frame position and try again.
417 videoThumbnails.add(VIDEO_DEFAULT_IMAGE);
418 if (cacheDirectory != null) {
419 try {
420 ImageIO.write(VIDEO_DEFAULT_IMAGE, THUMBNAIL_FORMAT,
421 Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash(), i + "-" + framePositions[i] + "." + THUMBNAIL_FORMAT).toFile()); //NON-NLS)
422 } catch (IOException ex) {
423 logger.log(Level.WARNING, "Unable to save default video thumbnail for " + file.getMd5Hash() + " at frame position " + framePositions[i], ex);
424 }
425 }
426
427 continue;
428 }
429 // If the image is empty, return since no buffered image can be created.
430 if (imageMatrix.empty()) {
431 videoThumbnails.add(VIDEO_DEFAULT_IMAGE);
432 if (cacheDirectory != null) {
433 try {
434 ImageIO.write(VIDEO_DEFAULT_IMAGE, THUMBNAIL_FORMAT,
435 Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash(), i + "-" + framePositions[i] + "." + THUMBNAIL_FORMAT).toFile()); //NON-NLS)
436 } catch (IOException ex) {
437 logger.log(Level.WARNING, "Unable to save default video thumbnail for " + file.getMd5Hash() + " at frame position " + framePositions[i], ex);
438 }
439 }
440 continue;
441 }
442
443 int matrixColumns = imageMatrix.cols();
444 int matrixRows = imageMatrix.rows();
445
446 // Convert the matrix that contains the frame to a buffered image.
447 if (bufferedImage == null) {
448 bufferedImage = new BufferedImage(matrixColumns, matrixRows, BufferedImage.TYPE_3BYTE_BGR);
449 }
450
451 byte[] data = new byte[matrixRows * matrixColumns * (int) (imageMatrix.elemSize())];
452 imageMatrix.get(0, 0, data); //copy the image to data
453
454 if (imageMatrix.channels() == 3) {
455 for (int k = 0; k < data.length; k += 3) {
456 byte temp = data[k];
457 data[k] = data[k + 2];
458 data[k + 2] = temp;
459 }
460 }
461
462 bufferedImage.getRaster().setDataElements(0, 0, matrixColumns, matrixRows, data);
463 if (Thread.interrupted()) {
464 thumbnailWrapper.setThumbnails(videoThumbnails, framePositions);
465 try {
466 FileUtils.forceDelete(Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash()).toFile());
467 } catch (IOException ex) {
468 logger.log(Level.WARNING, "Unable to delete directory for cancelled video thumbnail process", ex);
469 }
470 return;
471 }
472 BufferedImage thumbnail = ScalrWrapper.resize(bufferedImage, Scalr.Method.SPEED, Scalr.Mode.FIT_TO_HEIGHT, ImageUtils.ICON_SIZE_LARGE, ImageUtils.ICON_SIZE_MEDIUM, Scalr.OP_ANTIALIAS);
473 //We are height limited here so it can be wider than it can be tall.Scalr maintains the aspect ratio.
474 videoThumbnails.add(thumbnail);
475 if (cacheDirectory != null) {
476 try {
477 ImageIO.write(thumbnail, THUMBNAIL_FORMAT,
478 Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash(), i + "-" + framePositions[i] + "." + THUMBNAIL_FORMAT).toFile()); //NON-NLS)
479 } catch (IOException ex) {
480 logger.log(Level.WARNING, "Unable to save video thumbnail for " + file.getMd5Hash() + " at frame position " + framePositions[i], ex);
481 }
482 }
483 }
484 thumbnailWrapper.setThumbnails(videoThumbnails, framePositions);
485 } finally {
486 videoFile.release(); // close the file}
487 }
488 } else {
489 loadSavedThumbnails(cacheDirectory, thumbnailWrapper, VIDEO_DEFAULT_IMAGE);
490 }
491 }
492
498 private static BufferedImage getDefaultVideoThumbnail() {
499 try {
500 return ImageIO.read(ImageUtils.class
501 .getResourceAsStream("/org/sleuthkit/autopsy/images/failedToCreateVideoThumb.png"));//NON-NLS
502 } catch (IOException ex) {
503 logger.log(Level.SEVERE, "Failed to load 'failed to create video' placeholder.", ex); //NON-NLS
504 }
505 return null;
506 }
507
518 private static void loadSavedThumbnails(String cacheDirectory, VideoThumbnailsWrapper thumbnailWrapper, BufferedImage failedVideoThumbImage) {
519 int[] framePositions = new int[4];
520 List<Image> videoThumbnails = new ArrayList<>();
521 int thumbnailNumber = 0;
522 String md5 = thumbnailWrapper.getResultFile().getFirstInstance().getMd5Hash();
523 for (String fileName : Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, md5).toFile().list()) {
524 try {
525 videoThumbnails.add(ImageIO.read(Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, md5, fileName).toFile()));
526 } catch (IOException ex) {
527 videoThumbnails.add(failedVideoThumbImage);
528 logger.log(Level.WARNING, "Unable to read saved video thumbnail " + fileName + " for " + md5, ex);
529 }
530 int framePos = Integer.valueOf(FilenameUtils.getBaseName(fileName).substring(2));
531 framePositions[thumbnailNumber] = framePos;
532 thumbnailNumber++;
533 }
534 thumbnailWrapper.setThumbnails(videoThumbnails, framePositions);
535 }
536
543 private static List<Image> createDefaultThumbnailList(BufferedImage failedVideoThumbImage) {
544 List<Image> videoThumbnails = new ArrayList<>();
545 videoThumbnails.add(failedVideoThumbImage);
546 videoThumbnails.add(failedVideoThumbImage);
547 videoThumbnails.add(failedVideoThumbImage);
548 videoThumbnails.add(failedVideoThumbImage);
549 return videoThumbnails;
550 }
551
555 private DiscoveryUiUtils() {
556 //private constructor in a utility class intentionally left blank
557 }
558}
static synchronized BufferedImage resize(BufferedImage input, int width, int height)
static< T > long writeToFile(Content content, java.io.File outputFile, ProgressHandle progress, Future< T > worker, boolean source)

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