From ed74732c1d5677eeeecaa38986b95568d8de3f8b Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Mon, 14 Sep 2020 14:17:26 -0400 Subject: [PATCH 1/2] Implemented domain discovery thumbnail API --- .../discovery/search/DomainSearch.java | 23 ++- .../search/DomainSearchArtifactsCache.java | 55 +++++++ .../search/DomainSearchArtifactsLoader.java | 61 ++++++++ .../search/DomainSearchArtifactsRequest.java | 73 +++++++++ .../search/DomainSearchThumbnailLoader.java | 143 ++++++++++++++++++ .../search/DomainSearchThumbnailRequest.java | 51 +++++++ 6 files changed, 404 insertions(+), 2 deletions(-) create mode 100755 Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchArtifactsCache.java create mode 100755 Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchArtifactsLoader.java create mode 100755 Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchArtifactsRequest.java create mode 100755 Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchThumbnailLoader.java create mode 100755 Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchThumbnailRequest.java diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearch.java b/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearch.java index 94ad264155..8faf1822a7 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearch.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearch.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.discovery.search; +import java.awt.Image; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -26,6 +27,7 @@ import java.util.Map; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; import org.sleuthkit.autopsy.discovery.search.DiscoveryKeyUtils.GroupKey; import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; /** * Main class to perform the domain search. @@ -33,13 +35,15 @@ import org.sleuthkit.datamodel.SleuthkitCase; public class DomainSearch { private final DomainSearchCache searchCache; + private final DomainSearchThumbnailLoader thumbnailLoader; public DomainSearch() { - this(new DomainSearchCache()); + this(new DomainSearchCache(), new DomainSearchThumbnailLoader()); } - DomainSearch(DomainSearchCache cache) { + DomainSearch(DomainSearchCache cache, DomainSearchThumbnailLoader thumbnailLoader) { this.searchCache = cache; + this.thumbnailLoader = thumbnailLoader; } /** @@ -123,4 +127,19 @@ public class DomainSearch { return page; } + + /** + * Get a thumbnail representation of a domain name. See + * DomainSearchThumbnailRequest for more details. + * + * @param thumbnailRequest Thumbnail request for domain + * @return An Image instance or null if no thumbnail is available. + * + * @throws TskCoreException If there is an error reaching the case databases + * @throws DiscoveryException If there is an error with Discovery related + * processing + */ + public Image getThumbnail(DomainSearchThumbnailRequest thumbnailRequest) throws TskCoreException, DiscoveryException { + return thumbnailLoader.load(thumbnailRequest); + } } diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchArtifactsCache.java b/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchArtifactsCache.java new file mode 100755 index 0000000000..e1a11690d7 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchArtifactsCache.java @@ -0,0 +1,55 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 Basis Technology Corp. + * Contact: 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.discovery.search; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.LoadingCache; +import java.util.List; +import java.util.concurrent.ExecutionException; +import org.sleuthkit.datamodel.BlackboardArtifact; + +/** + * Caches artifact hits for a domain request. + */ +public class DomainSearchArtifactsCache { + + private static final int MAXIMUM_CACHE_SIZE = 5; + private static final LoadingCache> cache + = CacheBuilder.newBuilder() + .maximumSize(MAXIMUM_CACHE_SIZE) + .build(new DomainSearchArtifactsLoader()); + + /** + * Get artifact instances that match the requested criteria. If the request + * is new, the results will be automatically loaded. + * + * @param request Artifact request, specifies type, Case, and domain name. + * @return A list of matching artifacts + * + * @throws DiscoveryException Any error that occurs during the loading + * process. + */ + public List get(DomainSearchArtifactsRequest request) throws DiscoveryException { + try { + return cache.get(request); + } catch (ExecutionException ex) { + throw new DiscoveryException("Error fetching artifacts from cache", ex.getCause()); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchArtifactsLoader.java b/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchArtifactsLoader.java new file mode 100755 index 0000000000..20772f2c12 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchArtifactsLoader.java @@ -0,0 +1,61 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 Basis Technology Corp. + * Contact: 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.discovery.search; + +import com.google.common.cache.CacheLoader; +import java.util.List; +import java.util.ArrayList; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; +import org.sleuthkit.datamodel.BlackboardAttribute.Type; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.BlackboardArtifact; + +/** + * Loads artifacts for the given request. Searches TSK_DOMAIN and TSK_URL + * attributes for the requested domain name. TSK_DOMAIN is exact match (ignoring + * case). TSK_URL is sub-string match (ignoring case). + */ +public class DomainSearchArtifactsLoader extends CacheLoader> { + + private static final Type TSK_DOMAIN = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DOMAIN); + private static final Type TSK_URL = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_URL); + + @Override + public List load(DomainSearchArtifactsRequest artifactsRequest) throws TskCoreException { + final SleuthkitCase caseDb = artifactsRequest.getSleuthkitCase(); + final String normalizedDomain = artifactsRequest.getDomain().toLowerCase(); + final List artifacts = caseDb.getBlackboardArtifacts(artifactsRequest.getArtifactType()); + final List matchingDomainArtifacts = new ArrayList<>(); + + for (BlackboardArtifact artifact : artifacts) { + final BlackboardAttribute tskDomain = artifact.getAttribute(TSK_DOMAIN); + final BlackboardAttribute tskUrl = artifact.getAttribute(TSK_URL); + + if (tskDomain != null && tskDomain.getValueString().toLowerCase().equals(normalizedDomain)) { + matchingDomainArtifacts.add(artifact); + } else if (tskUrl != null && tskUrl.getValueString().toLowerCase().contains(normalizedDomain)) { + matchingDomainArtifacts.add(artifact); + } + } + + return matchingDomainArtifacts; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchArtifactsRequest.java b/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchArtifactsRequest.java new file mode 100755 index 0000000000..13f607eb6c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchArtifactsRequest.java @@ -0,0 +1,73 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 Basis Technology Corp. + * Contact: 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.discovery.search; + +import java.util.Objects; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; + +/** + * Requests artifacts of a specific type and domain from a given Case. + */ +public class DomainSearchArtifactsRequest { + + private final SleuthkitCase sleuthkitCase; + private final String domain; + private final ARTIFACT_TYPE artifactType; + + public DomainSearchArtifactsRequest(SleuthkitCase sleuthkitCase, + String domain, ARTIFACT_TYPE artifactType) { + this.sleuthkitCase = sleuthkitCase; + this.domain = domain; + this.artifactType = artifactType; + } + + public SleuthkitCase getSleuthkitCase() { + return sleuthkitCase; + } + + public String getDomain() { + return domain; + } + + public ARTIFACT_TYPE getArtifactType() { + return artifactType; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof DomainSearchArtifactsRequest)) { + return false; + } + + DomainSearchArtifactsRequest otherRequest = (DomainSearchArtifactsRequest) other; + return this.sleuthkitCase == otherRequest.getSleuthkitCase() + && this.domain.equals(otherRequest.getDomain()) + && this.artifactType == otherRequest.getArtifactType(); + } + + @Override + public int hashCode() { + return 79 * 5 + Objects.hash(this.domain, this.artifactType); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchThumbnailLoader.java b/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchThumbnailLoader.java new file mode 100755 index 0000000000..f2a447c73c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchThumbnailLoader.java @@ -0,0 +1,143 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 Basis Technology Corp. + * Contact: 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.discovery.search; + +import java.awt.Image; +import java.util.List; +import java.util.ArrayList; +import java.util.Collections; +import org.sleuthkit.autopsy.coreutils.ImageUtils; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.AbstractFile; +import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_CACHE; +import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Loads a thumbnail for the given request. Thumbnail candidates are JPEG files + * that are either TSK_WEB_DOWNLOAD or TSK_WEB_CACHE artifacts. JPEG files are + * sorted by most recent if sourced from TSK_WEB_DOWNLOADs. JPEG files are + * sorted by size if sourced from TSK_WEB_CACHE artifacts. Artifacts are first + * loaded from the DomainSearchArtifactsCache and then further analyzed. + */ +public class DomainSearchThumbnailLoader { + + private static final String JPG_EXTENSION = "jpg"; + private static final String JPG_MIME_TYPE = "image/jpeg"; + private final DomainSearchArtifactsCache artifactsCache; + + public DomainSearchThumbnailLoader() { + this(new DomainSearchArtifactsCache()); + } + + DomainSearchThumbnailLoader(DomainSearchArtifactsCache artifactsCache) { + this.artifactsCache = artifactsCache; + } + + public Image load(DomainSearchThumbnailRequest thumbnailRequest) throws TskCoreException, DiscoveryException { + + final SleuthkitCase caseDb = thumbnailRequest.getSleuthkitCase(); + + final DomainSearchArtifactsRequest webDownloadsRequest = new DomainSearchArtifactsRequest( + caseDb, thumbnailRequest.getDomain(), TSK_WEB_DOWNLOAD); + + final List webDownloads = artifactsCache.get(webDownloadsRequest); + final List webDownloadPictures = getJpegsFromWebDownload(caseDb, webDownloads); + Collections.sort(webDownloadPictures, (file1, file2) -> Long.compare(file1.getCrtime(), file2.getCrtime())); + + for (int i = webDownloadPictures.size() - 1; i >= 0; i--) { + // Get the most recent image, according to creation time. + final AbstractFile mostRecent = webDownloadPictures.get(i); + + final Image candidateThumbnail = ImageUtils.getThumbnail(mostRecent, thumbnailRequest.getIconSize()); + if (candidateThumbnail != ImageUtils.getDefaultThumbnail()) { + return candidateThumbnail; + } + } + + final DomainSearchArtifactsRequest webCacheRequest = new DomainSearchArtifactsRequest( + caseDb, thumbnailRequest.getDomain(), TSK_WEB_CACHE); + + final List webCacheArtifacts = artifactsCache.get(webCacheRequest); + final List webCachePictures = getJpegsFromWebCache(caseDb, webCacheArtifacts); + Collections.sort(webCachePictures, (file1, file2) -> Long.compare(file1.getSize(), file2.getSize())); + + for (int i = webCachePictures.size() - 1; i >= 0; i--) { + // Get the largest image, according to file size. + final AbstractFile largest = webCachePictures.get(i); + + final Image candidateThumbnail = ImageUtils.getThumbnail(largest, thumbnailRequest.getIconSize()); + if (candidateThumbnail != ImageUtils.getDefaultThumbnail()) { + return candidateThumbnail; + } + } + + return null; + } + + /** + * Finds all JPEG source files from TSK_WEB_DOWNLOAD instances. + */ + private List getJpegsFromWebDownload(SleuthkitCase caseDb, List artifacts) throws TskCoreException { + final List jpegs = new ArrayList<>(); + + for (BlackboardArtifact artifact : artifacts) { + final Content sourceContent = caseDb.getContentById(artifact.getObjectID()); + addIfJpeg(jpegs, sourceContent); + } + + return jpegs; + } + + /** + * Finds all JPEG source files from TSK_WEB_CACHE instances. + */ + private List getJpegsFromWebCache(SleuthkitCase caseDb, List artifacts) throws TskCoreException { + final BlackboardAttribute.Type TSK_PATH_ID = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PATH_ID); + + final List jpegs = new ArrayList<>(); + for (BlackboardArtifact artifact : artifacts) { + final BlackboardAttribute tskPathId = artifact.getAttribute(TSK_PATH_ID); + if (tskPathId != null) { + final Content sourceContent = caseDb.getContentById(tskPathId.getValueLong()); + addIfJpeg(jpegs, sourceContent); + } + } + + return jpegs; + } + + /** + * Checks if the candidate source content is indeed a JPEG file. + */ + private void addIfJpeg(List files, Content sourceContent) { + if ((sourceContent instanceof AbstractFile) && !(sourceContent instanceof DataSource)) { + final AbstractFile file = (AbstractFile) sourceContent; + if (JPG_EXTENSION.equals(file.getNameExtension()) + || JPG_MIME_TYPE.equals(file.getMIMEType())) { + files.add(file); + } + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchThumbnailRequest.java b/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchThumbnailRequest.java new file mode 100755 index 0000000000..b3806ebeb1 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchThumbnailRequest.java @@ -0,0 +1,51 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 Basis Technology Corp. + * Contact: 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.discovery.search; + +import org.sleuthkit.datamodel.SleuthkitCase; + +/** + * Requests a thumbnail to be generated for a given Case, domain and + * size. IconSize should be a value obtained from ImageUtils. + */ +public class DomainSearchThumbnailRequest { + + private final SleuthkitCase sleuthkitCase; + private final String domain; + private final int iconSize; + + public DomainSearchThumbnailRequest(SleuthkitCase sleuthkitCase, + String domain, int iconSize) { + this.sleuthkitCase = sleuthkitCase; + this.domain = domain; + this.iconSize = iconSize; + } + + public SleuthkitCase getSleuthkitCase() { + return sleuthkitCase; + } + + public String getDomain() { + return domain; + } + + public int getIconSize() { + return iconSize; + } +} From 39cb3424cffe2581346762bf24f85d65433690ad Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dsmyda" Date: Mon, 14 Sep 2020 16:45:37 -0400 Subject: [PATCH 2/2] Fixed tests and added cache for thumbnails --- .../discovery/search/DomainSearch.java | 14 +++-- .../search/DomainSearchArtifactsCache.java | 2 +- .../search/DomainSearchThumbnailCache.java | 53 +++++++++++++++++++ .../search/DomainSearchThumbnailLoader.java | 4 +- .../search/DomainSearchThumbnailRequest.java | 22 ++++++++ .../discovery/search/DomainSearchTest.java | 24 ++++----- 6 files changed, 97 insertions(+), 22 deletions(-) create mode 100755 Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchThumbnailCache.java diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearch.java b/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearch.java index 8faf1822a7..be1b04ed1b 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearch.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearch.java @@ -27,7 +27,6 @@ import java.util.Map; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; import org.sleuthkit.autopsy.discovery.search.DiscoveryKeyUtils.GroupKey; import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.TskCoreException; /** * Main class to perform the domain search. @@ -35,15 +34,15 @@ import org.sleuthkit.datamodel.TskCoreException; public class DomainSearch { private final DomainSearchCache searchCache; - private final DomainSearchThumbnailLoader thumbnailLoader; + private final DomainSearchThumbnailCache thumbnailCache; public DomainSearch() { - this(new DomainSearchCache(), new DomainSearchThumbnailLoader()); + this(new DomainSearchCache(), new DomainSearchThumbnailCache()); } - DomainSearch(DomainSearchCache cache, DomainSearchThumbnailLoader thumbnailLoader) { + DomainSearch(DomainSearchCache cache, DomainSearchThumbnailCache thumbnailCache) { this.searchCache = cache; - this.thumbnailLoader = thumbnailLoader; + this.thumbnailCache = thumbnailCache; } /** @@ -135,11 +134,10 @@ public class DomainSearch { * @param thumbnailRequest Thumbnail request for domain * @return An Image instance or null if no thumbnail is available. * - * @throws TskCoreException If there is an error reaching the case databases * @throws DiscoveryException If there is an error with Discovery related * processing */ - public Image getThumbnail(DomainSearchThumbnailRequest thumbnailRequest) throws TskCoreException, DiscoveryException { - return thumbnailLoader.load(thumbnailRequest); + public Image getThumbnail(DomainSearchThumbnailRequest thumbnailRequest) throws DiscoveryException { + return thumbnailCache.get(thumbnailRequest); } } diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchArtifactsCache.java b/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchArtifactsCache.java index e1a11690d7..7efc42e219 100755 --- a/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchArtifactsCache.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchArtifactsCache.java @@ -25,7 +25,7 @@ import java.util.concurrent.ExecutionException; import org.sleuthkit.datamodel.BlackboardArtifact; /** - * Caches artifact hits for a domain request. + * Caches artifact requests. */ public class DomainSearchArtifactsCache { diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchThumbnailCache.java b/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchThumbnailCache.java new file mode 100755 index 0000000000..2a02cce9af --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchThumbnailCache.java @@ -0,0 +1,53 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 Basis Technology Corp. + * Contact: 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.discovery.search; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.LoadingCache; +import java.awt.Image; +import java.util.concurrent.ExecutionException; + +/** + * Caches thumbnail requests. + */ +public class DomainSearchThumbnailCache { + + private static final int MAXIMUM_CACHE_SIZE = 500; + private static final LoadingCache cache + = CacheBuilder.newBuilder() + .maximumSize(MAXIMUM_CACHE_SIZE) + .build(new DomainSearchThumbnailLoader()); + + /** + * Get a thumbnail for the requested domain. If the request is new, the + * thumbnail will be automatically loaded. + * + * @param request Requested domain to thumbnail + * @return The thumbnail Image instance, or null if no thumbnail is available + * + * @throws DiscoveryException If any error occurs during thumbnail generation. + */ + public Image get(DomainSearchThumbnailRequest request) throws DiscoveryException { + try { + return cache.get(request); + } catch (ExecutionException ex) { + throw new DiscoveryException("Error fetching artifacts from cache", ex.getCause()); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchThumbnailLoader.java b/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchThumbnailLoader.java index f2a447c73c..0a6a34bf8c 100755 --- a/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchThumbnailLoader.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchThumbnailLoader.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.discovery.search; +import com.google.common.cache.CacheLoader; import java.awt.Image; import java.util.List; import java.util.ArrayList; @@ -41,7 +42,7 @@ import org.sleuthkit.datamodel.TskCoreException; * sorted by size if sourced from TSK_WEB_CACHE artifacts. Artifacts are first * loaded from the DomainSearchArtifactsCache and then further analyzed. */ -public class DomainSearchThumbnailLoader { +public class DomainSearchThumbnailLoader extends CacheLoader { private static final String JPG_EXTENSION = "jpg"; private static final String JPG_MIME_TYPE = "image/jpeg"; @@ -55,6 +56,7 @@ public class DomainSearchThumbnailLoader { this.artifactsCache = artifactsCache; } + @Override public Image load(DomainSearchThumbnailRequest thumbnailRequest) throws TskCoreException, DiscoveryException { final SleuthkitCase caseDb = thumbnailRequest.getSleuthkitCase(); diff --git a/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchThumbnailRequest.java b/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchThumbnailRequest.java index b3806ebeb1..ca2bbcdc5a 100755 --- a/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchThumbnailRequest.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/search/DomainSearchThumbnailRequest.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.discovery.search; +import java.util.Objects; import org.sleuthkit.datamodel.SleuthkitCase; /** @@ -48,4 +49,25 @@ public class DomainSearchThumbnailRequest { public int getIconSize() { return iconSize; } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof DomainSearchThumbnailRequest)) { + return false; + } + + DomainSearchThumbnailRequest otherRequest = (DomainSearchThumbnailRequest) other; + return this.sleuthkitCase == otherRequest.getSleuthkitCase() + && this.domain.equals(otherRequest.getDomain()) + && this.iconSize == otherRequest.getIconSize(); + } + + @Override + public int hashCode() { + return 79 * 5 + Objects.hash(this.domain, this.iconSize); + } } diff --git a/Core/test/unit/src/org/sleuthkit/autopsy/discovery/search/DomainSearchTest.java b/Core/test/unit/src/org/sleuthkit/autopsy/discovery/search/DomainSearchTest.java index 9cc4f5c49c..76fa103104 100755 --- a/Core/test/unit/src/org/sleuthkit/autopsy/discovery/search/DomainSearchTest.java +++ b/Core/test/unit/src/org/sleuthkit/autopsy/discovery/search/DomainSearchTest.java @@ -48,7 +48,7 @@ public class DomainSearchTest { }; when(cache.get(null, new ArrayList<>(), null, null, null, null, null)).thenReturn(dummyData); - DomainSearch domainSearch = new DomainSearch(cache); + DomainSearch domainSearch = new DomainSearch(cache, null); Map sizes = domainSearch.getGroupSizes(null, new ArrayList<>(), null, null, null, null, null); assertEquals(4, sizes.get(groupOne).longValue()); @@ -83,7 +83,7 @@ public class DomainSearchTest { when(cache.get(null, new ArrayList<>(), null, null, null, null, null)).thenReturn(dummyData); - DomainSearch domainSearch = new DomainSearch(cache); + DomainSearch domainSearch = new DomainSearch(cache, null); Map sizes = domainSearch.getGroupSizes(null, new ArrayList<>(), null, null, null, null, null); assertEquals(4, sizes.get(groupOne).longValue()); @@ -97,7 +97,7 @@ public class DomainSearchTest { when(cache.get(null, new ArrayList<>(), null, null, null, null, null)).thenReturn(new HashMap<>()); - DomainSearch domainSearch = new DomainSearch(cache); + DomainSearch domainSearch = new DomainSearch(cache, null); Map sizes = domainSearch.getGroupSizes(null, new ArrayList<>(), null, null, null, null, null); assertEquals(0, sizes.size()); @@ -122,7 +122,7 @@ public class DomainSearchTest { when(cache.get(null, new ArrayList<>(), null, null, null, null, null)).thenReturn(dummyData); - DomainSearch domainSearch = new DomainSearch(cache); + DomainSearch domainSearch = new DomainSearch(cache, null); List firstPage = domainSearch.getDomainsInGroup(null, new ArrayList<>(), null, null, null, groupOne, 0, 3, null, null); assertEquals(3, firstPage.size()); @@ -150,7 +150,7 @@ public class DomainSearchTest { when(cache.get(null, new ArrayList<>(), null, null, null, null, null)).thenReturn(dummyData); - DomainSearch domainSearch = new DomainSearch(cache); + DomainSearch domainSearch = new DomainSearch(cache, null); List firstPage = domainSearch.getDomainsInGroup(null, new ArrayList<>(), null, null, null, groupOne, 0, 100, null, null); assertEquals(4, firstPage.size()); @@ -178,7 +178,7 @@ public class DomainSearchTest { when(cache.get(null, new ArrayList<>(), null, null, null, null, null)).thenReturn(dummyData); - DomainSearch domainSearch = new DomainSearch(cache); + DomainSearch domainSearch = new DomainSearch(cache, null); List firstPage = domainSearch.getDomainsInGroup(null, new ArrayList<>(), null, null, null, groupOne, 0, 2, null, null); assertEquals(2, firstPage.size()); @@ -206,7 +206,7 @@ public class DomainSearchTest { when(cache.get(null, new ArrayList<>(), null, null, null, null, null)).thenReturn(dummyData); - DomainSearch domainSearch = new DomainSearch(cache); + DomainSearch domainSearch = new DomainSearch(cache, null); List firstPage = domainSearch.getDomainsInGroup(null, new ArrayList<>(), null, null, null, groupOne, 3, 1, null, null); assertEquals(1, firstPage.size()); @@ -232,7 +232,7 @@ public class DomainSearchTest { when(cache.get(null, new ArrayList<>(), null, null, null, null, null)).thenReturn(dummyData); - DomainSearch domainSearch = new DomainSearch(cache); + DomainSearch domainSearch = new DomainSearch(cache, null); List firstPage = domainSearch.getDomainsInGroup(null, new ArrayList<>(), null, null, null, groupOne, 20, 5, null, null); assertEquals(0, firstPage.size()); @@ -257,7 +257,7 @@ public class DomainSearchTest { when(cache.get(null, new ArrayList<>(), null, null, null, null, null)).thenReturn(dummyData); - DomainSearch domainSearch = new DomainSearch(cache); + DomainSearch domainSearch = new DomainSearch(cache, null); List firstPage = domainSearch.getDomainsInGroup(null, new ArrayList<>(), null, null, null, groupOne, 0, 0, null, null); assertEquals(0, firstPage.size()); @@ -292,7 +292,7 @@ public class DomainSearchTest { when(cache.get(null, new ArrayList<>(), null, null, null, null, null)).thenReturn(dummyData); - DomainSearch domainSearch = new DomainSearch(cache); + DomainSearch domainSearch = new DomainSearch(cache, null); List firstPage = domainSearch.getDomainsInGroup(null, new ArrayList<>(), null, null, null, groupOne, 0, 3, null, null); assertEquals(3, firstPage.size()); @@ -327,7 +327,7 @@ public class DomainSearchTest { when(cache.get(null, new ArrayList<>(), null, null, null, null, null)).thenReturn(dummyData); - DomainSearch domainSearch = new DomainSearch(cache); + DomainSearch domainSearch = new DomainSearch(cache, null); List firstPage = domainSearch.getDomainsInGroup(null, new ArrayList<>(), null, null, null, groupTwo, 1, 2, null, null); assertEquals(2, firstPage.size()); @@ -359,7 +359,7 @@ public class DomainSearchTest { when(cache.get(null, new ArrayList<>(), null, null, null, null, null)).thenReturn(dummyData); - DomainSearch domainSearch = new DomainSearch(cache); + DomainSearch domainSearch = new DomainSearch(cache, null); int start = 0; int size = 2;