/* * Autopsy Forensic Browser * * Copyright 2011-2016 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.datamodel; import java.lang.ref.SoftReference; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.stream.Collectors; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.concurrent.Task; import javafx.scene.image.Image; import javafx.util.Pair; import javax.annotation.Nonnull; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.text.WordUtils; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.imagegallery.FileTypeUtils; import org.sleuthkit.autopsy.imagegallery.ThumbnailCache; import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.Tag; import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; /** * A file that contains visual information such as an image or video. */ public abstract class DrawableFile { private static final Logger LOGGER = Logger.getLogger(DrawableFile.class.getName()); public static DrawableFile create(AbstractFile abstractFileById, boolean analyzed) { return create(abstractFileById, analyzed, FileTypeUtils.hasVideoMIMEType(abstractFileById)); } /** * Skip the database query if we have already determined the file type. */ public static DrawableFile create(AbstractFile abstractFileById, boolean analyzed, boolean isVideo) { return isVideo ? new VideoFile(abstractFileById, analyzed) : new ImageFile(abstractFileById, analyzed); } public static DrawableFile create(Long id, boolean analyzed) throws TskCoreException, IllegalStateException { return create(Case.getCurrentCase().getSleuthkitCase().getAbstractFileById(id), analyzed); } private SoftReference<Image> imageRef; private String drawablePath; private final AbstractFile file; private final SimpleBooleanProperty analyzed; private final SimpleObjectProperty<Category> category = new SimpleObjectProperty<>(null); private String make; private String model; protected DrawableFile(AbstractFile file, Boolean analyzed) { this.analyzed = new SimpleBooleanProperty(analyzed); this.file = file; } public abstract boolean isVideo(); public List<Pair<DrawableAttribute<?>, Collection<?>>> getAttributesList() { return DrawableAttribute.getValues().stream() .map(this::makeAttributeValuePair) .collect(Collectors.toList()); } public String getMIMEType() { return file.getMIMEType(); } public long getId() { return file.getId(); } public long getCtime() { return file.getCtime(); } public long getCrtime() { return file.getCrtime(); } public long getAtime() { return file.getAtime(); } public long getMtime() { return file.getMtime(); } public String getMd5Hash() { return file.getMd5Hash(); } public String getName() { return file.getName(); } public String getAtimeAsDate() { return file.getAtimeAsDate(); } public synchronized String getUniquePath() throws TskCoreException { return file.getUniquePath(); } public SleuthkitCase getSleuthkitCase() { return file.getSleuthkitCase(); } private Pair<DrawableAttribute<?>, Collection<?>> makeAttributeValuePair(DrawableAttribute<?> t) { return new Pair<>(t, t.getValue(DrawableFile.this)); } public String getModel() { if (model == null) { model = WordUtils.capitalizeFully((String) getValueOfBBAttribute(ARTIFACT_TYPE.TSK_METADATA_EXIF, ATTRIBUTE_TYPE.TSK_DEVICE_MODEL)); } return model; } public String getMake() { if (make == null) { make = WordUtils.capitalizeFully((String) getValueOfBBAttribute(ARTIFACT_TYPE.TSK_METADATA_EXIF, ATTRIBUTE_TYPE.TSK_DEVICE_MAKE)); } return make; } public Set<TagName> getTagNames() { try { return getContentTags().stream() .map(Tag::getName) .collect(Collectors.toSet()); } catch (TskCoreException ex) { Logger.getAnonymousLogger().log(Level.WARNING, "problem looking up " + DrawableAttribute.TAGS.getDisplayName() + " for " + file.getName(), ex); //NON-NLS } catch (IllegalStateException ex) { Logger.getAnonymousLogger().log(Level.WARNING, "there is no case open; failed to look up " + DrawableAttribute.TAGS.getDisplayName() + " for " + getContentPathSafe(), ex); //NON-NLS } return Collections.emptySet(); } protected Object getValueOfBBAttribute(ARTIFACT_TYPE artType, ATTRIBUTE_TYPE attrType) { try { //why doesn't file.getArtifacts() work? //TODO: this seams like overkill, use a more targeted query ArrayList<BlackboardArtifact> artifacts = file.getArtifacts(artType);// getAllArtifacts(); for (BlackboardArtifact artf : artifacts) { if (artf.getArtifactTypeID() == artType.getTypeID()) { for (BlackboardAttribute attr : artf.getAttributes()) { if (attr.getAttributeType().getTypeID() == attrType.getTypeID()) { switch (attr.getAttributeType().getValueType()) { case BYTE: return attr.getValueBytes(); case DOUBLE: return attr.getValueDouble(); case INTEGER: return attr.getValueInt(); case LONG: return attr.getValueLong(); case STRING: return attr.getValueString(); case DATETIME: return attr.getValueLong(); } } } } } } catch (TskCoreException ex) { Logger.getAnonymousLogger().log(Level.WARNING, ex, () -> MessageFormat.format("problem looking up {0}/{1}" + " " + " for {2}", new Object[]{artType.getDisplayName(), attrType.getDisplayName(), getContentPathSafe()})); //NON-NLS } return ""; } public void setCategory(Category category) { categoryProperty().set(category); } public Category getCategory() { updateCategory(); return category.get(); } public SimpleObjectProperty<Category> categoryProperty() { return category; } /** * set the category property to the most severe one found */ private void updateCategory() { try { category.set(getContentTags().stream() .map(Tag::getName).filter(CategoryManager::isCategoryTagName) .map(TagName::getDisplayName) .map(Category::fromDisplayName) .sorted().findFirst() //sort by severity and take the first .orElse(Category.ZERO) ); } catch (TskCoreException ex) { LOGGER.log(Level.WARNING, "problem looking up category for " + this.getContentPathSafe(), ex); //NON-NLS } catch (IllegalStateException ex) { // We get here many times if the case is closed during ingest, so don't print out a ton of warnings. } } private List<ContentTag> getContentTags() throws TskCoreException { return getSleuthkitCase().getContentTagsByContent(file); } @Deprecated public Image getThumbnail() { try { return getThumbnailTask().get(); } catch (InterruptedException | ExecutionException ex) { return null; } } public Task<Image> getThumbnailTask() { return ThumbnailCache.getDefault().getThumbnailTask(this); } @Deprecated //use non-blocking getReadFullSizeImageTask instead for most cases public Image getFullSizeImage() { try { return getReadFullSizeImageTask().get(); } catch (InterruptedException | ExecutionException ex) { return null; } } public Task<Image> getReadFullSizeImageTask() { Image image = (imageRef != null) ? imageRef.get() : null; if (image == null || image.isError()) { Task<Image> readImageTask = getReadFullSizeImageTaskHelper(); readImageTask.stateProperty().addListener(stateProperty -> { switch (readImageTask.getState()) { case SUCCEEDED: try { imageRef = new SoftReference<>(readImageTask.get()); } catch (InterruptedException | ExecutionException exception) { LOGGER.log(Level.WARNING, getMessageTemplate(exception), getContentPathSafe()); } break; } }); return readImageTask; } else { return TaskUtils.taskFrom(() -> image); } } abstract String getMessageTemplate(Exception exception); abstract Task<Image> getReadFullSizeImageTaskHelper(); public void setAnalyzed(Boolean analyzed) { this.analyzed.set(analyzed); } public boolean isAnalyzed() { return analyzed.get(); } public AbstractFile getAbstractFile() { return this.file; } abstract Double getWidth(); abstract Double getHeight(); public String getDrawablePath() { if (drawablePath != null) { return drawablePath; } else { try { drawablePath = StringUtils.removeEnd(getUniquePath(), getName()); return drawablePath; } catch (TskCoreException ex) { LOGGER.log(Level.WARNING, "failed to get drawablePath from " + getContentPathSafe(), ex); //NON-NLS return ""; } } } public Set<String> getHashSetNames() throws TskCoreException { return file.getHashSetNames(); } @Nonnull public Set<String> getHashSetNamesUnchecked() { try { return getHashSetNames(); } catch (TskCoreException ex) { LOGGER.log(Level.WARNING, "Failed to get hash set names", ex); //NON-NLS return Collections.emptySet(); } } /** * Get the unique path for this DrawableFile, or if that fails, just return * the name. * * @param content * * @return */ public String getContentPathSafe() { try { return getUniquePath(); } catch (TskCoreException tskCoreException) { String contentName = this.getName(); LOGGER.log(Level.SEVERE, "Failed to get unique path for " + contentName, tskCoreException); //NOI18N NON-NLS return contentName; } } }