diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java index ea50eebda7..f53834c3b0 100755 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -27,15 +27,19 @@ import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.logging.Level; import javax.imageio.ImageIO; import javax.swing.ImageIcon; +import org.openide.util.Exceptions; 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.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ReadContentInputStream; import org.sleuthkit.datamodel.TskCoreException; @@ -51,7 +55,7 @@ public class ImageUtils { 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()); - + private static final List SUPP_MIME_TYPES = Arrays.asList(ImageIO.getReaderMIMETypes()); /** * Get the default Icon, which is the icon for a file. * @return @@ -72,18 +76,41 @@ public class ImageUtils { } AbstractFile f = (AbstractFile) content; - final String fName = f.getName(); - final int dotIdx = fName.lastIndexOf('.'); - if (dotIdx == -1 || dotIdx == (fName.length() - 1)) { - return isJpegFileHeader(f); + if (f.getSize() == 0) { + return false; } - final String ext = fName.substring(dotIdx + 1).toLowerCase(); + // check the blackboard for a file type attribute + try { + ArrayList attributes = f.getGenInfoAttributes(ATTRIBUTE_TYPE.TSK_FILE_TYPE_SIG); + for (BlackboardAttribute attribute : attributes) { + if (SUPP_MIME_TYPES.contains(attribute.getValueString())) { + return true; + } + } + } + catch (TskCoreException ex) { + logger.log(Level.WARNING, "Error while getting file signature from blackboard.", ex); + } + + final String fName = f.getName(); + final int dotIdx = fName.lastIndexOf('.'); + + // if we have an extension, check it + if ((dotIdx != -1) && (dotIdx != (fName.length() - 1))) { + + 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)); + // Note: thumbnail generator only supports JPG, GIF, and PNG for now + if (SUPP_EXTENSIONS.contains(ext)) { + return true; + } + } + + // if no extension or one that is not for an image, then read the content + return isJpegFileHeader(f); } + /** * Get an icon of a specified size. diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java b/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java index d43b344b13..6bcb210bfc 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java @@ -383,12 +383,19 @@ public class ReportGenerator { progress.setMaximumProgress(ARTIFACT_TYPE.values().length + 2); // +2 for content and blackboard artifact tags } } - + + + // report on the blackboard results makeBlackboardArtifactTables(); + + // report on the tagged files and artifacts makeContentTagsTables(); makeBlackboardArtifactTagsTables(); + + // report on the tagged images makeThumbnailTable(); + // finish progress, wrap up for (TableReportModule module : tableModules) { tableProgress.get(module).complete(); module.endReport(); @@ -397,11 +404,14 @@ public class ReportGenerator { return 0; } + /** + * Generate the tables for the selected blackboard artifacts + */ private void makeBlackboardArtifactTables() { // Make a comment string describing the tag names filter in effect. StringBuilder comment = new StringBuilder(); if (!tagNamesFilter.isEmpty()) { - comment.append("This report only includes results tagged with: "); + comment.append("Contains results that were tagged with one of the following: "); comment.append(makeCommaSeparatedList(tagNamesFilter)); } @@ -483,6 +493,9 @@ public class ReportGenerator { } } + /** + * Make table for tagged files + */ private void makeContentTagsTables() { // Check for cancellaton. removeCancelledTableReportModules(); @@ -508,7 +521,7 @@ public class ReportGenerator { ArrayList columnHeaders = new ArrayList<>(Arrays.asList("File", "Tag", "Comment")); StringBuilder comment = new StringBuilder(); if (!tagNamesFilter.isEmpty()) { - comment.append("This report only includes file tagged with: "); + comment.append("Contains files that were tagged with one of the following: "); comment.append(makeCommaSeparatedList(tagNamesFilter)); } if (module instanceof ReportHTML) { @@ -524,20 +537,25 @@ public class ReportGenerator { // Give the modules the rows for the content tags. for (ContentTag tag : tags) { - if (passesTagNamesFilter(tag.getName().getDisplayName())) { - checkIfTagHasImage(tag); - ArrayList rowData = new ArrayList<>(Arrays.asList(tag.getContent().getName(), tag.getName().getDisplayName(), tag.getComment())); - for (TableReportModule module : tableModules) { - // @@@ This casting is a tricky little workaround to allow the HTML report module to slip in a content hyperlink. - if (module instanceof ReportHTML) { - ReportHTML htmlReportModule = (ReportHTML)module; - htmlReportModule.addRowWithTaggedContentHyperlink(rowData, tag); - } - else { - module.addRow(rowData); - } - } + // skip tags that we are not reporting on + if (passesTagNamesFilter(tag.getName().getDisplayName()) == false) { + continue; } + + ArrayList rowData = new ArrayList<>(Arrays.asList(tag.getContent().getName(), tag.getName().getDisplayName(), tag.getComment())); + for (TableReportModule module : tableModules) { + // @@@ This casting is a tricky little workaround to allow the HTML report module to slip in a content hyperlink. + if (module instanceof ReportHTML) { + ReportHTML htmlReportModule = (ReportHTML)module; + htmlReportModule.addRowWithTaggedContentHyperlink(rowData, tag); + } + else { + module.addRow(rowData); + } + } + + // see if it is for an image so that we later report on it + checkIfTagHasImage(tag); } // The the modules content tags reporting is ended. @@ -548,6 +566,9 @@ public class ReportGenerator { } } + /** + * Generate the tables for the tagged artifacts + */ private void makeBlackboardArtifactTagsTables() { // Check for cancellaton. removeCancelledTableReportModules(); @@ -579,14 +600,18 @@ public class ReportGenerator { // Give the modules the rows for the content tags. for (BlackboardArtifactTag tag : tags) { - if (passesTagNamesFilter(tag.getName().getDisplayName())) { - checkIfTagHasImage(tag); - List row; - for (TableReportModule module : tableModules) { - row = new ArrayList<>(Arrays.asList(tag.getArtifact().getArtifactTypeName(), tag.getName().getDisplayName(), tag.getComment(), tag.getContent().getName())); - module.addRow(row); - } + if (passesTagNamesFilter(tag.getName().getDisplayName()) == false) { + continue; } + + List row; + for (TableReportModule module : tableModules) { + row = new ArrayList<>(Arrays.asList(tag.getArtifact().getArtifactTypeName(), tag.getName().getDisplayName(), tag.getComment(), tag.getContent().getName())); + module.addRow(row); + } + + // check if the tag is an image that we should later make a thumbnail for + checkIfTagHasImage(tag); } // The the modules blackboard artifact tags reporting is ended. @@ -597,7 +622,12 @@ public class ReportGenerator { } } - boolean passesTagNamesFilter(String tagName) { + /** + * Test if the user requested that this tag be reported on + * @param tagName + * @return true if it should be reported on + */ + private boolean passesTagNamesFilter(String tagName) { return tagNamesFilter.isEmpty() || tagNamesFilter.contains(tagName); } @@ -611,25 +641,36 @@ public class ReportGenerator { } } + /** + * Make a report for the files that were previously found to + * be images. + */ private void makeThumbnailTable() { for (TableReportModule module : tableModules) { - tableProgress.get(module).updateStatusLabel("Now processing Tagged Images..."); + tableProgress.get(module).updateStatusLabel("Creating thumbnails..."); if (module instanceof ReportHTML) { ReportHTML htmlModule = (ReportHTML) module; - htmlModule.startDataType("Tagged Images", "Tagged Results and Contents that contain images."); + htmlModule.startDataType("Thumbnails", "Contains thumbnails of images that are associated with tagged files and results."); List emptyHeaders = new ArrayList<>(); for (int i = 0; i < ReportHTML.THUMBNAIL_COLUMNS; i++) { emptyHeaders.add(""); } htmlModule.startTable(emptyHeaders); + htmlModule.addThumbnailRows(images); + htmlModule.endTable(); htmlModule.endDataType(); } } } + /** + * Analyze artifact associated with tag and add to internal list if it is associated + * with an image. + * @param artifactTag + */ private void checkIfTagHasImage(BlackboardArtifactTag artifactTag) { AbstractFile file; try { @@ -638,34 +679,37 @@ public class ReportGenerator { logger.log(Level.WARNING, "Error while getting content from a blackboard artifact to report on.", ex); return; } - - if (file.isDir() || - file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS || - file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS) { - return; - } - - // Only include content for images - if (ImageUtils.thumbnailSupported(file)) { - images.add(file); - } + checkIfFileIsImage(file); } + /** + * Analyze file that tag is associated with and determine if + * it is an image and should have a thumbnail reported for it. + * Images are added to internal list. + * @param contentTag + */ private void checkIfTagHasImage(ContentTag contentTag) { Content c = contentTag.getContent(); if (c instanceof AbstractFile == false) { return; } - AbstractFile file = (AbstractFile) c; + checkIfFileIsImage((AbstractFile) c); + } + /** + * If file is an image file, add it to the internal 'images' list. + * @param file + */ + private void checkIfFileIsImage(AbstractFile file) { + if (file.isDir() || file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS || file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS) { return; } - if (ImageUtils.thumbnailSupported(c)) { - images.add(c); + if (ImageUtils.thumbnailSupported(file)) { + images.add(file); } } } diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java index 5eef5996dd..318da83853 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java @@ -533,42 +533,13 @@ public class ReportHTML implements TableReportModule { return; } - // 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()); - File localFileFolder = new File(localFilePath.toString()); - if (!localFileFolder.exists()) { - localFileFolder.mkdirs(); - } - - // 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 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 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 tagName to a file. - File localFile = new File(localFilePath.toString()); - if (!localFile.exists()) { - ExtractFscContentVisitor.extract(file, localFile, null, null); - } + // save it in a folder based on the tag name + String localFilePath = saveContent(file, contentTag.getName().getDisplayName()); // Add the hyperlink to the row. A column header for it was created in startTable(). StringBuilder localFileLink = new StringBuilder(); localFileLink.append("View File"); row.add(localFileLink.toString()); @@ -628,17 +599,21 @@ public class ReportHTML implements TableReportModule { AbstractFile file = (AbstractFile) content; - String contentPath = saveContent(file); + // save copies of the orginal image and thumbnail image String thumbnailPath = prepareThumbnail(file); if (thumbnailPath == null) { continue; } + String contentPath = saveContent(file, "thumbs_fullsize"); + StringBuilder linkToThumbnail = new StringBuilder(); linkToThumbnail.append(""); linkToThumbnail.append(""); - linkToThumbnail.append(""); + linkToThumbnail.append("
"); + linkToThumbnail.append(file.getName()).append("
"); + // @@@ Add tags here currentRow.add(linkToThumbnail.toString()); totalCount++; @@ -670,11 +645,21 @@ public class ReportHTML implements TableReportModule { return false; } - public String saveContent(AbstractFile file) { + /** + * Save a local copy of the given file in the reports folder. + * @param file File to save + * @param dirName Custom top-level folder to use to store the files in (tag name, etc.) + * @return Path to where file was stored + */ + public String saveContent(AbstractFile file, String dirName) { + // clean up the dir name passed in + String dirName2 = dirName.replace("/", "_"); + dirName2 = dirName2.replace("\\", "_"); + // Make a folder for the local file with the same tagName as the tag. StringBuilder localFilePath = new StringBuilder(); localFilePath.append(path); - localFilePath.append("tagged_images"); + localFilePath.append(dirName2); File localFileFolder = new File(localFilePath.toString()); if (!localFileFolder.exists()) { localFileFolder.mkdirs();