/* * 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.casemodule.services; import java.io.Closeable; import java.io.IOException; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifactTag; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; /** * A per case Autopsy service that manages the addition of content and artifact * tags to the case database. */ public class TagsManager implements Closeable { private static final Logger LOGGER = Logger.getLogger(TagsManager.class.getName()); @NbBundle.Messages("TagsManager.predefTagNames.bookmark.text=Bookmark") private static final Set<String> STANDARD_TAG_DISPLAY_NAMES = new HashSet<>(Arrays.asList(Bundle.TagsManager_predefTagNames_bookmark_text())); private final SleuthkitCase caseDb; /** * Constructs a per case Autopsy service that manages the addition of * content and artifact tags to the case database. * * @param caseDb The case database. */ TagsManager(SleuthkitCase caseDb) { this.caseDb = caseDb; } /** * Gets a list of all tag names currently in the case database. * * @return A list, possibly empty, of TagName objects. * * @throws TskCoreException If there is an error querying the case database. */ public List<TagName> getAllTagNames() throws TskCoreException { return caseDb.getAllTagNames(); } /** * Gets a list of all tag names currently in use in the case database for * tagging content or artifacts. * * @return A list, possibly empty, of TagName objects. * * @throws TskCoreException If there is an error querying the case database. */ public List<TagName> getTagNamesInUse() throws TskCoreException { return caseDb.getTagNamesInUse(); } /** * Gets a map of tag display names to tag name entries in the case database. * It has keys for the display names of the standard tag types, the current * user's custom tag types, and the tags in the case database. The value for * a given key will be null if the corresponding tag type is defined, but a * tag name entry has not yet added to the case database. In that case, * addTagName may be called to add the tag name entry. * * @return A map of tag display names to possibly null TagName object * references. * * @throws TskCoreException if there is an error querying the case database. */ public synchronized Map<String, TagName> getDisplayNamesToTagNamesMap() throws TskCoreException { /** * Order is important here. The keys (display names) for the current * user's custom tag types are added to the map first, with null TagName * values. If tag name entries exist for those keys, loading of the tag * names from the database supplies the missing values. Standard tag * names are added during the initialization of the case database. * * Note that creating the map on demand increases the probability that * the display names of newly added custom tag types and the display * names of tags added to a multi-user case by other users appear in the * map. */ Map<String, TagName> tagNames = new HashMap<>(); Set<TagNameDefiniton> customTypes = TagNameDefiniton.getTagNameDefinitions(); for (TagNameDefiniton tagType : customTypes) { tagNames.put(tagType.getDisplayName(), null); } for (TagName tagName : caseDb.getAllTagNames()) { tagNames.put(tagName.getDisplayName(), tagName); } return new HashMap<>(tagNames); } /** * Adds a tag name entry to the case database and adds a corresponding tag * type to the current user's custom tag types. * * @param displayName The display name for the new tag type. * * @return A TagName representing the tag name database entry that can be * used to add instances of the tag type to the case database. * * @throws TagNameAlreadyExistsException If the tag name already exists in * the case database. * @throws TskCoreException If there is an error adding the tag * name to the case database. */ public synchronized TagName addTagName(String displayName) throws TagNameAlreadyExistsException, TskCoreException { return addTagName(displayName, "", TagName.HTML_COLOR.NONE); } /** * Adds a tag name entry to the case database and adds a corresponding tag * type to the current user's custom tag types. * * @param displayName The display name for the new tag type. * @param description The description for the new tag type. * * @return A TagName object that can be used to add instances of the tag * type to the case database. * * @throws TagNameAlreadyExistsException If the tag name already exists in * the case database. * @throws TskCoreException If there is an error adding the tag * name to the case database. */ public synchronized TagName addTagName(String displayName, String description) throws TagNameAlreadyExistsException, TskCoreException { return addTagName(displayName, description, TagName.HTML_COLOR.NONE); } /** * Adds a tag name entry to the case database and adds a corresponding tag * type to the current user's custom tag types. * * @param displayName The display name for the new tag type. * @param description The description for the new tag type. * @param color The color to associate with the new tag type. * * @return A TagName object that can be used to add instances of the tag * type to the case database. * * @throws TagNameAlreadyExistsException If the tag name already exists. * @throws TskCoreException If there is an error adding the tag * name to the case database. */ public synchronized TagName addTagName(String displayName, String description, TagName.HTML_COLOR color) throws TagNameAlreadyExistsException, TskCoreException { try { TagName tagName = caseDb.addTagName(displayName, description, color); if (!STANDARD_TAG_DISPLAY_NAMES.contains(displayName)) { Set<TagNameDefiniton> customTypes = TagNameDefiniton.getTagNameDefinitions(); customTypes.add(new TagNameDefiniton(displayName, description, color)); TagNameDefiniton.setTagNameDefinitions(customTypes); } return tagName; } catch (TskCoreException ex) { List<TagName> existingTagNames = caseDb.getAllTagNames(); for (TagName tagName : existingTagNames) { if (tagName.getDisplayName().equals(displayName)) { throw new TagNameAlreadyExistsException(); } } throw ex; } } /** * Tags a content object. * * @param content The content to tag. * @param tagName The representation of the desired tag type in the case * database, which can be obtained by calling getTagNames * and/or addTagName. * * @return A ContentTag object representing the new tag. * * @throws TskCoreException If there is an error adding the tag to the case * database. */ public ContentTag addContentTag(Content content, TagName tagName) throws TskCoreException { return addContentTag(content, tagName, "", -1, -1); } /** * Tags a content object. * * @param content The content to tag. * @param tagName The representation of the desired tag type in the case * database, which can be obtained by calling getTagNames * and/or addTagName. * @param comment A comment to store with the tag. * * @return A ContentTag object representing the new tag. * * @throws TskCoreException If there is an error adding the tag to the case * database. */ public ContentTag addContentTag(Content content, TagName tagName, String comment) throws TskCoreException { return addContentTag(content, tagName, comment, -1, -1); } /** * Tags a content object or a section of a content object. * * @param content The content to tag. * @param tagName The representation of the desired tag type in the * case database, which can be obtained by calling * getTagNames and/or addTagName. * @param comment A comment to store with the tag. * @param beginByteOffset Designates the beginning of a tagged section. * @param endByteOffset Designates the end of a tagged section. * * @return A ContentTag object representing the new tag. * * @throws TskCoreException If there is an error adding the tag to the case * database. */ public ContentTag addContentTag(Content content, TagName tagName, String comment, long beginByteOffset, long endByteOffset) throws TskCoreException { ContentTag tag; tag = caseDb.addContentTag(content, tagName, comment, beginByteOffset, endByteOffset); try { Case.getCurrentCase().notifyContentTagAdded(tag); } catch (IllegalStateException ex) { throw new TskCoreException("Added a tag to a closed case", ex); } return tag; } /** * Deletes a content tag. * * @param tag The tag to delete. * * @throws TskCoreException If there is an error deleting the tag from the * case database. */ public void deleteContentTag(ContentTag tag) throws TskCoreException { caseDb.deleteContentTag(tag); try { Case.getCurrentCase().notifyContentTagDeleted(tag); } catch (IllegalStateException ex) { throw new TskCoreException("Deleted a tag from a closed case", ex); } } /** * Gets all content tags for the current case. * * @return A list, possibly empty, of content tags. * * @throws TskCoreException If there is an error getting the tags from the * case database. */ public synchronized List<ContentTag> getAllContentTags() throws TskCoreException { return caseDb.getAllContentTags(); } /** * Gets content tags count by tag name. * * @param tagName The representation of the desired tag type in the case * database, which can be obtained by calling getTagNames * and/or addTagName. * * @return A count of the content tags with the specified tag name. * * @throws TskCoreException If there is an error getting the tags count from * the case database. */ public synchronized long getContentTagsCountByTagName(TagName tagName) throws TskCoreException { return caseDb.getContentTagsCountByTagName(tagName); } /** * Gets a content tag by tag id. * * @param tagId The tag id of interest. * * @return The content tag with the specified tag id. * * @throws TskCoreException If there is an error getting the tag from the * case database. */ public synchronized ContentTag getContentTagByTagID(long tagId) throws TskCoreException { return caseDb.getContentTagByID(tagId); } /** * Gets content tags by tag name. * * @param tagName The tag name of interest. * * @return A list, possibly empty, of the content tags with the specified * tag name. * * @throws TskCoreException If there is an error getting the tags from the * case database. */ public synchronized List<ContentTag> getContentTagsByTagName(TagName tagName) throws TskCoreException { return caseDb.getContentTagsByTagName(tagName); } /** * Gets content tags count by content. * * @param content The content of interest. * * @return A list, possibly empty, of the tags that have been applied to the * content. * * @throws TskCoreException If there is an error getting the tags from the * case database. */ public synchronized List<ContentTag> getContentTagsByContent(Content content) throws TskCoreException { return caseDb.getContentTagsByContent(content); } /** * Tags an artifact. * * @param artifact The artifact to tag. * @param tagName The representation of the desired tag type in the case * database, which can be obtained by calling getTagNames * and/or addTagName. * * @return A BlackboardArtifactTag object representing the new tag. * * @throws TskCoreException If there is an error adding the tag to the case * database. */ public synchronized BlackboardArtifactTag addBlackboardArtifactTag(BlackboardArtifact artifact, TagName tagName) throws TskCoreException { return addBlackboardArtifactTag(artifact, tagName, ""); } /** * Tags an artifact. * * @param artifact The artifact to tag. * @param tagName The representation of the desired tag type in the case * database, which can be obtained by calling getTagNames * and/or addTagName. * @param comment A comment to store with the tag. * * @return A BlackboardArtifactTag object representing the new tag. * * @throws TskCoreException If there is an error adding the tag to the case * database. */ public synchronized BlackboardArtifactTag addBlackboardArtifactTag(BlackboardArtifact artifact, TagName tagName, String comment) throws TskCoreException { BlackboardArtifactTag tag = caseDb.addBlackboardArtifactTag(artifact, tagName, comment); try { Case.getCurrentCase().notifyBlackBoardArtifactTagAdded(tag); } catch (IllegalStateException ex) { throw new TskCoreException("Added a tag to a closed case", ex); } return tag; } /** * Deletes an artifact tag. * * @param tag The tag to delete. * * @throws TskCoreException If there is an error deleting the tag from the * case database. */ public synchronized void deleteBlackboardArtifactTag(BlackboardArtifactTag tag) throws TskCoreException { caseDb.deleteBlackboardArtifactTag(tag); try { Case.getCurrentCase().notifyBlackBoardArtifactTagDeleted(tag); } catch (IllegalStateException ex) { throw new TskCoreException("Deleted a tag from a closed case", ex); } } /** * Gets all artifact tags for the current case. * * @return A list, possibly empty, of artifact tags. * * @throws TskCoreException If there is an error getting the tags from the * case database. */ public synchronized List<BlackboardArtifactTag> getAllBlackboardArtifactTags() throws TskCoreException { return caseDb.getAllBlackboardArtifactTags(); } /** * Gets an artifact tags count by tag name. * * @param tagName The representation of the desired tag type in the case * database, which can be obtained by calling getTagNames * and/or addTagName. * * @return A count of the artifact tags with the specified tag name. * * @throws TskCoreException If there is an error getting the tags count from * the case database. */ public synchronized long getBlackboardArtifactTagsCountByTagName(TagName tagName) throws TskCoreException { return caseDb.getBlackboardArtifactTagsCountByTagName(tagName); } /** * Gets an artifact tag by tag id. * * @param tagId The tag id of interest. * * @return The artifact tag with the specified tag id. * * @throws TskCoreException If there is an error getting the tag from the * case database. */ public synchronized BlackboardArtifactTag getBlackboardArtifactTagByTagID(long tagId) throws TskCoreException { return caseDb.getBlackboardArtifactTagByID(tagId); } /** * Gets artifact tags by tag name. * * @param tagName The representation of the desired tag type in the case * database, which can be obtained by calling getTagNames * and/or addTagName. * * @return A list, possibly empty, of the artifact tags with the specified * tag name. * * @throws TskCoreException If there is an error getting the tags from the * case database. */ public synchronized List<BlackboardArtifactTag> getBlackboardArtifactTagsByTagName(TagName tagName) throws TskCoreException { return caseDb.getBlackboardArtifactTagsByTagName(tagName); } /** * Gets artifact tags for a particular artifact. * * @param artifact The artifact of interest. * * @return A list, possibly empty, of the tags that have been applied to the * artifact. * * @throws TskCoreException If there is an error getting the tags from the * case database. */ public synchronized List<BlackboardArtifactTag> getBlackboardArtifactTagsByArtifact(BlackboardArtifact artifact) throws TskCoreException { return caseDb.getBlackboardArtifactTagsByArtifact(artifact); } /** * Returns true if the tag display name contains an illegal character. Used * after a tag display name is retrieved from user input. * * @param content Display name of the tag being added. * * @return boolean indicating whether the name has an invalid character. */ public static boolean containsIllegalCharacters(String content) { return (content.contains("\\") || content.contains(":") || content.contains("*") || content.contains("?") || content.contains("\"") || content.contains("<") || content.contains(">") || content.contains("|") || content.contains(",") || content.contains(";")); } /** * Exception thrown if there is an attempt to add a duplicate tag name. */ public static class TagNameAlreadyExistsException extends Exception { private static final long serialVersionUID = 1L; } /** * Checks whether a tag name with a given display name exists in the case * database. * * @param tagDisplayName The display name. * * @return True or false. * * @deprecated Not reliable for multi-user cases. */ @Deprecated public synchronized boolean tagNameExists(String tagDisplayName) { try { Map<String, TagName> tagNames = getDisplayNamesToTagNamesMap(); return tagNames.containsKey(tagDisplayName) && (tagNames.get(tagDisplayName) != null); } catch (TskCoreException ex) { LOGGER.log(Level.SEVERE, "Error querying case database for tag names", ex); return false; } } /** * Closes the tags manager. * * @throws IOException If there is a problem closing the tags manager. * @deprecated Tags manager clients should not close the tags manager. */ @Override @Deprecated public synchronized void close() throws IOException { } }