Merged in upstream

This commit is contained in:
U-BASIS\dsmyda 2020-09-16 14:41:30 -04:00
commit fa3c168922
19 changed files with 569 additions and 88 deletions

View File

@ -222,7 +222,7 @@ public class DiscoveryAttributes {
static class FrequencyAttribute extends AttributeType {
static final int BATCH_SIZE = 50; // Number of hashes to look up at one time
static final int DOMAIN_BATCH_SIZE = 500; // Number of domains to look up at one time
@Override
@ -255,13 +255,13 @@ public class DiscoveryAttributes {
private void processResultFilesForCR(List<Result> results,
CentralRepository centralRepoDb) throws DiscoveryException {
List<ResultFile> currentFiles = new ArrayList<>();
Set<String> hashesToLookUp = new HashSet<>();
Set<String> hashesToLookUp = new HashSet<>();
List<ResultDomain> domainsToQuery = new ArrayList<>();
for (Result result : results) {
if (result.getKnown() == TskData.FileKnown.KNOWN) {
result.setFrequency(SearchData.Frequency.KNOWN);
}
if (result.getType() != SearchData.Type.DOMAIN) {
ResultFile file = (ResultFile) result;
if (file.getFrequency() == SearchData.Frequency.UNKNOWN
@ -270,56 +270,41 @@ public class DiscoveryAttributes {
hashesToLookUp.add(file.getFirstInstance().getMd5Hash());
currentFiles.add(file);
}
if (hashesToLookUp.size() >= BATCH_SIZE) {
computeFrequency(hashesToLookUp, currentFiles, centralRepoDb);
hashesToLookUp.clear();
currentFiles.clear();
if (hashesToLookUp.size() >= BATCH_SIZE) {
computeFrequency(hashesToLookUp, currentFiles, centralRepoDb);
hashesToLookUp.clear();
currentFiles.clear();
}
} else {
ResultDomain domain = (ResultDomain) result;
try {
CorrelationAttributeInstance.Type domainAttributeType
= centralRepoDb.getCorrelationTypeById(CorrelationAttributeInstance.DOMAIN_TYPE_ID);
Long count = centralRepoDb.getCountArtifactInstancesByTypeValue(domainAttributeType, domain.getDomain());
domain.setFrequency(SearchData.Frequency.fromCount(count));
} catch (CentralRepoException ex) {
throw new DiscoveryException("Error encountered querying the central repository.", ex);
} catch (CorrelationAttributeNormalizationException ex) {
logger.log(Level.INFO, "Domain [%s] could not be normalized for central repository querying, skipping...", domain.getDomain());
}
}
if (hashesToLookUp.size() >= BATCH_SIZE) {
computeFrequency(hashesToLookUp, currentFiles, centralRepoDb);
ResultDomain domainInstance = (ResultDomain) result;
domainsToQuery.add(domainInstance);
if (domainsToQuery.size() == DOMAIN_BATCH_SIZE) {
queryDomainFrequency(domainsToQuery, centralRepoDb);
domainsToQuery.clear();
}
}
}
queryDomainFrequency(domainsToQuery, centralRepoDb);
computeFrequency(hashesToLookUp, currentFiles, centralRepoDb);
}
}
private static void queryDomainFrequency(List<ResultDomain> domainsToQuery, CentralRepository centralRepository) throws DiscoveryException {
if (domainsToQuery.isEmpty()) {
return;
}
try {
final Map<String, List<ResultDomain>> resultDomainTable = new HashMap<>();
final StringJoiner joiner = new StringJoiner(", ");
final CorrelationAttributeInstance.Type attributeType = centralRepository.getCorrelationTypeById(CorrelationAttributeInstance.DOMAIN_TYPE_ID);
for(ResultDomain domainInstance : domainsToQuery) {
for (ResultDomain domainInstance : domainsToQuery) {
try {
final String domainValue = domainInstance.getDomain();
final String normalizedDomain = CorrelationAttributeNormalizer.normalize(attributeType, domainValue);
@ -333,10 +318,10 @@ public class DiscoveryAttributes {
}
final String tableName = CentralRepoDbUtil.correlationTypeToInstanceTableName(attributeType);
final String domainFrequencyQuery = " value AS domain_name, COUNT(*) AS frequency " +
"FROM " + tableName + " " +
"WHERE value IN (" + joiner + ") " +
"GROUP BY value";
final String domainFrequencyQuery = " value AS domain_name, COUNT(*) AS frequency "
+ "FROM " + tableName + " "
+ "WHERE value IN (" + joiner + ") "
+ "GROUP BY value";
final DomainFrequencyCallback frequencyCallback = new DomainFrequencyCallback(resultDomainTable);
centralRepository.processSelectClause(domainFrequencyQuery, frequencyCallback);
@ -348,15 +333,15 @@ public class DiscoveryAttributes {
throw new DiscoveryException("Fatal exception encountered querying the CR.", ex);
}
}
private static class DomainFrequencyCallback implements InstanceTableCallback {
private final Map<String, List<ResultDomain>> domainLookup;
private SQLException sqlCause;
private DomainFrequencyCallback(Map<String, List<ResultDomain>> domainLookup) {
this.domainLookup = domainLookup;
}
}
@Override
public void process(ResultSet resultSet) {
@ -364,9 +349,9 @@ public class DiscoveryAttributes {
while (resultSet.next()) {
String domain = resultSet.getString("domain_name");
Long frequency = resultSet.getLong("frequency");
List<ResultDomain> domainInstances = domainLookup.get(domain);
for(ResultDomain domainInstance : domainInstances) {
for (ResultDomain domainInstance : domainInstances) {
domainInstance.setFrequency(SearchData.Frequency.fromCount(frequency));
}
}
@ -374,7 +359,7 @@ public class DiscoveryAttributes {
this.sqlCause = ex;
}
}
SQLException getCause() {
return this.sqlCause;
}
@ -742,7 +727,7 @@ public class DiscoveryAttributes {
FILE_TAG(new FileTagAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_tag_displayName()),
OBJECT_DETECTED(new ObjectDetectedAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_object_displayName()),
MOST_RECENT_DATE(new MostRecentActivityDateAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_mostRecentDate_displayName()),
FIRST_DATE(new MostRecentActivityDateAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_firstDate_displayName()),
FIRST_DATE(new FirstActivityDateAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_firstDate_displayName()),
NUMBER_OF_VISITS(new NumberOfVisitsAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_numberOfVisits_displayName()),
NO_GROUPING(new NoGroupingAttribute(), Bundle.DiscoveryAttributes_GroupingAttributeType_none_displayName());
@ -781,7 +766,7 @@ public class DiscoveryAttributes {
return Arrays.asList(FREQUENCY, MOST_RECENT_DATE, FIRST_DATE, NUMBER_OF_VISITS);
}
}
/**
* Computes the CR frequency of all the given hashes and updates the list of
* files.

View File

@ -22,6 +22,7 @@ import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
@ -166,7 +167,8 @@ public class DiscoveryKeyUtils {
/**
* Get the fileSorting
* @return
*
* @return
*/
ResultsSorter.SortingMethod getFileSortingMethod() {
return fileSortingMethod;
@ -938,6 +940,9 @@ public class DiscoveryKeyUtils {
}
}
/**
* Key representing a date of most recent activity.
*/
static class MostRecentActivityDateGroupKey extends GroupKey {
private final Long epochDate;
@ -948,7 +953,7 @@ public class DiscoveryKeyUtils {
MostRecentActivityDateGroupKey(Result result) {
if (result instanceof ResultDomain) {
epochDate = ((ResultDomain) result).getActivityEnd();
dateNameString = new SimpleDateFormat("yyyy/MM/dd").format(new Date(TimeUnit.SECONDS.toMillis(epochDate)));
dateNameString = new SimpleDateFormat("yyyy/MM/dd", Locale.getDefault()).format(new Date(TimeUnit.SECONDS.toMillis(epochDate)));
} else {
epochDate = Long.MAX_VALUE;
dateNameString = Bundle.DiscoveryKeyUtils_MostRecentActivityDateGroupKey_noDate();
@ -1020,6 +1025,9 @@ public class DiscoveryKeyUtils {
}
}
/**
* Key representing a date of first activity.
*/
static class FirstActivityDateGroupKey extends GroupKey {
private final Long epochDate;
@ -1030,7 +1038,7 @@ public class DiscoveryKeyUtils {
FirstActivityDateGroupKey(Result result) {
if (result instanceof ResultDomain) {
epochDate = ((ResultDomain) result).getActivityStart();
dateNameString = new SimpleDateFormat("yyyy/MM/dd").format(new Date(TimeUnit.SECONDS.toMillis(epochDate)));
dateNameString = new SimpleDateFormat("yyyy/MM/dd", Locale.getDefault()).format(new Date(TimeUnit.SECONDS.toMillis(epochDate)));
} else {
epochDate = Long.MAX_VALUE;
dateNameString = Bundle.DiscoveryKeyUtils_FirstActivityDateGroupKey_noDate();

View File

@ -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;
@ -33,13 +34,15 @@ import org.sleuthkit.datamodel.SleuthkitCase;
public class DomainSearch {
private final DomainSearchCache searchCache;
private final DomainSearchThumbnailCache thumbnailCache;
public DomainSearch() {
this(new DomainSearchCache());
this(new DomainSearchCache(), new DomainSearchThumbnailCache());
}
DomainSearch(DomainSearchCache cache) {
DomainSearch(DomainSearchCache cache, DomainSearchThumbnailCache thumbnailCache) {
this.searchCache = cache;
this.thumbnailCache = thumbnailCache;
}
/**
@ -123,4 +126,18 @@ 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 DiscoveryException If there is an error with Discovery related
* processing
*/
public Image getThumbnail(DomainSearchThumbnailRequest thumbnailRequest) throws DiscoveryException {
return thumbnailCache.get(thumbnailRequest);
}
}

View File

@ -0,0 +1,55 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 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.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 requests.
*/
public class DomainSearchArtifactsCache {
private static final int MAXIMUM_CACHE_SIZE = 500;
private static final LoadingCache<DomainSearchArtifactsRequest, List<BlackboardArtifact>> 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<BlackboardArtifact> get(DomainSearchArtifactsRequest request) throws DiscoveryException {
try {
return cache.get(request);
} catch (ExecutionException ex) {
throw new DiscoveryException("Error fetching artifacts from cache", ex);
}
}
}

View File

@ -0,0 +1,61 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 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.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<DomainSearchArtifactsRequest, List<BlackboardArtifact>> {
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<BlackboardArtifact> load(DomainSearchArtifactsRequest artifactsRequest) throws TskCoreException {
final SleuthkitCase caseDb = artifactsRequest.getSleuthkitCase();
final String normalizedDomain = artifactsRequest.getDomain().toLowerCase();
final List<BlackboardArtifact> artifacts = caseDb.getBlackboardArtifacts(artifactsRequest.getArtifactType());
final List<BlackboardArtifact> 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().equalsIgnoreCase(normalizedDomain)) {
matchingDomainArtifacts.add(artifact);
} else if (tskUrl != null && tskUrl.getValueString().toLowerCase().contains(normalizedDomain)) {
matchingDomainArtifacts.add(artifact);
}
}
return matchingDomainArtifacts;
}
}

View File

@ -0,0 +1,73 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 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.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);
}
}

View File

@ -36,10 +36,10 @@ import org.sleuthkit.datamodel.SleuthkitCase;
class DomainSearchCache {
private static final int MAXIMUM_CACHE_SIZE = 10;
private static final LoadingCache<SearchKey, Map<GroupKey, List<Result>>> cache =
CacheBuilder.newBuilder()
.maximumSize(MAXIMUM_CACHE_SIZE)
.build(new DomainSearchCacheLoader());
private static final LoadingCache<SearchKey, Map<GroupKey, List<Result>>> cache
= CacheBuilder.newBuilder()
.maximumSize(MAXIMUM_CACHE_SIZE)
.build(new DomainSearchCacheLoader());
/**
* Get domain search results matching the given parameters. If no results
@ -57,7 +57,7 @@ class DomainSearchCache {
groupSortingType, domainSortingMethod, caseDb, centralRepoDb);
return cache.get(searchKey);
} catch (Throwable ex) {
} catch (ExecutionException ex) {
throw new DiscoveryException("Error fetching results from cache", ex.getCause());
}
}

View File

@ -0,0 +1,53 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 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.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<DomainSearchThumbnailRequest, Image> 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);
}
}
}

View File

@ -0,0 +1,149 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 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.discovery.search;
import com.google.common.cache.CacheLoader;
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;
import org.openide.util.ImageUtilities;
/**
* 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 extends CacheLoader<DomainSearchThumbnailRequest, Image> {
private static final String UNSUPPORTED_IMAGE = "org/sleuthkit/autopsy/images/image-extraction-not-supported.png";
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;
}
@Override
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<BlackboardArtifact> webDownloads = artifactsCache.get(webDownloadsRequest);
final List<AbstractFile> 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<BlackboardArtifact> webCacheArtifacts = artifactsCache.get(webCacheRequest);
final List<AbstractFile> 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 ImageUtilities.loadImage(UNSUPPORTED_IMAGE, false);
}
/**
* Finds all JPEG source files from TSK_WEB_DOWNLOAD instances.
*/
private List<AbstractFile> getJpegsFromWebDownload(SleuthkitCase caseDb, List<BlackboardArtifact> artifacts) throws TskCoreException {
final List<AbstractFile> 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<AbstractFile> getJpegsFromWebCache(SleuthkitCase caseDb, List<BlackboardArtifact> artifacts) throws TskCoreException {
final BlackboardAttribute.Type TSK_PATH_ID = new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PATH_ID);
final List<AbstractFile> 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<AbstractFile> 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);
}
}
}
}

View File

@ -0,0 +1,73 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 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.discovery.search;
import java.util.Objects;
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;
}
@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);
}
}

View File

@ -25,6 +25,7 @@ import javax.swing.DefaultListModel;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JList;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.discovery.search.SearchData;
import org.sleuthkit.autopsy.discovery.search.SearchFiltering.ArtifactTypeFilter;
import org.sleuthkit.datamodel.BlackboardArtifact;
@ -133,10 +134,11 @@ class ArtifactTypeFilterPanel extends AbstractDiscoveryFilterPanel {
return null;
}
@NbBundle.Messages({"ArtifactTypeFilterPanel.selectionNeeded.text=At least one Result type must be selected."})
@Override
String checkForError() {
if (artifactTypeCheckbox.isSelected() && artifactList.getSelectedValuesList().isEmpty()) {
return "At least one Result type must be selected.";
return Bundle.ArtifactTypeFilterPanel_selectionNeeded_text();
}
return "";
}

View File

@ -1,3 +1,4 @@
ArtifactTypeFilterPanel.selectionNeeded.text=At least one Result type must be selected.
CTL_OpenDiscoveryAction=Discovery
# {0} - dataSourceName
DataSourceModuleWrapper.exifModule.text=Exif Parser module was not run on data source: {0}\n
@ -7,6 +8,8 @@ DataSourceModuleWrapper.fileTypeModule.text=File Type Identification module was
DataSourceModuleWrapper.hashModule.text=Hash Lookup module was not run on data source: {0}\n
# {0} - timeZone
DateFilterPanel.dateRange.text=Date Range ({0}):
DateFilterPanel.invalidRange.text=Range or Only Last must be selected
DateFilterPanel.startOrEndNeeded.text=A start or end date must be specified to use the range filter
DiscoveryDialog.name.text=Discovery
DiscoveryTopComponent.cancelButton.text=Cancel Search
DiscoveryTopComponent.name=\ Discovery

View File

@ -19,8 +19,8 @@
package org.sleuthkit.autopsy.discovery.ui;
import java.awt.event.ActionListener;
import java.time.Duration;
import java.time.LocalDate;
import java.time.Period;
import java.time.ZoneId;
import org.sleuthkit.autopsy.discovery.search.AbstractFilter;
import javax.swing.JCheckBox;
@ -278,13 +278,15 @@ class DateFilterPanel extends AbstractDiscoveryFilterPanel {
}
}
@NbBundle.Messages({"DateFilterPanel.invalidRange.text=Range or Only Last must be selected",
"DateFilterPanel.startOrEndNeeded.text=A start or end date must be specified to use the range filter"})
@Override
String checkForError() {
if (dateFilterCheckBox.isSelected()) {
if (!(rangeRadioButton.isSelected() || mostRecentRadioButton.isSelected())) {
return "Range or Only Last must be selected";
return Bundle.DateFilterPanel_invalidRange_text();
} else if (rangeRadioButton.isSelected() && !(startCheckBox.isSelected() || endCheckBox.isSelected())) {
return "A start or end date must be specified to use the range filter";
return Bundle.DateFilterPanel_startOrEndNeeded_text();
}
}
return "";
@ -292,13 +294,12 @@ class DateFilterPanel extends AbstractDiscoveryFilterPanel {
@Override
AbstractFilter getFilter() {
if (dateFilterCheckBox.isSelected()) {
LocalDate startDate = LocalDate.MIN;
LocalDate endDate = LocalDate.MAX;
ZoneId zone = Utils.getUserPreferredZoneId();
if (rangeRadioButton.isSelected() && (startCheckBox.isSelected() || endCheckBox.isSelected())) {
if (startCheckBox.isSelected() && startDatePicker.getDate().equals(startDate)) {
if (startCheckBox.isSelected() && startDatePicker.getDate() != null) {
startDate = startDatePicker.getDate();
}
if (endCheckBox.isSelected() && endDatePicker.getDate() != null) {
@ -306,7 +307,7 @@ class DateFilterPanel extends AbstractDiscoveryFilterPanel {
}
} else if (dateFilterCheckBox.isSelected() && mostRecentRadioButton.isSelected()) {
endDate = LocalDate.now();
startDate = LocalDate.now().minus(Duration.ofDays((long) daysSpinner.getValue()));
startDate = LocalDate.now().minus(Period.ofDays((Integer) daysSpinner.getValue()));
}
return new SearchFiltering.ArtifactDateRangeFilter(startDate.atStartOfDay(zone).toEpochSecond(), endDate.atStartOfDay(zone).toEpochSecond() + SECS_PER_DAY);//to insure end date is inclusive
}

View File

@ -24,6 +24,7 @@ import java.awt.Point;
import java.awt.event.MouseEvent;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JList;
@ -38,8 +39,7 @@ class DomainSummaryPanel extends javax.swing.JPanel implements ListCellRenderer<
private static final long serialVersionUID = 1L;
private static final Color SELECTION_COLOR = new Color(0, 120, 215);
private static final int MAX_NAME_STRING = 90;
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("MMM dd yyyy");
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("MMM dd yyyy", Locale.getDefault());
/**
* Creates new form DomainPanel.

View File

@ -11,6 +11,7 @@
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,44,0,0,1,-112"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
@ -38,6 +39,8 @@
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;DomainWrapper&gt;"/>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
</Component>
</SubComponents>

View File

@ -21,8 +21,7 @@ package org.sleuthkit.autopsy.discovery.ui;
import javax.swing.DefaultListModel;
/**
*
* @author wschaefer
* A JPanel to display domain summaries.
*/
public class DomainSummaryViewer extends javax.swing.JPanel {
@ -57,7 +56,7 @@ public class DomainSummaryViewer extends javax.swing.JPanel {
private void initComponents() {
domainScrollPane = new javax.swing.JScrollPane();
domainList = new javax.swing.JList<>();
javax.swing.JList<DomainWrapper> domainList = new javax.swing.JList<>();
setLayout(new java.awt.BorderLayout());
@ -70,7 +69,6 @@ public class DomainSummaryViewer extends javax.swing.JPanel {
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JList<DomainWrapper> domainList;
private javax.swing.JScrollPane domainScrollPane;
// End of variables declaration//GEN-END:variables

View File

@ -22,12 +22,12 @@ import java.awt.Image;
import org.sleuthkit.autopsy.discovery.search.ResultDomain;
/**
*
* @author wschaefer
* Class to wrap all the information necessary for a domain summary to be
* displayed.
*/
public class DomainWrapper {
private ResultDomain domain;
private final ResultDomain domain;
private Image thumbnail = null;
/**
@ -43,10 +43,10 @@ public class DomainWrapper {
/**
* Set the thumbnail which exists.
*
* @param thumbnail The Image object which will be used to represent this
* @param thumbnail The image object which will be used to represent this
* domain object.
*/
void setThumnail(Image thumbnail) {
void setThumbnail(Image thumbnail) {
this.thumbnail = thumbnail;
}
@ -64,7 +64,7 @@ public class DomainWrapper {
/**
* Get the image to be used for the domain.
*
* @return The Image which represents the domain.
* @return The image which represents the domain.
*/
Image getThumbnail() {
return thumbnail;

View File

@ -120,7 +120,7 @@ final class ResultsPanel extends javax.swing.JPanel {
}
}
});
//JIRA-TODO
//JIRA-TODO 6307 Add listener for domainSummaryViewer when 6782, 6773, and the other details area related stories are done
}
/**
@ -812,7 +812,7 @@ final class ResultsPanel extends javax.swing.JPanel {
@Override
protected Void doInBackground() throws Exception {
domainWrapper.setThumnail(null);
domainWrapper.setThumbnail(null);
return null;
}
@ -821,13 +821,13 @@ final class ResultsPanel extends javax.swing.JPanel {
try {
get();
} catch (InterruptedException | ExecutionException ex) {
domainWrapper.setThumnail(null);
domainWrapper.setThumbnail(null);
logger.log(Level.WARNING, "Document Worker Exception", ex);
} catch (CancellationException ignored) {
domainWrapper.setThumnail(null);
domainWrapper.setThumbnail(null);
//we want to do nothing in response to this since we allow it to be cancelled
}
documentPreviewViewer.repaint();
domainSummaryViewer.repaint();
}
}

View File

@ -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<GroupKey, Integer> 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<GroupKey, Integer> 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<GroupKey, Integer> 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<Result> 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<Result> 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<Result> 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<Result> 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<Result> 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<Result> 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<Result> 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<Result> 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;