diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java index d0eeddf633..b83cb21645 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java @@ -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; } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java index 547354ec08..d8131ccbc6 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewChildren.java @@ -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 { - private static final IsSupportedContentVisitor isSupportedVisitor = new IsSupportedContentVisitor(); static final int IMAGES_PER_PAGE = 200; private Node parent; private final HashMap> pages = new HashMap>(); 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 { 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 { this.iconSize = iconSize; } - private static class IsSupportedContentVisitor extends ContentVisitor.Default { - - private final List SUPP_EXTENSIONS; - - IsSupportedContentVisitor() { - String[] supportedImagesSuffixes = ImageIO.getReaderFileSuffixes(); - - SUPP_EXTENSIONS = new ArrayList(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 diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewNode.java b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewNode.java index b69c7db3f5..4cdc1f128a 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewNode.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/ThumbnailViewNode.java @@ -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 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,90 +64,16 @@ 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(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; diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java new file mode 100755 index 0000000000..c51334b71b --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -0,0 +1,196 @@ + /* + * + * Autopsy Forensic Browser + * + * Copyright 2012 Basis Technology Corp. + * + * Copyright 2012 42six Solutions. + * Contact: aebadirad 42six com + * Project Contact/Architect: carrier sleuthkit 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 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); + } + } + + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java b/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java index d96c066291..f95c002134 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java @@ -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 @@ -462,7 +465,7 @@ public class ReportGenerator { } continue; } - + module.addRow(rowData); } } @@ -565,15 +568,31 @@ public class ReportGenerator { comment.append("This report only includes results tagged with: "); 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"))); + module.startDataType(ARTIFACT_TYPE.TSK_TAG_ARTIFACT.getDisplayName(), comment.toString()); + 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 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); + } } } } @@ -598,7 +617,7 @@ public class ReportGenerator { iter.remove(); } } - } + } } /// @@@ Should move the methods specific to TableReportsWorker into that scope. @@ -1334,21 +1353,6 @@ public class ReportGenerator { private Map 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; - } } } diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java index 85f76bb31f..f678729622 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java @@ -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 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 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(""); + } 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 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(""); + 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 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,43 +575,45 @@ 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); } - + // Add the hyperlink to the row. A column header for it was created in startTable(). StringBuilder localFileLink = new StringBuilder(); localFileLink.append("View File"); + localFileLink.append("\">"); + localFileLink.append(linkHTMLContent); + localFileLink.append(""); 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(); + } + } diff --git a/ExifParser/src/org/sleuthkit/autopsy/exifparser/ExifParserFileIngestModule.java b/ExifParser/src/org/sleuthkit/autopsy/exifparser/ExifParserFileIngestModule.java index a8b00ca015..85cbb7c5f9 100644 --- a/ExifParser/src/org/sleuthkit/autopsy/exifparser/ExifParserFileIngestModule.java +++ b/ExifParser/src/org/sleuthkit/autopsy/exifparser/ExifParserFileIngestModule.java @@ -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