mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-14 17:06:16 +00:00
Merge pull request #398 from jawallace/thumbnail_reporting
Thumbnail reporting
This commit is contained in:
commit
cc1a7f7f47
@ -41,6 +41,7 @@ import org.openide.nodes.NodeListener;
|
||||
import org.openide.nodes.NodeMemberEvent;
|
||||
import org.openide.nodes.NodeReorderEvent;
|
||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
|
||||
import org.sleuthkit.autopsy.coreutils.ImageUtils;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
@ -63,7 +64,7 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer {
|
||||
private int curPage;
|
||||
private int totalPages;
|
||||
private int curPageImages;
|
||||
private int iconSize = ThumbnailViewNode.ICON_SIZE_MEDIUM;
|
||||
private int iconSize = ImageUtils.ICON_SIZE_MEDIUM;
|
||||
private final PageUpdater pageUpdater = new PageUpdater();
|
||||
|
||||
/**
|
||||
@ -240,13 +241,13 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer {
|
||||
|
||||
private void thumbnailSizeComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_thumbnailSizeComboBoxActionPerformed
|
||||
|
||||
iconSize = ThumbnailViewNode.ICON_SIZE_MEDIUM; //default size
|
||||
iconSize = ImageUtils.ICON_SIZE_MEDIUM; //default size
|
||||
switch(thumbnailSizeComboBox.getSelectedIndex()) {
|
||||
case 0:
|
||||
iconSize = ThumbnailViewNode.ICON_SIZE_SMALL;
|
||||
iconSize = ImageUtils.ICON_SIZE_SMALL;
|
||||
break;
|
||||
case 2:
|
||||
iconSize = ThumbnailViewNode.ICON_SIZE_LARGE;
|
||||
iconSize = ImageUtils.ICON_SIZE_LARGE;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -21,21 +21,13 @@ package org.sleuthkit.autopsy.corecomponents;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import javax.imageio.ImageIO;
|
||||
import org.openide.nodes.AbstractNode;
|
||||
import org.openide.nodes.Children;
|
||||
import org.openide.nodes.Node;
|
||||
import org.openide.util.lookup.Lookups;
|
||||
import org.sleuthkit.autopsy.contentviewers.Utilities;
|
||||
import org.sleuthkit.autopsy.coreutils.ImageUtils;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.ContentVisitor;
|
||||
import org.sleuthkit.datamodel.DerivedFile;
|
||||
import org.sleuthkit.datamodel.File;
|
||||
import org.sleuthkit.datamodel.LocalFile;
|
||||
import org.sleuthkit.datamodel.LayoutFile;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* Complementary class to ThumbnailViewNode. Children node factory. Wraps around
|
||||
@ -49,13 +41,12 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
*/
|
||||
class ThumbnailViewChildren extends Children.Keys<Integer> {
|
||||
|
||||
private static final IsSupportedContentVisitor isSupportedVisitor = new IsSupportedContentVisitor();
|
||||
static final int IMAGES_PER_PAGE = 200;
|
||||
private Node parent;
|
||||
private final HashMap<Integer, List<Node>> pages = new HashMap<Integer, List<Node>>();
|
||||
private int totalImages = 0;
|
||||
private int totalPages = 0;
|
||||
private int iconSize = ThumbnailViewNode.ICON_SIZE_MEDIUM;
|
||||
private int iconSize = ImageUtils.ICON_SIZE_MEDIUM;
|
||||
private static final Logger logger = Logger.getLogger(ThumbnailViewChildren.class.getName());
|
||||
|
||||
/**
|
||||
@ -152,7 +143,7 @@ class ThumbnailViewChildren extends Children.Keys<Integer> {
|
||||
if (node != null) {
|
||||
Content content = node.getLookup().lookup(Content.class);
|
||||
if (content != null) {
|
||||
return content.accept(isSupportedVisitor);
|
||||
return ImageUtils.thumbnailSupported(content);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@ -162,59 +153,6 @@ class ThumbnailViewChildren extends Children.Keys<Integer> {
|
||||
this.iconSize = iconSize;
|
||||
}
|
||||
|
||||
private static class IsSupportedContentVisitor extends ContentVisitor.Default<Boolean> {
|
||||
|
||||
private final List<String> SUPP_EXTENSIONS;
|
||||
|
||||
IsSupportedContentVisitor() {
|
||||
String[] supportedImagesSuffixes = ImageIO.getReaderFileSuffixes();
|
||||
|
||||
SUPP_EXTENSIONS = new ArrayList<String>(supportedImagesSuffixes.length);
|
||||
for (int i = 0; i < supportedImagesSuffixes.length; ++i) {
|
||||
String suffix = supportedImagesSuffixes[i];
|
||||
SUPP_EXTENSIONS.add("." + suffix);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(DerivedFile f) {
|
||||
return isSupported(f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(LocalFile f) {
|
||||
return isSupported(f);
|
||||
}
|
||||
|
||||
public Boolean visit(LayoutFile f) {
|
||||
return isSupported(f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean visit(File f) {
|
||||
return isSupported(f);
|
||||
}
|
||||
|
||||
public Boolean isSupported(AbstractFile f) {
|
||||
final String fName = f.getName();
|
||||
final int dotIdx = fName.lastIndexOf('.');
|
||||
if (dotIdx == -1) {
|
||||
return Utilities.isJpegFileHeader(f);
|
||||
}
|
||||
|
||||
final String ext = fName.substring(dotIdx).toLowerCase();
|
||||
|
||||
// Note: thumbnail generator only supports JPG, GIF, and PNG for now
|
||||
return (f.getSize() > 0
|
||||
&& SUPP_EXTENSIONS.contains(ext));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean defaultVisit(Content cntnt) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Node representing page node, a parent of image nodes, with a name showing
|
||||
* children range
|
||||
|
@ -18,30 +18,12 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.corecomponents;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.MediaTracker;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.logging.Level;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JFrame;
|
||||
import org.openide.nodes.Children;
|
||||
import org.openide.nodes.FilterNode;
|
||||
import org.openide.nodes.Node;
|
||||
import org.openide.util.Exceptions;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.corelibs.ScalrWrapper;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.ImageUtils;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.ReadContentInputStream;
|
||||
import org.sleuthkit.datamodel.TskException;
|
||||
|
||||
/**
|
||||
* Node that wraps around original node and adds the bitmap icon representing
|
||||
@ -50,12 +32,7 @@ import org.sleuthkit.datamodel.TskException;
|
||||
class ThumbnailViewNode extends FilterNode {
|
||||
|
||||
private SoftReference<Image> iconCache = null;
|
||||
private static final Image defaultIcon = new ImageIcon("/org/sleuthkit/autopsy/images/file-icon.png").getImage();
|
||||
private static final Logger logger = Logger.getLogger(ThumbnailViewNode.class.getName());
|
||||
static final int ICON_SIZE_SMALL = 50;
|
||||
static final int ICON_SIZE_MEDIUM = 100;
|
||||
static final int ICON_SIZE_LARGE = 200;
|
||||
private int iconSize = ICON_SIZE_MEDIUM;
|
||||
private int iconSize = ImageUtils.ICON_SIZE_MEDIUM;
|
||||
//private final BufferedImage defaultIconBI;
|
||||
|
||||
/**
|
||||
@ -87,91 +64,17 @@ class ThumbnailViewNode extends FilterNode {
|
||||
Content content = this.getLookup().lookup(Content.class);
|
||||
|
||||
if (content != null) {
|
||||
// If a thumbnail file is already saved locally
|
||||
if (getFile(content.getId()).exists()) {
|
||||
try {
|
||||
BufferedImage bicon = ImageIO.read(getFile(content.getId()));
|
||||
if (bicon == null) {
|
||||
icon = ThumbnailViewNode.defaultIcon;
|
||||
} else if (bicon.getWidth() != iconSize) {
|
||||
icon = generateAndSaveIcon(content);
|
||||
} else {
|
||||
icon = bicon;
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
icon = ThumbnailViewNode.defaultIcon;
|
||||
}
|
||||
} else { // Make a new icon
|
||||
icon = generateAndSaveIcon(content);
|
||||
}
|
||||
icon = ImageUtils.getIcon(content, iconSize);
|
||||
} else {
|
||||
icon = ThumbnailViewNode.defaultIcon;
|
||||
icon = ImageUtils.getDefaultIcon();
|
||||
}
|
||||
|
||||
iconCache = new SoftReference<Image>(icon);
|
||||
iconCache = new SoftReference<>(icon);
|
||||
}
|
||||
|
||||
return icon;
|
||||
}
|
||||
|
||||
private Image generateAndSaveIcon(Content content) {
|
||||
Image icon = null;
|
||||
try {
|
||||
icon = generateIcon(content);
|
||||
if (icon == null) {
|
||||
icon = ThumbnailViewNode.defaultIcon;
|
||||
} else {
|
||||
File f = getFile(content.getId());
|
||||
if (f.exists()) {
|
||||
f.delete();
|
||||
}
|
||||
ImageIO.write((BufferedImage) icon, "jpg", getFile(content.getId()));
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.WARNING, "Could not write cache thumbnail: " + content, ex);
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
/*
|
||||
* Generate a scaled image
|
||||
*/
|
||||
private BufferedImage generateIcon(Content content) {
|
||||
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
inputStream = new ReadContentInputStream(content);
|
||||
BufferedImage bi = ImageIO.read(inputStream);
|
||||
if (bi == null) {
|
||||
logger.log(Level.WARNING, "No image reader for file: " + content.getName());
|
||||
return null;
|
||||
}
|
||||
BufferedImage biScaled = ScalrWrapper.resizeFast(bi, iconSize);
|
||||
|
||||
return biScaled;
|
||||
}catch (OutOfMemoryError e) {
|
||||
logger.log(Level.WARNING, "Could not scale image (too large): " + content.getName(), e);
|
||||
return null;
|
||||
}
|
||||
catch (Exception e) {
|
||||
logger.log(Level.WARNING, "Could not scale image: " + content.getName(), e);
|
||||
return null;
|
||||
} finally {
|
||||
if (inputStream != null) {
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.WARNING, "Could not close input stream after resizing thumbnail: " + content.getName(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static File getFile(long id) {
|
||||
return new File(Case.getCurrentCase().getCacheDirectory() + File.separator + id + ".jpg");
|
||||
}
|
||||
|
||||
public void setIconSize(int iconSize) {
|
||||
this.iconSize = iconSize;
|
||||
iconCache = null;
|
||||
|
196
Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java
Executable file
196
Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java
Executable file
@ -0,0 +1,196 @@
|
||||
/*
|
||||
*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2012 Basis Technology Corp.
|
||||
*
|
||||
* Copyright 2012 42six Solutions.
|
||||
* Contact: aebadirad <at> 42six <dot> com
|
||||
* Project Contact/Architect: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.coreutils;
|
||||
|
||||
import java.awt.Image;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.ImageIcon;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.contentviewers.Utilities;
|
||||
import org.sleuthkit.autopsy.corelibs.ScalrWrapper;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.ReadContentInputStream;
|
||||
|
||||
/**
|
||||
* Utilities for creating and manipulating thumbnail and icon images.
|
||||
* @author jwallace
|
||||
*/
|
||||
public class ImageUtils {
|
||||
public static final int ICON_SIZE_SMALL = 50;
|
||||
public static final int ICON_SIZE_MEDIUM = 100;
|
||||
public static final int ICON_SIZE_LARGE = 200;
|
||||
private static final Logger logger = Logger.getLogger(ImageUtils.class.getName());
|
||||
private static final Image DEFAULT_ICON = new ImageIcon("/org/sleuthkit/autopsy/images/file-icon.png").getImage();
|
||||
private static final List<String> SUPP_EXTENSIONS = Arrays.asList(ImageIO.getReaderFileSuffixes());
|
||||
|
||||
/**
|
||||
* Get the default Icon, which is the icon for a file.
|
||||
* @return
|
||||
*/
|
||||
public static Image getDefaultIcon() {
|
||||
return DEFAULT_ICON;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can a thumbnail be generated for the content?
|
||||
*
|
||||
* @param content
|
||||
* @return
|
||||
*/
|
||||
public static boolean thumbnailSupported(Content content) {
|
||||
if (content instanceof AbstractFile == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AbstractFile f = (AbstractFile) content;
|
||||
final String fName = f.getName();
|
||||
final int dotIdx = fName.lastIndexOf('.');
|
||||
if (dotIdx == -1 || dotIdx == (fName.length() - 1)) {
|
||||
return Utilities.isJpegFileHeader(f);
|
||||
}
|
||||
|
||||
final String ext = fName.substring(dotIdx + 1).toLowerCase();
|
||||
|
||||
// Note: thumbnail generator only supports JPG, GIF, and PNG for now
|
||||
return (f.getSize() > 0
|
||||
&& SUPP_EXTENSIONS.contains(ext));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an icon of a specified size.
|
||||
*
|
||||
* @param content
|
||||
* @param iconSize
|
||||
* @return
|
||||
*/
|
||||
public static Image getIcon(Content content, int iconSize) {
|
||||
Image icon;
|
||||
// If a thumbnail file is already saved locally
|
||||
File file = getFile(content.getId());
|
||||
if (file.exists()) {
|
||||
try {
|
||||
BufferedImage bicon = ImageIO.read(file);
|
||||
if (bicon == null) {
|
||||
icon = DEFAULT_ICON;
|
||||
} else if (bicon.getWidth() != iconSize) {
|
||||
icon = generateAndSaveIcon(content, iconSize);
|
||||
} else {
|
||||
icon = bicon;
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.WARNING, "Error while reading image.", ex);
|
||||
icon = DEFAULT_ICON;
|
||||
}
|
||||
} else { // Make a new icon
|
||||
icon = generateAndSaveIcon(content, iconSize);
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cached file of the icon. Generates the icon and its file if it
|
||||
* doesn't already exist, so this method guarantees to return a file that
|
||||
* exists.
|
||||
* @param content
|
||||
* @param iconSize
|
||||
* @return
|
||||
*/
|
||||
public static File getIconFile(Content content, int iconSize) {
|
||||
getIcon(content, iconSize);
|
||||
return getFile(content.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cached file of the content object with the given id.
|
||||
*
|
||||
* The returned file may not exist.
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
public static File getFile(long id) {
|
||||
return new File(Case.getCurrentCase().getCacheDirectory() + File.separator + id + ".jpg");
|
||||
}
|
||||
|
||||
|
||||
private static Image generateAndSaveIcon(Content content, int iconSize) {
|
||||
Image icon = null;
|
||||
try {
|
||||
icon = generateIcon(content, iconSize);
|
||||
if (icon == null) {
|
||||
return DEFAULT_ICON;
|
||||
} else {
|
||||
File f = getFile(content.getId());
|
||||
if (f.exists()) {
|
||||
f.delete();
|
||||
}
|
||||
ImageIO.write((BufferedImage) icon, "jpg", getFile(content.getId()));
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.WARNING, "Could not write cache thumbnail: " + content, ex);
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
/*
|
||||
* Generate a scaled image
|
||||
*/
|
||||
private static BufferedImage generateIcon(Content content, int iconSize) {
|
||||
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
inputStream = new ReadContentInputStream(content);
|
||||
BufferedImage bi = ImageIO.read(inputStream);
|
||||
if (bi == null) {
|
||||
logger.log(Level.WARNING, "No image reader for file: " + content.getName());
|
||||
return null;
|
||||
}
|
||||
BufferedImage biScaled = ScalrWrapper.resizeFast(bi, iconSize);
|
||||
|
||||
return biScaled;
|
||||
} catch (OutOfMemoryError e) {
|
||||
logger.log(Level.WARNING, "Could not scale image (too large): " + content.getName(), e);
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.WARNING, "Could not scale image: " + content.getName(), e);
|
||||
return null;
|
||||
} finally {
|
||||
if (inputStream != null) {
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.WARNING, "Could not close input stream after resizing thumbnail: " + content.getName(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -48,6 +48,7 @@ import javax.swing.SwingWorker;
|
||||
import org.openide.filesystems.FileUtil;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.coreutils.EscapeUtil;
|
||||
import org.sleuthkit.autopsy.coreutils.ImageUtils;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.autopsy.report.ReportProgressPanel.ReportStatus;
|
||||
@ -57,9 +58,11 @@ import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifactTag;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.ContentTag;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.TskException;
|
||||
|
||||
/**
|
||||
* Instances of this class use GeneralReportModules, TableReportModules and
|
||||
@ -566,14 +569,30 @@ public class ReportGenerator {
|
||||
comment.append(makeCommaSeparatedList(tagNamesFilter));
|
||||
}
|
||||
module.startDataType(ARTIFACT_TYPE.TSK_TAG_ARTIFACT.getDisplayName(), comment.toString());
|
||||
module.startTable(new ArrayList<>(Arrays.asList("Result Type", "Tag", "Comment", "Source File")));
|
||||
String[] tableHeaders;
|
||||
if (module instanceof ReportHTML) {
|
||||
tableHeaders = new String[] {"Result Type", "Tag", "Comment", "Source File", "Thumbnail"};
|
||||
} else {
|
||||
tableHeaders = new String[] {"Result Type", "Tag", "Comment", "Source File"};
|
||||
}
|
||||
module.startTable(new ArrayList<>(Arrays.asList(tableHeaders)));
|
||||
}
|
||||
|
||||
// Give the modules the rows for the content tags.
|
||||
for (BlackboardArtifactTag tag : tags) {
|
||||
if (passesTagNamesFilter(tag.getName().getDisplayName())) {
|
||||
List<String> row;
|
||||
File thumbFile;
|
||||
for (TableReportModule module : tableModules) {
|
||||
module.addRow(new ArrayList<>(Arrays.asList(tag.getArtifact().getArtifactTypeName(), tag.getName().getDisplayName(), tag.getComment(), tag.getContent().getName())));
|
||||
// We have specific behavior if the module is a ReportHTML.
|
||||
// We add a thumbnail if the artifact is associated with an
|
||||
// image file.
|
||||
row = new ArrayList<>(Arrays.asList(tag.getArtifact().getArtifactTypeName(), tag.getName().getDisplayName(), tag.getComment(), tag.getContent().getName()));
|
||||
if (module instanceof ReportHTML) {
|
||||
((ReportHTML) module).addRowWithTaggedContentHyperlink(row, tag);
|
||||
} else {
|
||||
module.addRow(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1334,21 +1353,6 @@ public class ReportGenerator {
|
||||
private Map<Integer,String> getMappedAttributes() {
|
||||
return ReportGenerator.this.getMappedAttributes(attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a BlackboardArtifact.
|
||||
*
|
||||
* @param long artifactId An artifact id
|
||||
* @return The BlackboardArtifact associated with the artifact id
|
||||
*/
|
||||
private BlackboardArtifact getArtifactByID(long artifactId) {
|
||||
try {
|
||||
return skCase.getBlackboardArtifact(artifactId);
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.WARNING, "Failed to get blackboard artifact by ID.", ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,9 +39,10 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.logging.Level;
|
||||
import org.openide.filesystems.FileUtil;
|
||||
import org.openide.util.Exceptions;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.openide.filesystems.FileUtil;
|
||||
import org.sleuthkit.autopsy.coreutils.ImageUtils;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.datamodel.ContentUtils.ExtractFscContentVisitor;
|
||||
import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||
@ -51,17 +52,20 @@ import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifactTag;
|
||||
import org.sleuthkit.datamodel.ContentTag;
|
||||
import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM;
|
||||
|
||||
public class ReportHTML implements TableReportModule {
|
||||
private static final Logger logger = Logger.getLogger(ReportHTML.class.getName());
|
||||
private static final String THUMBS_REL_PATH = "thumbs" + File.separator;
|
||||
private static ReportHTML instance;
|
||||
private Case currentCase;
|
||||
private SleuthkitCase skCase;
|
||||
|
||||
private Map<String, Integer> dataTypes;
|
||||
private String path;
|
||||
private String thumbsPath;
|
||||
private String currentDataType; // name of current data type
|
||||
private Integer rowCount; // number of rows (aka artifacts or tags) for the current data type
|
||||
private Writer out;
|
||||
@ -90,6 +94,7 @@ public class ReportHTML implements TableReportModule {
|
||||
dataTypes = new TreeMap<>();
|
||||
|
||||
path = "";
|
||||
thumbsPath = "";
|
||||
currentDataType = "";
|
||||
rowCount = 0;
|
||||
|
||||
@ -266,8 +271,10 @@ public class ReportHTML implements TableReportModule {
|
||||
refresh();
|
||||
// Setup the path for the HTML report
|
||||
this.path = path + "HTML Report" + File.separator;
|
||||
this.thumbsPath = this.path + "thumbs" + File.separator;
|
||||
try {
|
||||
FileUtil.createFolder(new File(this.path));
|
||||
FileUtil.createFolder(new File(this.thumbsPath));
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.SEVERE, "Unable to make HTML report folder.");
|
||||
}
|
||||
@ -499,21 +506,67 @@ public class ReportHTML implements TableReportModule {
|
||||
|
||||
/**
|
||||
* Saves a local copy of a tagged file and adds a row with a hyper link to
|
||||
* the file.
|
||||
* the file. The hyper link will be a thumbnail if the Content associated
|
||||
* with the given ContentTag is an image.
|
||||
*
|
||||
* @param row Values for each data cell in the row.
|
||||
* @param contentTag A content tag to use to make the hyper link.
|
||||
*/
|
||||
public void addRowWithTaggedContentHyperlink(List<String> row, ContentTag contentTag) {
|
||||
// Only handling AbstractFiles at present.
|
||||
String tagName = contentTag.getName().getDisplayName();
|
||||
AbstractFile file;
|
||||
if (contentTag.getContent() instanceof AbstractFile) {
|
||||
file = (AbstractFile)contentTag.getContent();
|
||||
StringBuilder linkContent = new StringBuilder();
|
||||
if (ImageUtils.thumbnailSupported(file)) {
|
||||
linkContent.append("<img src=\"").append(prepareThumbnail(file)).append("\" />");
|
||||
} else {
|
||||
linkContent.append("View File");
|
||||
}
|
||||
addRowWithTaggedContentHyperlink(row, file, tagName, linkContent.toString());
|
||||
}
|
||||
else {
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a local copy of a tagged file and adds a row with a hyper link to
|
||||
* the file. The hyper link will be a thumbnail if the Content associated
|
||||
* with the given BlackboardArtifactTag is an image.
|
||||
*
|
||||
* @param row Values for each data cell in the row.
|
||||
* @param artifactTag An artifact tag to use to make the hyper link.
|
||||
*/
|
||||
public void addRowWithTaggedContentHyperlink(List<String> row, BlackboardArtifactTag artifactTag) {
|
||||
String tagName = artifactTag.getName().getDisplayName();
|
||||
AbstractFile file;
|
||||
try {
|
||||
file = Case.getCurrentCase().getSleuthkitCase().getAbstractFileById(artifactTag.getArtifact().getObjectID());
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.WARNING, "Error while getting content from a blackboard artifact to report on.", ex);
|
||||
return;
|
||||
}
|
||||
|
||||
// Only include content for images
|
||||
if (ImageUtils.thumbnailSupported(file)) {
|
||||
StringBuilder linkContent = new StringBuilder();
|
||||
linkContent.append("<img src=\"").append(prepareThumbnail(file)).append("\" />");
|
||||
addRowWithTaggedContentHyperlink(row, file, tagName, linkContent.toString());
|
||||
} else {
|
||||
row.add("");
|
||||
this.addRow(row);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a local copy of a tagged file and adds a row with a hyper link to
|
||||
* the file. The content of the hyperlink is provided in linkHTMLContent.
|
||||
*
|
||||
* @param row Values for each data cell in the row
|
||||
* @param file The file to link to in the report.
|
||||
* @param tagName the name of the tag that the content was flagged by
|
||||
* @param linkHTMLContent the html that will be the body of the link
|
||||
*/
|
||||
private void addRowWithTaggedContentHyperlink(List<String> row, AbstractFile file, String tagName, String linkHTMLContent) {
|
||||
// Don't make a local copy of the file if it is a directory or unallocated space.
|
||||
if (file.isDir() ||
|
||||
file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS ||
|
||||
@ -522,33 +575,33 @@ public class ReportHTML implements TableReportModule {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make a folder for the local file with the same name as the tag.
|
||||
// Make a folder for the local file with the same tagName as the tag.
|
||||
StringBuilder localFilePath = new StringBuilder();
|
||||
localFilePath.append(path);
|
||||
localFilePath.append(contentTag.getName().getDisplayName());
|
||||
localFilePath.append(tagName);
|
||||
File localFileFolder = new File(localFilePath.toString());
|
||||
if (!localFileFolder.exists()) {
|
||||
localFileFolder.mkdirs();
|
||||
}
|
||||
|
||||
// Construct a file name for the local file that incorporates the file id to ensure uniqueness.
|
||||
// Construct a file tagName for the local file that incorporates the file id to ensure uniqueness.
|
||||
String fileName = file.getName();
|
||||
String objectIdSuffix = "_" + file.getId();
|
||||
int lastDotIndex = fileName.lastIndexOf(".");
|
||||
if (lastDotIndex != -1 && lastDotIndex != 0) {
|
||||
// The file name has a conventional extension. Insert the object id before the '.' of the extension.
|
||||
// The file tagName has a conventional extension. Insert the object id before the '.' of the extension.
|
||||
fileName = fileName.substring(0, lastDotIndex) + objectIdSuffix + fileName.substring(lastDotIndex, fileName.length());
|
||||
}
|
||||
else {
|
||||
// The file has no extension or the only '.' in the file is an initial '.', as in a hidden file.
|
||||
// Add the object id to the end of the file name.
|
||||
// Add the object id to the end of the file tagName.
|
||||
fileName += objectIdSuffix;
|
||||
}
|
||||
localFilePath.append(File.separator);
|
||||
localFilePath.append(fileName);
|
||||
|
||||
// If the local file doesn't already exist, create it now.
|
||||
// The existence check is necessary because it is possible to apply multiple tags with the same name to a file.
|
||||
// The existence check is necessary because it is possible to apply multiple tags with the same tagName to a file.
|
||||
File localFile = new File(localFilePath.toString());
|
||||
if (!localFile.exists()) {
|
||||
ExtractFscContentVisitor.extract(file, localFile, null, null);
|
||||
@ -558,7 +611,9 @@ public class ReportHTML implements TableReportModule {
|
||||
StringBuilder localFileLink = new StringBuilder();
|
||||
localFileLink.append("<a href=\"file:///");
|
||||
localFileLink.append(localFilePath.toString());
|
||||
localFileLink.append("\">View File</a>");
|
||||
localFileLink.append("\">");
|
||||
localFileLink.append(linkHTMLContent);
|
||||
localFileLink.append("</a>");
|
||||
row.add(localFileLink.toString());
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
@ -892,4 +947,17 @@ public class ReportHTML implements TableReportModule {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String prepareThumbnail(AbstractFile file) {
|
||||
File thumbFile = ImageUtils.getIconFile(file, ImageUtils.ICON_SIZE_SMALL);
|
||||
try {
|
||||
File to = new File(thumbsPath);
|
||||
FileUtil.copyFile(FileUtil.toFileObject(thumbFile), FileUtil.toFileObject(to), thumbFile.getName(), "");
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.SEVERE, "Failed to write thumb file to report directory.");
|
||||
}
|
||||
|
||||
return THUMBS_REL_PATH + thumbFile.getName();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.logging.Level;
|
||||
import org.sleuthkit.autopsy.contentviewers.Utilities;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.Version;
|
||||
import org.sleuthkit.autopsy.ingest.PipelineContext;
|
||||
@ -197,41 +198,7 @@ public final class ExifParserFileIngestModule extends IngestModuleAbstractFile {
|
||||
* @return true if to be processed
|
||||
*/
|
||||
private boolean parsableFormat(AbstractFile f) {
|
||||
return isJpegFileHeader(f);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if is jpeg file based on header
|
||||
*
|
||||
* @param file
|
||||
*
|
||||
* @return true if jpeg file, false otherwise
|
||||
*/
|
||||
public static boolean isJpegFileHeader(AbstractFile file) {
|
||||
if (file.getSize() < 100) {
|
||||
return false;
|
||||
}
|
||||
|
||||
byte[] fileHeaderBuffer = new byte[2];
|
||||
int bytesRead;
|
||||
try {
|
||||
bytesRead = file.read(fileHeaderBuffer, 0, 2);
|
||||
} catch (TskCoreException ex) {
|
||||
//ignore if can't read the first few bytes, not a JPEG
|
||||
return false;
|
||||
}
|
||||
if (bytesRead != 2) {
|
||||
return false;
|
||||
}
|
||||
/*
|
||||
* Check for the JPEG header. Since Java bytes are signed, we cast them
|
||||
* to an int first.
|
||||
*/
|
||||
if (((int) (fileHeaderBuffer[0] & 0xff) == 0xff) && ((int) (fileHeaderBuffer[1] & 0xff) == 0xd8)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return Utilities.isJpegFileHeader(f);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
Loading…
x
Reference in New Issue
Block a user