/*
* Autopsy Forensic Browser
*
* Copyright 2015-16 Basis Technology Corp.
* Contact: 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.imagegallery;
import com.google.common.collect.Sets;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import static java.util.Objects.isNull;
import java.util.Set;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.imageio.ImageIO;
import org.apache.commons.lang3.StringUtils;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Enum style singleton to provide utilities related to questions about a files
* type, and whether it should be supported in Image Gallery.
*
* TODO: refactor this to remove code that duplicates
* org.sleuthkit.autopsy.coreutils.ImageUtils
*/
public enum FileTypeUtils {
instance;
private static final Logger LOGGER = Logger.getLogger(FileTypeUtils.class.getName());
/**
* Set of specific mimetypes (as strings) that we should support(ie, include
* in db and show to user). These are in addition to all image/* or video/*
* types
*/
private static final Set<String> supportedMimeTypes = new HashSet<>();
/**
* set of specific mimetypes to support as videos, in addition to any type
* prefixed by video/
*/
private static final Set<String> videoMimeTypes = new HashSet<>();
/**
* set of extensions to support as images. lowercase without the period.
*/
private static final Set<String> imageExtensions = new HashSet<>();
/**
* set of extensions to support as videos. lowercase without the period.
*/
private static final Set<String> videoExtensions = new HashSet<>();
/**
* set of all extensions that we should support(ie, include in db and show
* to user). Initialized to be the concatenation of imageExtensions and
* videoExtensions sets.
*/
private static final Set<String> supportedExtensions;
/**
* Lazily instantiated FileTypeDetector to use when the mimetype of a file
* is needed
*/
private static FileTypeDetector FILE_TYPE_DETECTOR;
/**
* static initalizer block to initialize sets of extensions and mimetypes to
* be supported
*/
static {
ImageIO.scanForPlugins();
//add all extension ImageIO claims to support
imageExtensions.addAll(Stream.of(ImageIO.getReaderFileSuffixes())
.map(String::toLowerCase)
.collect(Collectors.toList()));
//add list of known image extensions
imageExtensions.addAll(Arrays.asList(
"bmp" //Bitmap NON-NLS
, "gif" //gif NON-NLS
, "jpg", "jpeg", "jpe", "jp2", "jpx" //jpeg variants NON-NLS
, "pbm", "pgm", "ppm" // Portable image format variants NON-NLS
, "png" //portable network graphic NON-NLS
, "tga" //targa NON-NLS
, "psd" //photoshop NON-NLS
, "tif", "tiff" //tiff variants NON-NLS
, "yuv", "ico" //icons NON-NLS
, "ai" //illustrator NON-NLS
, "svg" //scalable vector graphics NON-NLS
, "sn", "ras" //sun raster NON-NLS
, "ico" //windows icons NON-NLS
, "tga" //targa NON-NLS
, "wmf", "emf" // windows meta file NON-NLS
, "wmz", "emz" //compressed windows meta file NON-NLS
));
//add list of known video extensions
videoExtensions.addAll(Arrays.asList("fxm", "aaf", "3gp", "asf", "avi", //NON-NLS
"m1v", "m2v", "m4v", "mp4", "mov", "mpeg", "mpg", "mpe", "mp4", //NON-NLS
"rm", "wmv", "mpv", "flv", "swf")); //NON-NLS
supportedExtensions = Sets.union(imageExtensions, videoExtensions);
//add list of mimetypes to count as videos even though they aren't prefixed by video/
videoMimeTypes.addAll(Arrays.asList("application/x-shockwave-flash")); //NON-NLS
supportedMimeTypes.addAll(videoMimeTypes);
/*
* TODO: windows .cur cursor files get misidentified as
* application/x-123, so we claim to support application/x-123 so we
* don't miss them: ie this is a hack to cover another bug. when this is
* fixed, we should remove application/x-123 from the list of supported
* mime types.
*/
supportedMimeTypes.addAll(Arrays.asList("application/x-123")); //NON-NLS
supportedMimeTypes.addAll(Arrays.asList("application/x-wmf")); //NON-NLS
supportedMimeTypes.addAll(Arrays.asList("application/x-emf")); //NON-NLS
//add list of mimetypes ImageIO claims to support
supportedMimeTypes.addAll(Stream.of(ImageIO.getReaderMIMETypes())
.map(String::toLowerCase)
.collect(Collectors.toList()));
supportedMimeTypes.removeIf("application/octet-stream"::equals); //this is rarely usefull NON-NLS
}
public static Set<String> getAllSupportedMimeTypes() {
return Collections.unmodifiableSet(supportedMimeTypes);
}
static Set<String> getAllSupportedExtensions() {
return Collections.unmodifiableSet(supportedExtensions);
}
static synchronized FileTypeDetector getFileTypeDetector() throws FileTypeDetector.FileTypeDetectorInitException {
/*
* TODO: EUR-740 recreate FileTypeDetector when the user creates new
* user defined file types
*/
if (isNull(FILE_TYPE_DETECTOR)) {
FILE_TYPE_DETECTOR = new FileTypeDetector();
}
return FILE_TYPE_DETECTOR;
}
/**
* is the given file supported by image analyzer? ie, does it have a
* supported mime type (image/*, or video/*). if no mime type is found, does
* it have a supported extension or a jpeg/png header?
*
* @param file
*
* @return true if this file is supported or false if not
*/
public static boolean isDrawable(AbstractFile file) throws TskCoreException, FileTypeDetector.FileTypeDetectorInitException {
return hasDrawableMIMEType(file);
}
static boolean isDrawableMimeType(String mimeType) {
if (StringUtils.isBlank(mimeType)) {
return false;
} else {
String mimeTypeLower = mimeType.toLowerCase();
return mimeTypeLower.startsWith("image/")
|| mimeTypeLower.startsWith("video/")
|| supportedMimeTypes.contains(mimeTypeLower);
}
}
/**
*
* TODO: EUR-740 recreate FileTypeDetector when the user creates new user
* defined file types
*
* does the given file have drawable/supported mime type
*
* @param file
*
* @return an Optional containg: True if the file has an image or video mime
* type. False if a non image/video mimetype. empty Optional if a
* mimetype could not be detected.
*/
static boolean hasDrawableMIMEType(AbstractFile file) throws TskCoreException, FileTypeDetector.FileTypeDetectorInitException {
String mimeType = getFileTypeDetector().detect(file).toLowerCase();
return isDrawableMimeType(mimeType) || (mimeType.equals("audio/x-aiff") && "tiff".equalsIgnoreCase(file.getNameExtension()));
}
/**
* is the given file a video
*
* @param file
*
* @return true if the given file has a video mime type (video/*,
* application/x-shockwave-flash, etc) or, if no mimetype is
* available, a video extension.
*/
public static boolean hasVideoMIMEType(AbstractFile file) {
try {
String mimeType = getFileTypeDetector().detect(file).toLowerCase();
return mimeType.startsWith("video/") || videoMimeTypes.contains(mimeType);
} catch (FileTypeDetector.FileTypeDetectorInitException | TskCoreException ex) {
LOGGER.log(Level.SEVERE, "Error determining MIME type of " + getContentPathSafe(file), ex);
return false;
}
}
/**
* Get the unique path for the content, or if that fails, just return the
* name.
*
* @param content
*
* @return
*/
static String getContentPathSafe(Content content) {
try {
return content.getUniquePath();
} catch (TskCoreException tskCoreException) {
String contentName = content.getName();
LOGGER.log(Level.SEVERE, "Failed to get unique path for " + contentName, tskCoreException); //NOI18N NON-NLS
return contentName;
}
}
}