From dcc41893e4ba04160daeadf0633d478d5a75a03e Mon Sep 17 00:00:00 2001 From: apriestman Date: Fri, 26 Mar 2021 09:43:27 -0400 Subject: [PATCH 1/9] Handle errors from unknown CR types better --- .../CorrelationCaseChildNodeFactory.java | 18 +++++++++------- .../relationships/SummaryPanelWorker.java | 21 ++++++++++++++----- .../ContactArtifactViewer.java | 8 ++++--- 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/CorrelationCaseChildNodeFactory.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/CorrelationCaseChildNodeFactory.java index 588e474303..d34c607796 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/CorrelationCaseChildNodeFactory.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/CorrelationCaseChildNodeFactory.java @@ -108,14 +108,18 @@ final class CorrelationCaseChildNodeFactory extends ChildFactory Date: Fri, 26 Mar 2021 11:39:39 -0400 Subject: [PATCH 2/9] Use optional to indicate whether CR account type was found. --- .../datamodel/CentralRepository.java | 5 ++-- .../datamodel/CorrelationAttributeUtil.java | 9 ++++-- .../datamodel/PersonaAccount.java | 20 ++++++++++--- .../datamodel/RdbmsCentralRepo.java | 14 +++++---- .../CorrelationCaseChildNodeFactory.java | 29 +++++++++---------- .../relationships/SummaryPanelWorker.java | 14 +++------ .../ContactArtifactViewer.java | 7 +++-- 7 files changed, 56 insertions(+), 42 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java index 842c8e3f04..289dfa476f 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.centralrepository.datamodel; import java.sql.SQLException; import java.util.Collection; import java.util.List; +import java.util.Optional; import java.util.Set; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.autopsy.casemodule.Case; @@ -860,10 +861,10 @@ public interface CentralRepository { * Get account type by type name. * * @param accountTypeName account type name to look for - * @return CR account type + * @return CR account type (if found) * @throws CentralRepoException */ - CentralRepoAccountType getAccountTypeByName(String accountTypeName) throws CentralRepoException; + Optional getAccountTypeByName(String accountTypeName) throws CentralRepoException; /** * Gets all account types. diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeUtil.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeUtil.java index 4867b4eb0a..2d0315ef7b 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeUtil.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeUtil.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.logging.Level; import org.openide.util.NbBundle.Messages; @@ -308,8 +309,12 @@ public class CorrelationAttributeUtil { if (Account.Type.DEVICE.getTypeName().equalsIgnoreCase(accountTypeStr) == false && predefinedAccountType != null) { // Get the corresponding CentralRepoAccountType from the database. - CentralRepoAccountType crAccountType = CentralRepository.getInstance().getAccountTypeByName(accountTypeStr); - + Optional optCrAccountType = CentralRepository.getInstance().getAccountTypeByName(accountTypeStr); + if (!optCrAccountType.isPresent()) { + return; + } + CentralRepoAccountType crAccountType = optCrAccountType.get(); + int corrTypeId = crAccountType.getCorrelationTypeId(); CorrelationAttributeInstance.Type corrType = CentralRepository.getInstance().getCorrelationTypeById(corrTypeId); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PersonaAccount.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PersonaAccount.java index afbb2fe4b5..59b0ebe627 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PersonaAccount.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PersonaAccount.java @@ -26,8 +26,10 @@ import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Optional; import org.apache.commons.lang3.StringUtils; import org.sleuthkit.datamodel.Account; +import org.sleuthkit.datamodel.TskCoreException; /** * This class represents an association between a Persona and an Account. @@ -206,10 +208,15 @@ public class PersonaAccount { ); // create account - CentralRepoAccount.CentralRepoAccountType crAccountType = getCRInstance().getAccountTypeByName(rs.getString("type_name")); + String accountTypeName = rs.getString("type_name"); + Optional optCrAccountType = getCRInstance().getAccountTypeByName(accountTypeName); + if (! optCrAccountType.isPresent()) { + // The CR account can not be null, so throw an exception + throw new CentralRepoException("Account type with name '" + accountTypeName + "' not found in Central Repository"); + } CentralRepoAccount account = new CentralRepoAccount( rs.getInt("account_id"), - crAccountType, + optCrAccountType.get(), rs.getString("account_unique_identifier")); // create persona account @@ -389,10 +396,15 @@ public class PersonaAccount { while (rs.next()) { // create account - CentralRepoAccount.CentralRepoAccountType crAccountType = getCRInstance().getAccountTypeByName(rs.getString("type_name")); + String accountTypeName = rs.getString("type_name"); + Optional optCrAccountType = getCRInstance().getAccountTypeByName(accountTypeName); + if (! optCrAccountType.isPresent()) { + // The CR account can not be null, so throw an exception + throw new CentralRepoException("Account type with name '" + accountTypeName + "' not found in Central Repository"); + } CentralRepoAccount account = new CentralRepoAccount( rs.getInt("account_id"), - crAccountType, + optCrAccountType.get(), rs.getString("account_unique_identifier")); accountsList.add(account); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java index b48797e3fc..1b4ce08c18 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java @@ -37,6 +37,7 @@ import java.time.LocalDate; import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -78,7 +79,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { private static final int CASE_CACHE_TIMEOUT = 5; private static final int DATA_SOURCE_CACHE_TIMEOUT = 5; private static final int ACCOUNTS_CACHE_TIMEOUT = 5; - private static final Cache accountTypesCache = CacheBuilder.newBuilder().build(); + private static final Cache> accountTypesCache = CacheBuilder.newBuilder().build(); private static final Cache, CentralRepoAccount> accountsCache = CacheBuilder.newBuilder() .expireAfterWrite(ACCOUNTS_CACHE_TIMEOUT, TimeUnit.MINUTES). build(); @@ -1115,7 +1116,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { } @Override - public CentralRepoAccountType getAccountTypeByName(String accountTypeName) throws CentralRepoException { + public Optional getAccountTypeByName(String accountTypeName) throws CentralRepoException { try { return accountTypesCache.get(accountTypeName, () -> getCRAccountTypeFromDb(accountTypeName)); } catch (CacheLoader.InvalidCacheLoadException | ExecutionException ex) { @@ -1155,7 +1156,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { * * @throws CentralRepoException */ - private CentralRepoAccountType getCRAccountTypeFromDb(String accountTypeName) throws CentralRepoException { + private Optional getCRAccountTypeFromDb(String accountTypeName) throws CentralRepoException { String sql = "SELECT * FROM account_types WHERE type_name = ?"; try (Connection conn = connect(); @@ -1166,10 +1167,11 @@ abstract class RdbmsCentralRepo implements CentralRepository { if (resultSet.next()) { Account.Type acctType = new Account.Type(accountTypeName, resultSet.getString("display_name")); CentralRepoAccountType crAccountType = new CentralRepoAccountType(resultSet.getInt("id"), acctType, resultSet.getInt("correlation_type_id")); - accountTypesCache.put(accountTypeName, crAccountType); - return crAccountType; + accountTypesCache.put(accountTypeName, Optional.of(crAccountType)); + return Optional.of(crAccountType); } else { - throw new CentralRepoException("Failed to find entry for account type = " + accountTypeName); + accountTypesCache.put(accountTypeName, Optional.empty()); + return Optional.empty(); } } } catch (SQLException ex) { diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/CorrelationCaseChildNodeFactory.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/CorrelationCaseChildNodeFactory.java index d34c607796..1e2b350669 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/CorrelationCaseChildNodeFactory.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/CorrelationCaseChildNodeFactory.java @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.communications.relationships; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.logging.Level; import org.openide.nodes.AbstractNode; @@ -75,9 +76,9 @@ final class CorrelationCaseChildNodeFactory extends ChildFactory { try { - CorrelationAttributeInstance.Type correlationType = getCorrelationType(account.getAccountType()); - if (correlationType != null) { - List correlationInstances = dbInstance.getArtifactInstancesByTypeValue(correlationType, account.getTypeSpecificID()); + Optional optCorrelationType = getCorrelationType(account.getAccountType()); + if (optCorrelationType.isPresent()) { + List correlationInstances = dbInstance.getArtifactInstancesByTypeValue(optCorrelationType.get(), account.getTypeSpecificID()); correlationInstances.forEach((correlationInstance) -> { CorrelationCase correlationCase = correlationInstance.getCorrelationCase(); uniqueCaseMap.put(correlationCase.getCaseUUID(), correlationCase); @@ -103,24 +104,22 @@ final class CorrelationCaseChildNodeFactory extends ChildFactory getCorrelationType(Account.Type accountType) throws CentralRepoException { + + String accountTypeStr = accountType.getTypeName(); + if (Account.Type.DEVICE.getTypeName().equalsIgnoreCase(accountTypeStr) == false) { + Optional optCrAccountType = CentralRepository.getInstance().getAccountTypeByName(accountTypeStr); + if (optCrAccountType.isPresent()) { + int corrTypeId = optCrAccountType.get().getCorrelationTypeId(); + return Optional.of(CentralRepository.getInstance().getCorrelationTypeById(corrTypeId)); } - } catch (CentralRepoException ex) { - // The excpetion most likely just means we didn't find a matching account - // type - no need to log it. } - return null; + return Optional.empty(); } /** diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryPanelWorker.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryPanelWorker.java index a930e62e8f..1bf618664d 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryPanelWorker.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryPanelWorker.java @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.communications.relationships; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Optional; import java.util.logging.Level; import javax.swing.SwingWorker; import org.sleuthkit.autopsy.casemodule.Case; @@ -75,17 +76,10 @@ class SummaryPanelWorker extends SwingWorker optCrAccountType = CentralRepository.getInstance().getAccountTypeByName(account.getAccountType().getTypeName()); + if (optCrAccountType.isPresent()) { try { - crAccount = CentralRepository.getInstance().getAccount(crAccountType, account.getTypeSpecificID()); + crAccount = CentralRepository.getInstance().getAccount(optCrAccountType.get(), account.getTypeSpecificID()); } catch (InvalidAccountIDException unused) { // This was probably caused to a phone number not making // threw the normalization. diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/ContactArtifactViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/ContactArtifactViewer.java index 2be84beb9e..b17263a26d 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/ContactArtifactViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/ContactArtifactViewer.java @@ -32,6 +32,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.logging.Level; @@ -630,9 +631,9 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac // make a list of all unique accounts for this contact if (!account.getAccountType().equals(Account.Type.DEVICE)) { - CentralRepoAccount.CentralRepoAccountType crAccountType = CentralRepository.getInstance().getAccountTypeByName(account.getAccountType().getTypeName()); - if (crAccountType != null) { - CentralRepoAccount crAccount = CentralRepository.getInstance().getAccount(crAccountType, account.getTypeSpecificID()); + Optional optCrAccountType = CentralRepository.getInstance().getAccountTypeByName(account.getAccountType().getTypeName()); + if (optCrAccountType.isPresent()) { + CentralRepoAccount crAccount = CentralRepository.getInstance().getAccount(optCrAccountType.get(), account.getTypeSpecificID()); if (crAccount != null && uniqueAccountsList.contains(crAccount) == false) { uniqueAccountsList.add(crAccount); From d3f9e52ad51b9e784a3f0e9e468f4e3ca26691ee Mon Sep 17 00:00:00 2001 From: Kelly Kelly Date: Tue, 30 Mar 2021 12:07:33 -0400 Subject: [PATCH 3/9] Updated TskEvent names --- .../sleuthkit/autopsy/casemodule/Case.java | 46 ++++++++----------- .../autopsy/casemodule/events/HostsEvent.java | 2 + ...dEvent.java => OsAccountDeletedEvent.java} | 4 +- ...vedEvent.java => PersonsDeletedEvent.java} | 4 +- 4 files changed, 25 insertions(+), 31 deletions(-) rename Core/src/org/sleuthkit/autopsy/casemodule/events/{OsAccountRemovedEvent.java => OsAccountDeletedEvent.java} (90%) rename Core/src/org/sleuthkit/autopsy/casemodule/events/{PersonsRemovedEvent.java => PersonsDeletedEvent.java} (90%) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index c0497c083c..5f11f50344 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -87,10 +87,10 @@ import org.sleuthkit.autopsy.casemodule.events.HostsChangedEvent; import org.sleuthkit.autopsy.casemodule.events.HostsRemovedEvent; import org.sleuthkit.autopsy.casemodule.events.OsAccountAddedEvent; import org.sleuthkit.autopsy.casemodule.events.OsAccountChangedEvent; -import org.sleuthkit.autopsy.casemodule.events.OsAccountRemovedEvent; +import org.sleuthkit.autopsy.casemodule.events.OsAccountDeletedEvent; import org.sleuthkit.autopsy.casemodule.events.PersonsAddedEvent; import org.sleuthkit.autopsy.casemodule.events.PersonsChangedEvent; -import org.sleuthkit.autopsy.casemodule.events.PersonsRemovedEvent; +import org.sleuthkit.autopsy.casemodule.events.PersonsDeletedEvent; import org.sleuthkit.autopsy.casemodule.events.ReportAddedEvent; import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData.CaseNodeDataException; import org.sleuthkit.autopsy.casemodule.multiusercases.CoordinationServiceUtils; @@ -140,24 +140,16 @@ import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.FileSystem; import org.sleuthkit.datamodel.Host; -import org.sleuthkit.datamodel.HostManager.HostsCreationEvent; -import org.sleuthkit.datamodel.HostManager.HostsUpdateEvent; -import org.sleuthkit.datamodel.HostManager.HostsDeletionEvent; import org.sleuthkit.datamodel.Image; import org.sleuthkit.datamodel.OsAccount; -import org.sleuthkit.datamodel.OsAccountManager.OsAccountsCreationEvent; -import org.sleuthkit.datamodel.OsAccountManager.OsAccountsDeleteEvent; -import org.sleuthkit.datamodel.OsAccountManager.OsAccountsUpdateEvent; import org.sleuthkit.datamodel.Person; -import org.sleuthkit.datamodel.PersonManager.PersonsCreationEvent; -import org.sleuthkit.datamodel.PersonManager.PersonsUpdateEvent; -import org.sleuthkit.datamodel.PersonManager.PersonsDeletionEvent; import org.sleuthkit.datamodel.Report; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TimelineManager; import org.sleuthkit.datamodel.SleuthkitCaseAdminUtil; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskDataException; +import org.sleuthkit.datamodel.TskEvent; import org.sleuthkit.datamodel.TskUnsupportedSchemaVersionException; /** @@ -513,34 +505,34 @@ public class Case { } @Subscribe - public void publishOsAccountAddedEvent(OsAccountsCreationEvent event) { + public void publishOsAccountAddedEvent(TskEvent.OsAccountsAddedTskEvent event) { for(OsAccount account: event.getOsAcounts()) { eventPublisher.publish(new OsAccountAddedEvent(account)); } } @Subscribe - public void publishOsAccountChangedEvent(OsAccountsUpdateEvent event) { + public void publishOsAccountChangedEvent(TskEvent.OsAccountsChangedTskEvent event) { for(OsAccount account: event.getOsAcounts()) { eventPublisher.publish(new OsAccountChangedEvent(account)); } } @Subscribe - public void publishOsAccountDeletedEvent(OsAccountsDeleteEvent event) { + public void publishOsAccountDeletedEvent(TskEvent.OsAccountsDeletedTskEvent event) { for(Long accountId: event.getOsAcountObjectIds()) { - eventPublisher.publish(new OsAccountRemovedEvent(accountId)); + eventPublisher.publish(new OsAccountDeletedEvent(accountId)); } } /** - * Publishes an autopsy event from the sleuthkit HostCreationEvent + * Publishes an autopsy event from the sleuthkit HostAddedEvent * indicating that hosts have been created. * * @param event The sleuthkit event for the creation of hosts. */ @Subscribe - public void publishHostsAddedEvent(HostsCreationEvent event) { + public void publishHostsAddedEvent(TskEvent.HostsAddedTskEvent event) { eventPublisher.publish(new HostsAddedEvent( event == null ? Collections.emptyList() : event.getHosts())); } @@ -552,7 +544,7 @@ public class Case { * @param event The sleuthkit event for the updating of hosts. */ @Subscribe - public void publishHostsChangedEvent(HostsUpdateEvent event) { + public void publishHostsChangedEvent(TskEvent.HostsChangedTskEvent event) { eventPublisher.publish(new HostsChangedEvent( event == null ? Collections.emptyList() : event.getHosts())); } @@ -564,31 +556,31 @@ public class Case { * @param event The sleuthkit event for the deleting of hosts. */ @Subscribe - public void publishHostsDeletedEvent(HostsDeletionEvent event) { + public void publishHostsDeletedEvent(TskEvent.HostsDeletedTskEvent event) { eventPublisher.publish(new HostsRemovedEvent( event == null ? Collections.emptyList() : event.getHosts())); } /** - * Publishes an autopsy event from the sleuthkit PersonCreationEvent + * Publishes an autopsy event from the sleuthkit PersonAddedEvent * indicating that persons have been created. * * @param event The sleuthkit event for the creation of persons. */ @Subscribe - public void publishPersonsAddedEvent(PersonsCreationEvent event) { + public void publishPersonsAddedEvent(TskEvent.PersonsAddedTskEvent event) { eventPublisher.publish(new PersonsAddedEvent( event == null ? Collections.emptyList() : event.getPersons())); } /** - * Publishes an autopsy event from the sleuthkit PersonUpdateEvent + * Publishes an autopsy event from the sleuthkit PersonChangedEvent * indicating that persons have been updated. * * @param event The sleuthkit event for the updating of persons. */ @Subscribe - public void publishPersonsChangedEvent(PersonsUpdateEvent event) { + public void publishPersonsChangedEvent(TskEvent.PersonsChangedTskEvent event) { eventPublisher.publish(new PersonsChangedEvent( event == null ? Collections.emptyList() : event.getPersons())); } @@ -600,8 +592,8 @@ public class Case { * @param event The sleuthkit event for the deleting of persons. */ @Subscribe - public void publishPersonsDeletedEvent(PersonsDeletionEvent event) { - eventPublisher.publish(new PersonsRemovedEvent( + public void publishPersonsDeletedEvent(TskEvent.PersonsDeletedTskEvent event) { + eventPublisher.publish(new PersonsDeletedEvent( event == null ? Collections.emptyList() : event.getPersons())); } } @@ -1803,7 +1795,7 @@ public class Case { } public void notifyOsAccountRemoved(Long osAccountObjectId) { - eventPublisher.publish(new OsAccountRemovedEvent(osAccountObjectId)); + eventPublisher.publish(new OsAccountDeletedEvent(osAccountObjectId)); } /** @@ -1851,7 +1843,7 @@ public class Case { * @param person The person that has been deleted. */ public void notifyPersonDeleted(Person person) { - eventPublisher.publish(new PersonsRemovedEvent(Collections.singletonList(person))); + eventPublisher.publish(new PersonsDeletedEvent(Collections.singletonList(person))); } /** diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/HostsEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/HostsEvent.java index 1ba225f4a2..68c821c979 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/events/HostsEvent.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/HostsEvent.java @@ -33,6 +33,8 @@ import org.sleuthkit.datamodel.TskCoreException; */ public class HostsEvent extends TskDataModelChangeEvent { + private static final long serialVersionUID = 1L; + /** * Retrieves a list of ids from a list of hosts. * diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/OsAccountRemovedEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/OsAccountDeletedEvent.java similarity index 90% rename from Core/src/org/sleuthkit/autopsy/casemodule/events/OsAccountRemovedEvent.java rename to Core/src/org/sleuthkit/autopsy/casemodule/events/OsAccountDeletedEvent.java index 4bf03c90d9..adc726fca8 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/events/OsAccountRemovedEvent.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/OsAccountDeletedEvent.java @@ -27,11 +27,11 @@ import org.sleuthkit.autopsy.events.AutopsyEvent; * oldValue will contain the objectId of the account that was removed. newValue * will be null. */ -public final class OsAccountRemovedEvent extends AutopsyEvent { +public final class OsAccountDeletedEvent extends AutopsyEvent { private static final long serialVersionUID = 1L; - public OsAccountRemovedEvent(Long osAccountObjectId) { + public OsAccountDeletedEvent(Long osAccountObjectId) { super(Case.Events.OS_ACCOUNT_REMOVED.toString(), osAccountObjectId, null); } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/PersonsRemovedEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/PersonsDeletedEvent.java similarity index 90% rename from Core/src/org/sleuthkit/autopsy/casemodule/events/PersonsRemovedEvent.java rename to Core/src/org/sleuthkit/autopsy/casemodule/events/PersonsDeletedEvent.java index 522c841461..a63fef32cb 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/events/PersonsRemovedEvent.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/PersonsDeletedEvent.java @@ -25,7 +25,7 @@ import org.sleuthkit.datamodel.Person; /** * Event fired when persons are removed. */ -public class PersonsRemovedEvent extends PersonsEvent { +public class PersonsDeletedEvent extends PersonsEvent { private static final long serialVersionUID = 1L; @@ -33,7 +33,7 @@ public class PersonsRemovedEvent extends PersonsEvent { * Main constructor. * @param dataModelObjects The list of persons that have been deleted. */ - public PersonsRemovedEvent(List dataModelObjects) { + public PersonsDeletedEvent(List dataModelObjects) { super(Case.Events.PERSONS_DELETED.name(), dataModelObjects); } } From 349d4f82be3a9b79dd34559b95086548892e7f6b Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Wed, 31 Mar 2021 20:17:38 -0400 Subject: [PATCH 4/9] priority categorizer --- .../DefaultDomainCategorizer.java | 5 +- .../DefaultPriorityDomainCategorizer.java | 104 ++++++++++++++++++ .../recentactivity/DomainCategoryRunner.java | 55 +++++---- 3 files changed, 140 insertions(+), 24 deletions(-) create mode 100644 RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DefaultPriorityDomainCategorizer.java diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DefaultDomainCategorizer.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DefaultDomainCategorizer.java index 0d64661f6c..d055e132da 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DefaultDomainCategorizer.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DefaultDomainCategorizer.java @@ -53,8 +53,7 @@ import org.sleuthkit.autopsy.url.analytics.DomainCategory; * https://bugs.openjdk.java.net/browse/JDK-8155591, * https://bugs.eclipse.org/bugs/show_bug.cgi?id=350279. */ -@SuppressWarnings("try") -public class DefaultDomainCategorizer implements DomainCategorizer { +class DefaultDomainCategorizer implements DomainCategorizer { private static final String CSV_DELIMITER = ","; private static final String DOMAIN_TYPE_CSV = "default_domain_categories.csv"; //NON-NLS @@ -162,7 +161,7 @@ public class DefaultDomainCategorizer implements DomainCategorizer { } @Override - public void close() throws Exception { + public void close() throws IOException { // clear out the mapping to release resources mapping = null; } diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DefaultPriorityDomainCategorizer.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DefaultPriorityDomainCategorizer.java new file mode 100644 index 0000000000..9b5714b9a5 --- /dev/null +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DefaultPriorityDomainCategorizer.java @@ -0,0 +1,104 @@ +/* + * 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.recentactivity; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.commons.lang.StringUtils; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.url.analytics.DomainCategorizer; +import org.sleuthkit.autopsy.url.analytics.DomainCategorizerException; +import org.sleuthkit.autopsy.url.analytics.DomainCategory; + +/** + * The autopsy provided domain category provider that overrides all domain + * category providers except the custom web domain categorizations. + */ +@Messages({ + "DefaultPriorityDomainCategorizer_searchEngineCategory=Search Engine" +}) +public class DefaultPriorityDomainCategorizer implements DomainCategorizer { + + // taken from https://www.google.com/supported_domains + private static final List GOOGLE_DOMAINS = Arrays.asList("google.com", "google.ad", "google.ae", "google.com.af", "google.com.ag", "google.com.ai", "google.al", "google.am", "google.co.ao", "google.com.ar", "google.as", "google.at", "google.com.au", "google.az", "google.ba", "google.com.bd", "google.be", "google.bf", "google.bg", "google.com.bh", "google.bi", "google.bj", "google.com.bn", "google.com.bo", "google.com.br", "google.bs", "google.bt", "google.co.bw", "google.by", "google.com.bz", "google.ca", "google.cd", "google.cf", "google.cg", "google.ch", "google.ci", "google.co.ck", "google.cl", "google.cm", "google.cn", "google.com.co", "google.co.cr", "google.com.cu", "google.cv", "google.com.cy", "google.cz", "google.de", "google.dj", "google.dk", "google.dm", "google.com.do", "google.dz", "google.com.ec", "google.ee", "google.com.eg", "google.es", "google.com.et", "google.fi", "google.com.fj", "google.fm", "google.fr", "google.ga", "google.ge", "google.gg", "google.com.gh", "google.com.gi", "google.gl", "google.gm", "google.gr", "google.com.gt", "google.gy", "google.com.hk", "google.hn", "google.hr", "google.ht", "google.hu", "google.co.id", "google.ie", "google.co.il", "google.im", "google.co.in", "google.iq", "google.is", "google.it", "google.je", "google.com.jm", "google.jo", "google.co.jp", "google.co.ke", "google.com.kh", "google.ki", "google.kg", "google.co.kr", "google.com.kw", "google.kz", "google.la", "google.com.lb", "google.li", "google.lk", "google.co.ls", "google.lt", "google.lu", "google.lv", "google.com.ly", "google.co.ma", "google.md", "google.me", "google.mg", "google.mk", "google.ml", "google.com.mm", "google.mn", "google.ms", "google.com.mt", "google.mu", "google.mv", "google.mw", "google.com.mx", "google.com.my", "google.co.mz", "google.com.na", "google.com.ng", "google.com.ni", "google.ne", "google.nl", "google.no", "google.com.np", "google.nr", "google.nu", "google.co.nz", "google.com.om", "google.com.pa", "google.com.pe", "google.com.pg", "google.com.ph", "google.com.pk", "google.pl", "google.pn", "google.com.pr", "google.ps", "google.pt", "google.com.py", "google.com.qa", "google.ro", "google.ru", "google.rw", "google.com.sa", "google.com.sb", "google.sc", "google.se", "google.com.sg", "google.sh", "google.si", "google.sk", "google.com.sl", "google.sn", "google.so", "google.sm", "google.sr", "google.st", "google.com.sv", "google.td", "google.tg", "google.co.th", "google.com.tj", "google.tl", "google.tm", "google.tn", "google.to", "google.com.tr", "google.tt", "google.com.tw", "google.co.tz", "google.com.ua", "google.co.ug", "google.co.uk", "google.com.uy", "google.co.uz", "google.com.vc", "google.co.ve", "google.vg", "google.co.vi", "google.com.vn", "google.vu", "google.ws", "google.rs", "google.co.za", "google.co.zm", "google.co.zw", "google.cat"); + + // taken from https://www.yahoo.com/everything/world + private static final List YAHOO_DOMAINS = Arrays.asList("espanol.yahoo.com", "au.yahoo.com", "be.yahoo.com", "fr-be.yahoo.com", "br.yahoo.com", "ca.yahoo.com", "espanol.yahoo.com", "espanol.yahoo.com", "de.yahoo.com", "es.yahoo.com", "espanol.yahoo.com", "fr.yahoo.com", "in.yahoo.com", "id.yahoo.com", "ie.yahoo.com", "it.yahoo.com", "en-maktoob.yahoo.com", "malaysia.yahoo.com", "espanol.yahoo.com", "nz.yahoo.com", "espanol.yahoo.com", "ph.yahoo.com", "qc.yahoo.com", "ro.yahoo.com", "sg.yahoo.com", "za.yahoo.com", "se.yahoo.com", "uk.yahoo.com", "yahoo.com", "espanol.yahoo.com", "vn.yahoo.com", "gr.yahoo.com", "maktoob.yahoo.com", "yahoo.com", "hk.yahoo.com", "tw.yahoo.com", "yahoo.co.jp"); + + private static final List OTHER_SEARCH_ENGINES = Arrays.asList( + "bing.com", + "baidu.com", + "sogou.com", + "soso.com", + "duckduckgo.com", + "swisscows.com", + "gibiru.com", + "cutestat.com", + "youdao.com", + "biglobe.ne.jp", + "givewater.com", + "ekoru.org", + "ecosia.org", + // according to https://en.wikipedia.org/wiki/Yandex + "yandex.ru", + "yandex.com" + ); + + private static final String WWW_PREFIX = "www"; + + private static final Map DOMAIN_LOOKUP + = Stream.of(GOOGLE_DOMAINS, YAHOO_DOMAINS, OTHER_SEARCH_ENGINES) + .flatMap((lst) -> lst.stream()) + .collect(Collectors.toMap((k) -> k, (k) -> Bundle.DefaultPriorityDomainCategorizer_searchEngineCategory(), (v1, v2) -> v1)); + + @Override + public void initialize() throws DomainCategorizerException { + } + + @Override + public DomainCategory getCategory(String domain, String host) throws DomainCategorizerException { + + String hostToUse = StringUtils.isBlank(host) ? domain : host; + + if (StringUtils.isBlank(hostToUse)) { + return null; + } + + List domainWords = Stream.of(hostToUse.toLowerCase().split("\\.")) + .filter(StringUtils::isNotBlank) + .map(String::trim) + .collect(Collectors.toList()); + + String sanitizedDomain = domainWords.stream() + // skip first word segment if 'www' + .skip(domainWords.size() > 0 && WWW_PREFIX.equals(domainWords.get(0)) ? 1 : 0) + .collect(Collectors.joining(".")); + + String category = DOMAIN_LOOKUP.get(sanitizedDomain); + return category == null ? null : new DomainCategory(sanitizedDomain, category); + } + + @Override + public void close() throws IOException { + } +} diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DomainCategoryRunner.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DomainCategoryRunner.java index 0102f6e868..d24a031a48 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DomainCategoryRunner.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DomainCategoryRunner.java @@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.recentactivity; import java.net.MalformedURLException; import java.net.URL; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -453,33 +454,45 @@ class DomainCategoryRunner extends Extract { @Override void configExtractor() throws IngestModule.IngestModuleException { // lookup all providers, filter null providers, and sort providers - Collection lookupList = Lookup.getDefault().lookupAll(DomainCategorizer.class); - if (lookupList == null) { - lookupList = Collections.emptyList(); - } - - List foundProviders = lookupList.stream() - .filter(provider -> provider != null) - .sorted((a, b) -> { - boolean aIsCustom = a.getClass().getName().contains(CUSTOM_CATEGORIZER_PATH); - boolean bIsCustom = b.getClass().getName().contains(CUSTOM_CATEGORIZER_PATH); - if (aIsCustom != bIsCustom) { - // push custom categorizer to top - return -Boolean.compare(aIsCustom, bIsCustom); - } - - return a.getClass().getName().compareToIgnoreCase(b.getClass().getName()); + Collection lookupCollection = Lookup.getDefault().lookupAll(DomainCategorizer.class); + Collection lookupList = (lookupCollection == null) ? + Collections.emptyList() : + lookupCollection; + + // this will be the class instance of the foundProviders + List foundProviders = new ArrayList<>(); + + // find the custom domain categories provider if present and add it first to the list + lookupList.stream() + .filter(categorizer -> categorizer.getClass().getName().contains(CUSTOM_CATEGORIZER_PATH)) + .findFirst() + .ifPresent((provider) -> foundProviders.add(provider)); + + // add the default priority categorizer + foundProviders.add(new DefaultPriorityDomainCategorizer()); + + // add all others except for the custom web domain categorizer, the default priority + // categorizer and the default categorizer + lookupList.stream() + .filter(categorizer -> categorizer != null) + .filter(categorizer -> { + String className = categorizer.getClass().getName(); + return !className.contains(CUSTOM_CATEGORIZER_PATH) && + !className.equals(DefaultPriorityDomainCategorizer.class.getName()) && + !className.equals(DefaultDomainCategorizer.class.getName()); }) - .collect(Collectors.toList()); - - // add the default categorizer last as a last resort + .sorted((a, b) -> a.getClass().getName().compareToIgnoreCase(b.getClass().getName())) + .forEach(foundProviders::add); + + // add the default categorizer last foundProviders.add(new DefaultDomainCategorizer()); - + for (DomainCategorizer provider : foundProviders) { try { provider.initialize(); } catch (DomainCategorizerException ex) { - throw new IngestModule.IngestModuleException("There was an error instantiating the provider: " + provider.getClass().getSimpleName(), ex); + throw new IngestModule.IngestModuleException("There was an error instantiating the provider: " + + provider.getClass().getSimpleName(), ex); } } From 05b9421ee864289facced08cdcb1de23b990f55b Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Thu, 1 Apr 2021 09:08:08 -0400 Subject: [PATCH 5/9] bundle changes --- .../autopsy/recentactivity/Bundle.properties-MERGED | 1 + .../autopsy/recentactivity/DefaultDomainCategorizer.java | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED index 547b90ca6a..b796a16d26 100755 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED @@ -16,6 +16,7 @@ DataSourceUsage_FlashDrive=Flash Drive # {0} - OS name DataSourceUsageAnalyzer.customVolume.label=OS Drive ({0}) DataSourceUsageAnalyzer.parentModuleName=Recent Activity +DefaultPriorityDomainCategorizer_searchEngineCategory=Search Engine DomainCategoryRunner_moduleName_text=DomainCategoryRunner DomainCategoryRunner_parentModuleName=Recent Activity DomainCategoryRunner_Progress_Message_Domain_Types=Finding Domain Types diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DefaultDomainCategorizer.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DefaultDomainCategorizer.java index d055e132da..0d64661f6c 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DefaultDomainCategorizer.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DefaultDomainCategorizer.java @@ -53,7 +53,8 @@ import org.sleuthkit.autopsy.url.analytics.DomainCategory; * https://bugs.openjdk.java.net/browse/JDK-8155591, * https://bugs.eclipse.org/bugs/show_bug.cgi?id=350279. */ -class DefaultDomainCategorizer implements DomainCategorizer { +@SuppressWarnings("try") +public class DefaultDomainCategorizer implements DomainCategorizer { private static final String CSV_DELIMITER = ","; private static final String DOMAIN_TYPE_CSV = "default_domain_categories.csv"; //NON-NLS @@ -161,7 +162,7 @@ class DefaultDomainCategorizer implements DomainCategorizer { } @Override - public void close() throws IOException { + public void close() throws Exception { // clear out the mapping to release resources mapping = null; } From d6460338f3b31316a373e98ecb56c8b07aca54e5 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Thu, 1 Apr 2021 09:41:56 -0400 Subject: [PATCH 6/9] license update --- .../recentactivity/DefaultPriorityDomainCategorizer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DefaultPriorityDomainCategorizer.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DefaultPriorityDomainCategorizer.java index 9b5714b9a5..da84660cc2 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DefaultPriorityDomainCategorizer.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DefaultPriorityDomainCategorizer.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2020 Basis Technology Corp. + * Copyright 2021 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); From 6bca3aa456e54608ab3ddff931902c2a35f04ff9 Mon Sep 17 00:00:00 2001 From: apriestman Date: Thu, 8 Apr 2021 07:36:42 -0400 Subject: [PATCH 7/9] Remove break statement --- .../src/org/sleuthkit/autopsy/recentactivity/Firefox.java | 1 - 1 file changed, 1 deletion(-) diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java index 0954081f30..29beb2434e 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java @@ -733,7 +733,6 @@ class Firefox extends Extract { } j++; dbFile.delete(); - break; } if(!context.dataSourceIngestIsCancelled()) { From 873299ea2d7bbadef1fc7d9b223d08e566b0c8f4 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Thu, 8 Apr 2021 08:38:43 -0400 Subject: [PATCH 8/9] send sqlite db to config instead of install folder --- .../WebCategoriesDataModel.java | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/url/analytics/domaincategorization/WebCategoriesDataModel.java b/Core/src/org/sleuthkit/autopsy/url/analytics/domaincategorization/WebCategoriesDataModel.java index 5b2932b718..6db36ffc47 100644 --- a/Core/src/org/sleuthkit/autopsy/url/analytics/domaincategorization/WebCategoriesDataModel.java +++ b/Core/src/org/sleuthkit/autopsy/url/analytics/domaincategorization/WebCategoriesDataModel.java @@ -26,6 +26,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.File; import java.io.IOException; +import java.nio.file.Path; import java.nio.file.Paths; import java.sql.Connection; import java.sql.DriverManager; @@ -44,7 +45,7 @@ import java.util.stream.IntStream; import java.util.stream.Stream; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; -import org.openide.modules.InstalledFileLocator; +import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.url.analytics.DomainCategory; /** @@ -136,13 +137,21 @@ class WebCategoriesDataModel implements AutoCloseable { * @return The path or null if the path cannot be reconciled. */ private static File getDefaultPath() { - File dir = InstalledFileLocator.getDefault().locate(ROOT_FOLDER, WebCategoriesDataModel.class.getPackage().getName(), false); - if (dir == null || !dir.exists()) { - logger.log(Level.WARNING, String.format("Unable to find file %s with InstalledFileLocator", ROOT_FOLDER)); + String configDir = PlatformUtil.getUserConfigDirectory(); + if (configDir == null || !new File(configDir).exists()) { + logger.log(Level.WARNING, "Unable to find UserConfigDirectory"); return null; } - return Paths.get(dir.getAbsolutePath(), FILE_REL_PATH).toFile(); + Path subDirPath = Paths.get(configDir, ROOT_FOLDER); + File subDir = subDirPath.toFile(); + if (!subDir.exists()) { + if (subDir.mkdirs()) { + logger.log(Level.WARNING, "There was an issue creating custom domain config at: {0}", subDirPath.toString()); + } + } + + return Paths.get(configDir, ROOT_FOLDER, FILE_REL_PATH).toFile(); } /** @@ -530,7 +539,7 @@ class WebCategoriesDataModel implements AutoCloseable { public synchronized void close() throws SQLException { if (dbConn != null) { dbConn.close(); - dbConn = null; + dbConn = null; } } } From f0693caa835ba3d4e42bf87d13c7e786db617cf8 Mon Sep 17 00:00:00 2001 From: Greg DiCristofaro Date: Thu, 8 Apr 2021 09:01:06 -0400 Subject: [PATCH 9/9] fix for logging if missing directory --- .../domaincategorization/WebCategoriesDataModel.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/url/analytics/domaincategorization/WebCategoriesDataModel.java b/Core/src/org/sleuthkit/autopsy/url/analytics/domaincategorization/WebCategoriesDataModel.java index 6db36ffc47..bb5c2abbae 100644 --- a/Core/src/org/sleuthkit/autopsy/url/analytics/domaincategorization/WebCategoriesDataModel.java +++ b/Core/src/org/sleuthkit/autopsy/url/analytics/domaincategorization/WebCategoriesDataModel.java @@ -145,10 +145,8 @@ class WebCategoriesDataModel implements AutoCloseable { Path subDirPath = Paths.get(configDir, ROOT_FOLDER); File subDir = subDirPath.toFile(); - if (!subDir.exists()) { - if (subDir.mkdirs()) { + if (!subDir.exists() && !subDir.mkdirs()) { logger.log(Level.WARNING, "There was an issue creating custom domain config at: {0}", subDirPath.toString()); - } } return Paths.get(configDir, ROOT_FOLDER, FILE_REL_PATH).toFile();