Autopsy  4.21.0
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  */
19 package org.sleuthkit.autopsy.discovery.ui;
20 
21 import com.google.common.io.Files;
22 import java.awt.Component;
23 import java.awt.Dimension;
24 import java.awt.Image;
25 import java.awt.Point;
26 import java.awt.image.BufferedImage;
27 import java.io.IOException;
28 import java.nio.file.Paths;
29 import java.util.ArrayList;
30 import java.util.Collections;
31 import java.util.HashMap;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.logging.Level;
35 import javax.imageio.ImageIO;
36 import javax.swing.ImageIcon;
37 import javax.swing.JComponent;
38 import javax.swing.JOptionPane;
39 import javax.swing.JScrollPane;
40 import javax.swing.JTextPane;
41 import org.apache.commons.io.FileUtils;
42 import org.apache.commons.io.FilenameUtils;
43 import org.imgscalr.Scalr;
44 import org.netbeans.api.progress.ProgressHandle;
45 import org.opencv.core.Mat;
46 import org.opencv.highgui.VideoCapture;
47 import org.openide.util.ImageUtilities;
48 import org.openide.util.NbBundle;
58 import org.sleuthkit.datamodel.AbstractFile;
59 import org.sleuthkit.datamodel.BlackboardArtifact;
60 import org.sleuthkit.datamodel.BlackboardAttribute;
61 import org.sleuthkit.datamodel.DataSource;
62 import org.sleuthkit.datamodel.IngestJobInfo;
63 import org.sleuthkit.datamodel.Score;
64 import org.sleuthkit.datamodel.SleuthkitCase;
65 import org.sleuthkit.datamodel.TskCoreException;
66 
70 final 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 
250  @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
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 File getVideoFileInTempDir(AbstractFile file)

Copyright © 2012-2022 Basis Technology. Generated on: Tue Feb 6 2024
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.