diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties index 94dffdeb9b..80e8cfd4d4 100644 --- a/Core/nbproject/project.properties +++ b/Core/nbproject/project.properties @@ -45,7 +45,6 @@ file.reference.jericho-html-3.3.jar=release/modules/ext/jericho-html-3.3.jar file.reference.jgraphx-v3.8.0.jar=release/modules/ext/jgraphx-v3.8.0.jar file.reference.jhighlight-1.0.3.jar=release\\modules\\ext\\jhighlight-1.0.3.jar file.reference.jmatio-1.5.jar=release\\modules\\ext\\jmatio-1.5.jar -file.reference.jna-5.1.0.jar=release\\modules\\ext\\jna-5.1.0.jar file.reference.json-simple-1.1.1.jar=release\\modules\\ext\\json-simple-1.1.1.jar file.reference.jsoup-1.11.3.jar=release\\modules\\ext\\jsoup-1.11.3.jar file.reference.jul-to-slf4j-1.7.25.jar=release\\modules\\ext\\jul-to-slf4j-1.7.25.jar @@ -97,7 +96,6 @@ file.reference.xz-1.8.jar=release\\modules\\ext\\xz-1.8.jar file.reference.zookeeper-3.4.6.jar=release/modules/ext/zookeeper-3.4.6.jar file.reference.SparseBitSet-1.1.jar=release/modules/ext/SparseBitSet-1.1.jar file.reference.commons-validator-1.6.jar=release/modules/ext/commons-validator-1.6.jar -file.reference.jna-3.4.0.jar=release/modules/ext/jna-3.4.0.jar file.reference.api-common-1.7.0.jar=release/modules/ext/api-common-1.7.0.jar file.reference.gax-1.44.0.jar=release/modules/ext/gax-1.44.0.jar file.reference.gax-grpc-1.44.0.jar=release/modules/ext/gax-grpc-1.44.0.jar diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index 182b92a661..02d37221a7 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -615,10 +615,6 @@ ext/commons-validator-1.6.jar release/modules/ext/commons-validator-1.6.jar - - ext/jna-5.1.0.jar - release\modules\ext\jna-5.1.0.jar - ext/jbig2-imageio-3.0.2.jar release\modules\ext\jbig2-imageio-3.0.2.jar diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Bundle.properties-MERGED index 345d5a420e..44f4462515 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Bundle.properties-MERGED @@ -7,7 +7,14 @@ AbstractSqlEamDb.cannotUpgrage.message=Currently selected database platform "{0} AbstractSqlEamDb.failedToReadMajorVersion.message=Failed to read schema version for Central Repository. AbstractSqlEamDb.failedToReadMinorVersion.message=Failed to read schema minor version for Central Repository. AbstractSqlEamDb.upgradeSchema.incompatible=The selected Central Repository is not compatible with the current version of the application, please upgrade the application if you wish to use this Central Repository. +CentralRepoDbChoice.Disabled.Text=Disabled +CentralRepoDbChoice.PostgreSQL.Text=Custom PostgreSQL +CentralRepoDbChoice.PostgreSQL_Multiuser.Text=PostgreSQL using multi-user settings +CentralRepoDbChoice.Sqlite.Text=SQLite CentralRepoDbManager.connectionErrorMsg.text=Failed to connect to central repository database. +CentralRepositoryService.progressMsg.updatingDataSourcesTable=Checking for v1.2 data updates... +CentralRepositoryService.progressMsg.updatingSchema=Updating schema... +CentralRepositoryService.serviceName=Central Repository Service CorrelationAttributeInstance.invalidName.message=Invalid database table name. Name must start with a lowercase letter and can only contain lowercase letters, numbers, and '_'. CorrelationAttributeInstance.nullName.message=Database name is null. CorrelationAttributeUtil.emailaddresses.text=Email Addresses @@ -21,7 +28,6 @@ CorrelationType.MAC.displayName=MAC Addresses CorrelationType.PHONE.displayName=Phone Numbers CorrelationType.SSID.displayName=Wireless Networks CorrelationType.USBID.displayName=USB Devices -DataSourceUpdateService.serviceName.text=Update Central Repository Data Sources EamArtifactInstances.knownStatus.bad=Bad EamArtifactInstances.knownStatus.known=Known EamArtifactInstances.knownStatus.unknown=Unknown diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbUpgrader13To14.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbUpgrader13To14.java index 9d6473055e..0acfd34ea5 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbUpgrader13To14.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbUpgrader13To14.java @@ -51,7 +51,7 @@ public class CentralRepoDbUpgrader13To14 implements CentralRepoDbUpgrader { if (type.getId() >= CorrelationAttributeInstance.ADDITIONAL_TYPES_BASE_ID) { // these are new Correlation types - new tables need to be created - statement.execute(String.format(RdbmsCentralRepoFactory.getCreateArtifactInstancesTableTemplate(selectedPlatform), instance_type_dbname, instance_type_dbname)); + statement.execute(String.format(RdbmsCentralRepoFactory.getCreateAccountInstancesTableTemplate(selectedPlatform), instance_type_dbname, instance_type_dbname)); statement.execute(String.format(RdbmsCentralRepoFactory.getAddCaseIdIndexTemplate(), instance_type_dbname, instance_type_dbname)); statement.execute(String.format(RdbmsCentralRepoFactory.getAddDataSourceIdIndexTemplate(), instance_type_dbname, instance_type_dbname)); statement.execute(String.format(RdbmsCentralRepoFactory.getAddValueIndexTemplate(), instance_type_dbname, instance_type_dbname)); @@ -61,9 +61,8 @@ public class CentralRepoDbUpgrader13To14 implements CentralRepoDbUpgrader { // add new correlation type CentralRepoDbUtil.insertCorrelationType(connection, type); - } else { - - // Alter the existing X_Instance tables to add account_id column + } else if (type.getId() == CorrelationAttributeInstance.EMAIL_TYPE_ID || type.getId() == CorrelationAttributeInstance.PHONE_TYPE_ID) { + // Alter the existing _instance tables for Phone and Email attributes to add account_id column String sqlStr = String.format(getAlterArtifactInstancesAddAccountIdTemplate(selectedPlatform), instance_type_dbname); statement.execute(sqlStr); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbUtil.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbUtil.java index 826c315c02..69ea8d4e05 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbUtil.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDbUtil.java @@ -325,4 +325,17 @@ public class CentralRepoDbUtil { closeStatement(preparedStatement); } + /** + * Checks if the given correlation attribute type has an account behind it. + * + * @param type Correlation type to check. + * + * @return True If the specified correlation type has an account. + */ + static boolean correlationAttribHasAnAccount(CorrelationAttributeInstance.Type type) { + return (type.getId() >= CorrelationAttributeInstance.ADDITIONAL_TYPES_BASE_ID) + || type.getId() == CorrelationAttributeInstance.PHONE_TYPE_ID + || type.getId() == CorrelationAttributeInstance.EMAIL_TYPE_ID; + } + } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoPostgresSettingsUtil.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoPostgresSettingsUtil.java index 3f75e9e183..86f929ba0e 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoPostgresSettingsUtil.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoPostgresSettingsUtil.java @@ -63,9 +63,21 @@ public class CentralRepoPostgresSettingsUtil { private CentralRepoPostgresSettingsUtil() {} - private void logException(TryHandler handler) { + /** + * Uses setter object to set a value as specified by 'value'. In the event that 'value' + * is null, the setter will not be called. Exceptions that are raised from the setter will + * be logged. + * + * @param setter The setter to call. + * @param value The value to use with the setter. + */ + private void setValOrLog(ValueSetter setter, String value) { + // ignore null values as they indicate a setting that is not set yet + if (value == null || value.isEmpty()) + return; + try { - handler.operation(); + setter.set(value); } catch (CentralRepoException | NumberFormatException e) { LOGGER.log(Level.WARNING, "There was an error in converting central repo postgres settings", e); @@ -73,10 +85,10 @@ public class CentralRepoPostgresSettingsUtil { } /** - * This interface represents an action that potentially throws an exception. + * This interface represents a setter that potentially throws an exception. */ - private interface TryHandler { - void operation() throws CentralRepoException, NumberFormatException; + private interface ValueSetter { + void set(String value) throws CentralRepoException, NumberFormatException; } /** @@ -96,15 +108,12 @@ public class CentralRepoPostgresSettingsUtil { return settings; } - logException(() -> settings.setHost(muConn.getHost())); - logException(() -> settings.setDbName(PostgresConnectionSettings.DEFAULT_DBNAME)); - logException(() -> settings.setUserName(muConn.getUserName())); - - logException(() -> settings.setPort(Integer.parseInt(muConn.getPort()))); - logException(() -> settings.setBulkThreshold(RdbmsCentralRepo.DEFAULT_BULK_THRESHHOLD)); - - logException(() -> settings.setPassword(muConn.getPassword())); + setValOrLog((v) -> settings.setHost(v), muConn.getHost()); + setValOrLog((v) -> settings.setUserName(v), muConn.getUserName()); + setValOrLog((v) -> settings.setPassword(v), muConn.getPassword()); + setValOrLog((v) -> settings.setPort(Integer.parseInt(v)), muConn.getPort()); + return settings; } @@ -120,24 +129,27 @@ public class CentralRepoPostgresSettingsUtil { Map keyVals = ModuleSettings.getConfigSettings(MODULE_KEY); - logException(() -> settings.setHost(keyVals.get(HOST_KEY))); - logException(() -> settings.setDbName(keyVals.get(DBNAME_KEY))); - logException(() -> settings.setUserName(keyVals.get(USER_KEY))); + setValOrLog((v) -> settings.setHost(v), keyVals.get(HOST_KEY)); + setValOrLog((v) -> settings.setDbName(v), keyVals.get(DBNAME_KEY)); + setValOrLog((v) -> settings.setUserName(v), keyVals.get(USER_KEY)); - logException(() -> settings.setPort(Integer.parseInt(keyVals.get(PORT_KEY)))); - logException(() -> settings.setBulkThreshold(Integer.parseInt(keyVals.get((BULK_THRESHOLD_KEY))))); + setValOrLog((v) -> settings.setPort(Integer.parseInt(v)), keyVals.get(PORT_KEY)); + setValOrLog((v) -> settings.setBulkThreshold(Integer.parseInt(v)), keyVals.get((BULK_THRESHOLD_KEY))); String passwordHex = keyVals.get(PASSWORD_KEY); - String password; - try { - password = TextConverter.convertHexTextToText(passwordHex); - } catch (TextConverterException ex) { - LOGGER.log(Level.WARNING, "Failed to convert password from hex text to text.", ex); - password = null; + if (passwordHex != null) { + String password; + try { + password = TextConverter.convertHexTextToText(passwordHex); + } catch (TextConverterException ex) { + LOGGER.log(Level.WARNING, "Failed to convert password from hex text to text.", ex); + password = null; + } + + final String finalPassword = password; + setValOrLog((v) -> settings.setPassword(v), finalPassword); } - final String finalPassword = password; - logException(() -> settings.setPassword(finalPassword)); return settings; } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java index 5b81dc6b3b..6a4a138cfb 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java @@ -806,15 +806,6 @@ public interface CentralRepository { public void processSelectClause(String selectClause, InstanceTableCallback instanceTableCallback) throws CentralRepoException; - /** - * Returns list of all correlation types. - * - * @return list of Correlation types - * @throws CentralRepoException - */ - List getCorrelationTypes() throws CentralRepoException; - - /** * Get account type by type name. * diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepositoryService.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepositoryService.java new file mode 100644 index 0000000000..8b442efc24 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepositoryService.java @@ -0,0 +1,115 @@ +/* + * Central Repository + * + * Copyright 2018-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.centralrepository.datamodel; + +import org.openide.util.NbBundle; +import org.openide.util.lookup.ServiceProvider; +import org.sleuthkit.autopsy.appservices.AutopsyService; +import org.sleuthkit.autopsy.progress.ProgressIndicator; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * The Autopsy application service for the central repository. + */ +@ServiceProvider(service = AutopsyService.class) +public class CentralRepositoryService implements AutopsyService { + + @Override + @NbBundle.Messages({ + "CentralRepositoryService.serviceName=Central Repository Service" + }) + public String getServiceName() { + return Bundle.CentralRepositoryService_serviceName(); + } + + @NbBundle.Messages({ + "CentralRepositoryService.progressMsg.updatingSchema=Updating schema...", + "CentralRepositoryService.progressMsg.updatingDataSourcesTable=Checking for v1.2 data updates...",}) + @Override + public void openCaseResources(CaseContext context) throws AutopsyServiceException { + if (!CentralRepository.isEnabled()) { + return; + } + + ProgressIndicator progress = context.getProgressIndicator(); + progress.progress(Bundle.CentralRepositoryService_progressMsg_updatingSchema()); + updateSchema(); + + if (context.cancelRequested()) { + return; + } + + progress.progress(Bundle.CentralRepositoryService_progressMsg_updatingDataSourcesTable()); + dataUpgradeForVersion1dot2(context.getCase()); + } + + /** + * Updates the central repository schema to the latest version. + * + * @throws AutopsyServiceException + */ + private void updateSchema() throws AutopsyServiceException { + try { + CentralRepoDbManager.upgradeDatabase(); + } catch (CentralRepoException ex) { + throw new AutopsyServiceException("Failed to update the Central Repository schema", ex); + } + } + + /** + * Adds missing data source object IDs from data sources in this case to the + * corresponding records in the central repository. This is a data update to + * go with the v1.2 schema update. + * + * @throws AutopsyServiceException + */ + private void dataUpgradeForVersion1dot2(Case currentCase) throws AutopsyServiceException { + try { + /* + * If the case is in the central repository, there may be missing + * data source object IDs in the data_sources.datasource_obj_id + * column that was added in the version 1.2 schema update. + */ + CentralRepository centralRepository = CentralRepository.getInstance(); + CorrelationCase correlationCase = centralRepository.getCase(currentCase); + if (correlationCase != null) { + for (CorrelationDataSource correlationDataSource : centralRepository.getDataSources()) { + /* + * ResultSet.getLong returns zero when the value in the + * result set is NULL. + */ + if (correlationDataSource.getCaseID() == correlationCase.getID() && correlationDataSource.getDataSourceObjectID() == 0) { + for (Content dataSource : currentCase.getDataSources()) { + if (((DataSource) dataSource).getDeviceId().equals(correlationDataSource.getDeviceID()) && dataSource.getName().equals(correlationDataSource.getName())) { + centralRepository.addDataSourceObjectId(correlationDataSource.getID(), dataSource.getId()); + break; + } + } + } + } + } + } catch (CentralRepoException | TskCoreException ex) { + throw new AutopsyServiceException("Failed to update data sources in the Central Repository for schema v1.2", ex); + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeInstance.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeInstance.java index 1ab978bc84..4d5e2857a1 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeInstance.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeInstance.java @@ -294,8 +294,9 @@ public class CorrelationAttributeInstance implements Serializable { // Create Correlation Types for Accounts. int correlationTypeId = ADDITIONAL_TYPES_BASE_ID; for (Account.Type type : Account.Type.PREDEFINED_ACCOUNT_TYPES) { + // Skip Device account type - we dont want to correlate on those. // Skip Phone and Email accounts as there are already Correlation types defined for those. - if (type != Account.Type.EMAIL && type != Account.Type.PHONE) { + if (type != Account.Type.DEVICE && type != Account.Type.EMAIL && type != Account.Type.PHONE) { defaultCorrelationTypes.add(new CorrelationAttributeInstance.Type(correlationTypeId, type.getDisplayName(), type.getTypeName().toLowerCase() + "_acct", true, true)); //NON-NLS correlationTypeId++; } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeNormalizer.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeNormalizer.java index f68dab484a..d762e74945 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeNormalizer.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeNormalizer.java @@ -23,6 +23,8 @@ import java.util.List; import java.util.Optional; import org.apache.commons.validator.routines.DomainValidator; import org.apache.commons.validator.routines.EmailValidator; +import org.sleuthkit.datamodel.CommunicationsUtils; +import org.sleuthkit.datamodel.TskCoreException; /** * Provides functions for normalizing data by attribute type before insertion or @@ -152,26 +154,25 @@ final public class CorrelationAttributeNormalizer { } /** - * Verify that there is an '@' and no invalid characters. Should normalize - * to lower case. + * Verify and normalize email address. */ private static String normalizeEmail(String data) throws CorrelationAttributeNormalizationException { - EmailValidator validator = EmailValidator.getInstance(true, true); - if (validator.isValid(data)) { - return data.toLowerCase(); - } else { - throw new CorrelationAttributeNormalizationException(String.format("Data was expected to be a valid email address: %s", data)); - } + try { + return CommunicationsUtils.normalizeEmailAddress(data); + } + catch(TskCoreException ex) { + throw new CorrelationAttributeNormalizationException(String.format("Data was expected to be a valid email address: %s", data), ex); + } } /** - * Verify it is only numbers and '+'. Strip spaces, dashes, and parentheses. + * Verify and normalize phone number. */ private static String normalizePhone(String data) throws CorrelationAttributeNormalizationException { - if (data.matches("\\+?[0-9()\\-\\s]+")) { - String phoneNumber = data.replaceAll("[^0-9\\+]", ""); - return phoneNumber; - } else { + try { + return CommunicationsUtils.normalizePhoneNum(data); + } + catch(TskCoreException ex) { throw new CorrelationAttributeNormalizationException(String.format("Data was expected to be a valid phone number: %s", data)); } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeUtil.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeUtil.java index 3d2abc5dd0..ed1f0a0f3d 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeUtil.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeUtil.java @@ -27,6 +27,7 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoAccount.CentralRepoAccountType; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Account; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -184,25 +185,29 @@ public class CorrelationAttributeUtil { // Get the account type from the artifact BlackboardAttribute accountTypeAttribute = acctArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE)); String accountTypeStr = accountTypeAttribute.getValueString(); + + // do not create any correlation attribute instance for a Device account + if (Account.Type.DEVICE.getTypeName().equalsIgnoreCase(accountTypeStr) == false) { - // Get the corresponding CentralRepoAccountType from the database. - CentralRepoAccountType crAccountType = CentralRepository.getInstance().getAccountTypeByName(accountTypeStr); + // Get the corresponding CentralRepoAccountType from the database. + CentralRepoAccountType crAccountType = CentralRepository.getInstance().getAccountTypeByName(accountTypeStr); - int corrTypeId = crAccountType.getCorrelationTypeId(); - CorrelationAttributeInstance.Type corrType = CentralRepository.getInstance().getCorrelationTypeById(corrTypeId); - - // Get the account identifier - BlackboardAttribute accountIdAttribute = acctArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ID)); - String accountIdStr = accountIdAttribute.getValueString(); - - // add/get the account and get its accountId. - CentralRepoAccount crAccount = CentralRepository.getInstance().getOrCreateAccount(crAccountType, accountIdStr); - - CorrelationAttributeInstance corrAttr = makeCorrAttr(acctArtifact, corrType, accountIdStr); - if (corrAttr != null) { - // set the account_id in correlation attribute - corrAttr.setAccountId(crAccount.getAccountId()); - corrAttrInstances.add(corrAttr); + int corrTypeId = crAccountType.getCorrelationTypeId(); + CorrelationAttributeInstance.Type corrType = CentralRepository.getInstance().getCorrelationTypeById(corrTypeId); + + // Get the account identifier + BlackboardAttribute accountIdAttribute = acctArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ID)); + String accountIdStr = accountIdAttribute.getValueString(); + + // add/get the account and get its accountId. + CentralRepoAccount crAccount = CentralRepository.getInstance().getOrCreateAccount(crAccountType, accountIdStr); + + CorrelationAttributeInstance corrAttr = makeCorrAttr(acctArtifact, corrType, accountIdStr); + if (corrAttr != null) { + // set the account_id in correlation attribute + corrAttr.setAccountId(crAccount.getAccountId()); + corrAttrInstances.add(corrAttr); + } } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/DataSourceUpdateService.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/DataSourceUpdateService.java deleted file mode 100644 index 2450ca3440..0000000000 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/DataSourceUpdateService.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Central Repository - * - * Copyright 2018 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.centralrepository.datamodel; - -import org.openide.util.NbBundle; -import org.openide.util.lookup.ServiceProvider; -import org.sleuthkit.autopsy.appservices.AutopsyService; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.DataSource; -import org.sleuthkit.datamodel.TskCoreException; - -/** - * Class which updates the data sources in the central repository to include the - * object id which ties them to the current case. - * - */ -@ServiceProvider(service = AutopsyService.class) -public class DataSourceUpdateService implements AutopsyService { - - @Override - @NbBundle.Messages({"DataSourceUpdateService.serviceName.text=Update Central Repository Data Sources"}) - public String getServiceName() { - return Bundle.DataSourceUpdateService_serviceName_text(); - } - - @Override - public void openCaseResources(CaseContext context) throws AutopsyServiceException { - if (CentralRepository.isEnabled()) { - try { - CentralRepository centralRepository = CentralRepository.getInstance(); - CorrelationCase correlationCase = centralRepository.getCase(context.getCase()); - //if the case isn't in the central repository yet there won't be data sources in it to update - if (correlationCase != null) { - for (CorrelationDataSource correlationDataSource : centralRepository.getDataSources()) { - //ResultSet.getLong has a value of 0 when the value is null - if (correlationDataSource.getCaseID() == correlationCase.getID() && correlationDataSource.getDataSourceObjectID() == 0) { - for (Content dataSource : context.getCase().getDataSources()) { - if (((DataSource) dataSource).getDeviceId().equals(correlationDataSource.getDeviceID()) && dataSource.getName().equals(correlationDataSource.getName())) { - centralRepository.addDataSourceObjectId(correlationDataSource.getID(), dataSource.getId()); - break; - } - } - } - } - } - } catch (CentralRepoException | TskCoreException ex) { - throw new AutopsyServiceException("Unabe to update datasources in central repository", ex); - } - } - } - -} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java index 63f8e2f13a..256e5407c9 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java @@ -80,6 +80,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { .expireAfterWrite(ACCOUNTS_CACHE_TIMEOUT, TimeUnit.MINUTES). build(); + private boolean isCRTypeCacheInitialized; private static final Cache typeCache = CacheBuilder.newBuilder().build(); private static final Cache caseCacheByUUID = CacheBuilder.newBuilder() .expireAfterWrite(CASE_CACHE_TIMEOUT, TimeUnit.MINUTES). @@ -106,6 +107,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { * @throws UnknownHostException, EamDbException */ protected RdbmsCentralRepo() throws CentralRepoException { + isCRTypeCacheInitialized = false; bulkArtifactsCount = 0; bulkArtifacts = new HashMap<>(); @@ -215,7 +217,10 @@ abstract class RdbmsCentralRepo implements CentralRepository { * Reset the contents of the caches associated with EamDb results. */ protected final void clearCaches() { - typeCache.invalidateAll(); + synchronized(typeCache) { + typeCache.invalidateAll(); + isCRTypeCacheInitialized = false; + } caseCacheByUUID.invalidateAll(); caseCacheById.invalidateAll(); dataSourceCacheByDsObjectId.invalidateAll(); @@ -1002,18 +1007,26 @@ abstract class RdbmsCentralRepo implements CentralRepository { public void addArtifactInstance(CorrelationAttributeInstance eamArtifact) throws CentralRepoException { checkAddArtifactInstanceNulls(eamArtifact); - - - - // @@@ We should cache the case and data source IDs in memory String tableName = CentralRepoDbUtil.correlationTypeToInstanceTableName(eamArtifact.getCorrelationType()); - String sql - = "INSERT INTO " + boolean artifactHasAnAccount = CentralRepoDbUtil.correlationAttribHasAnAccount(eamArtifact.getCorrelationType()); + + String sql; + // _instance table for accounts have an additional account_id column + if (artifactHasAnAccount) { + sql = "INSERT INTO " + tableName + "(case_id, data_source_id, value, file_path, known_status, comment, file_obj_id, account_id) " + "VALUES (?, ?, ?, ?, ?, ?, ?, ?) " + getConflictClause(); + } + else { + sql = "INSERT INTO " + + tableName + + "(case_id, data_source_id, value, file_path, known_status, comment, file_obj_id) " + + "VALUES (?, ?, ?, ?, ?, ?, ?) " + + getConflictClause(); + } try (Connection conn = connect(); PreparedStatement preparedStatement = conn.prepareStatement(sql);) { @@ -1032,10 +1045,13 @@ abstract class RdbmsCentralRepo implements CentralRepository { } preparedStatement.setLong(7, eamArtifact.getFileObjectId()); - if (eamArtifact.getAccountId() >= 0) { - preparedStatement.setLong(8, eamArtifact.getAccountId()); - } else { - preparedStatement.setNull(8, Types.INTEGER); + // set in the accountId only for artifacts that represent accounts + if (artifactHasAnAccount) { + if (eamArtifact.getAccountId() >= 0) { + preparedStatement.setLong(8, eamArtifact.getAccountId()); + } else { + preparedStatement.setNull(8, Types.INTEGER); + } } preparedStatement.executeUpdate(); @@ -3002,7 +3018,9 @@ abstract class RdbmsCentralRepo implements CentralRepository { typeId = newCorrelationTypeKnownId(newType); } - typeCache.put(newType.getId(), newType); + synchronized(typeCache) { + typeCache.put(newType.getId(), newType); + } return typeId; } @@ -3116,27 +3134,12 @@ abstract class RdbmsCentralRepo implements CentralRepository { @Override public List getDefinedCorrelationTypes() throws CentralRepoException { - Connection conn = connect(); - List aTypes = new ArrayList<>(); - PreparedStatement preparedStatement = null; - ResultSet resultSet = null; - String sql = "SELECT * FROM correlation_types"; - - try { - preparedStatement = conn.prepareStatement(sql); - resultSet = preparedStatement.executeQuery(); - while (resultSet.next()) { - aTypes.add(getCorrelationTypeFromResultSet(resultSet)); + synchronized (typeCache) { + if (isCRTypeCacheInitialized == false) { + getCorrelationTypesFromCr(); } - return aTypes; - - } catch (SQLException ex) { - throw new CentralRepoException("Error getting all correlation types.", ex); // NON-NLS - } finally { - CentralRepoDbUtil.closeStatement(preparedStatement); - CentralRepoDbUtil.closeResultSet(resultSet); - CentralRepoDbUtil.closeConnection(conn); + return new ArrayList<>(typeCache.asMap().values()); } } @@ -3232,7 +3235,9 @@ abstract class RdbmsCentralRepo implements CentralRepository { preparedStatement.setInt(4, aType.isEnabled() ? 1 : 0); preparedStatement.setInt(5, aType.getId()); preparedStatement.executeUpdate(); - typeCache.put(aType.getId(), aType); + synchronized(typeCache) { + typeCache.put(aType.getId(), aType); + } } catch (SQLException ex) { throw new CentralRepoException("Error updating correlation type.", ex); // NON-NLS } finally { @@ -3254,7 +3259,9 @@ abstract class RdbmsCentralRepo implements CentralRepository { @Override public CorrelationAttributeInstance.Type getCorrelationTypeById(int typeId) throws CentralRepoException { try { - return typeCache.get(typeId, () -> getCorrelationTypeByIdFromCr(typeId)); + synchronized(typeCache) { + return typeCache.get(typeId, () -> getCorrelationTypeByIdFromCr(typeId)); + } } catch (CacheLoader.InvalidCacheLoadException ignored) { //lambda valueloader returned a null value and cache can not store null values this is normal if the correlation type does not exist in the central repo yet return null; @@ -3263,44 +3270,6 @@ abstract class RdbmsCentralRepo implements CentralRepository { } } - /** - * Returns a list of all correlation types. It uses the cache to build the - * list. If the cache is empty, it reads from the database and loads up the - * cache. - * - * @return List of correlation types. - * @throws CentralRepoException - */ - @Override - public List getCorrelationTypes() throws CentralRepoException { - - if (typeCache.size() == 0) { - getCorrelationTypesFromCr(); - } - - return new ArrayList<>(typeCache.asMap().values()); - } - - /** - * Gets a Correlation type with the specified name. - * - * @param correlationtypeName Correlation type name - * @return Correlation type matching the given name, null if none matches. - * - * @throws CentralRepoException - */ - public CorrelationAttributeInstance.Type getCorrelationTypeByName(String correlationtypeName) throws CentralRepoException { - List correlationTypesList = getCorrelationTypes(); - - CorrelationAttributeInstance.Type correlationType - = correlationTypesList.stream() - .filter(x -> correlationtypeName.equalsIgnoreCase(x.getDisplayName())) - .findAny() - .orElse(null); - - return null; - } - /** * Get the EamArtifact.Type that has the given Type.Id from the central repo @@ -3347,16 +3316,22 @@ abstract class RdbmsCentralRepo implements CentralRepository { private void getCorrelationTypesFromCr() throws CentralRepoException { // clear out the cache - typeCache.invalidateAll(); + synchronized(typeCache) { + typeCache.invalidateAll(); + isCRTypeCacheInitialized = false; + } String sql = "SELECT * FROM correlation_types"; try ( Connection conn = connect(); PreparedStatement preparedStatement = conn.prepareStatement(sql); ResultSet resultSet = preparedStatement.executeQuery();) { - while (resultSet.next()) { - CorrelationAttributeInstance.Type aType = getCorrelationTypeFromResultSet(resultSet); - typeCache.put(aType.getId(), aType); + synchronized(typeCache) { + while (resultSet.next()) { + CorrelationAttributeInstance.Type aType = getCorrelationTypeFromResultSet(resultSet); + typeCache.put(aType.getId(), aType); + } + isCRTypeCacheInitialized = true; } } catch (SQLException ex) { throw new CentralRepoException("Error getting correlation types.", ex); // NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepoFactory.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepoFactory.java index 1232d708a6..c01d3ee4e4 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepoFactory.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepoFactory.java @@ -83,6 +83,7 @@ public class RdbmsCentralRepoFactory { public boolean initializeDatabaseSchema() { String createArtifactInstancesTableTemplate = getCreateArtifactInstancesTableTemplate(selectedPlatform); + String createAccountInstancesTableTemplate = getCreateAccountInstancesTableTemplate(selectedPlatform); String instancesCaseIdIdx = getAddCaseIdIndexTemplate(); String instancesDatasourceIdIdx = getAddDataSourceIdIndexTemplate(); @@ -147,7 +148,13 @@ public class RdbmsCentralRepoFactory { reference_type_dbname = CentralRepoDbUtil.correlationTypeToReferenceTableName(type); instance_type_dbname = CentralRepoDbUtil.correlationTypeToInstanceTableName(type); - stmt.execute(String.format(createArtifactInstancesTableTemplate, instance_type_dbname, instance_type_dbname)); + // use the correct create table template, based on whether the attribute type represents an account or not. + String createTableTemplate = (CentralRepoDbUtil.correlationAttribHasAnAccount(type)) + ? createAccountInstancesTableTemplate + : createArtifactInstancesTableTemplate; + + stmt.execute(String.format(createTableTemplate, instance_type_dbname, instance_type_dbname)); + stmt.execute(String.format(instancesCaseIdIdx, instance_type_dbname, instance_type_dbname)); stmt.execute(String.format(instancesDatasourceIdIdx, instance_type_dbname, instance_type_dbname)); stmt.execute(String.format(instancesValueIdx, instance_type_dbname, instance_type_dbname)); @@ -193,7 +200,7 @@ public class RdbmsCentralRepoFactory { result = CentralRepoDbUtil.insertDefaultCorrelationTypes(conn) && CentralRepoDbUtil.insertDefaultOrganization(conn) && - insertDefaultAccountsTablesContent(conn); + RdbmsCentralRepoFactory.insertDefaultAccountsTablesContent(conn, selectedPlatform ); // @TODO: uncomment when ready to create/populate persona tables // && insertDefaultPersonaTablesContent(conn); @@ -357,7 +364,7 @@ public class RdbmsCentralRepoFactory { + ")"; } /** - * Get the template String for creating a new _instances table in a Sqlite + * Get the template String for creating a new _instances table for non account artifacts in * central repository. %s will exist in the template where the name of the * new table will be added. * @@ -365,6 +372,31 @@ public class RdbmsCentralRepoFactory { */ static String getCreateArtifactInstancesTableTemplate(CentralRepoPlatforms selectedPlatform) { // Each "%s" will be replaced with the relevant TYPE_instances table name. + + return "CREATE TABLE IF NOT EXISTS %s (" + + getNumericPrimaryKeyClause("id", selectedPlatform) + + "case_id integer NOT NULL," + + "data_source_id integer NOT NULL," + + "value text NOT NULL," + + "file_path text NOT NULL," + + "known_status integer NOT NULL," + + "comment text," + + "file_obj_id " + getBigIntType(selectedPlatform) + " ," + + "CONSTRAINT %s_multi_unique UNIQUE(data_source_id, value, file_path)" + getOnConflictIgnoreClause(selectedPlatform) + "," + + "foreign key (case_id) references cases(id) ON UPDATE SET NULL ON DELETE SET NULL," + + "foreign key (data_source_id) references data_sources(id) ON UPDATE SET NULL ON DELETE SET NULL)"; + } + + /** + * Get the template String for creating a new _instances table for Accounts in + * central repository. %s will exist in the template where the name of the + * new table will be added. + * + * @return a String which is a template for creating a _instances table + */ + static String getCreateAccountInstancesTableTemplate(CentralRepoPlatforms selectedPlatform) { + // Each "%s" will be replaced with the relevant TYPE_instances table name. + return "CREATE TABLE IF NOT EXISTS %s (" + getNumericPrimaryKeyClause("id", selectedPlatform) + "case_id integer NOT NULL," @@ -380,7 +412,7 @@ public class RdbmsCentralRepoFactory { + "foreign key (case_id) references cases(id) ON UPDATE SET NULL ON DELETE SET NULL," + "foreign key (data_source_id) references data_sources(id) ON UPDATE SET NULL ON DELETE SET NULL)"; } - + /** * Get the statement String for creating a new data_sources table in a * Sqlite central repository. @@ -494,6 +526,7 @@ public class RdbmsCentralRepoFactory { * on the selected CR platform/RDMBS. * * @param pkName name of primary key. + * @param selectedPlatform The selected platform. * * @return SQL clause to be used in a Create table statement */ @@ -766,37 +799,11 @@ public class RdbmsCentralRepoFactory { } - /** - * Inserts the default content in accounts related tables. - * - * @param conn Database connection to use. - * - * @return True if success, false otherwise. - */ - private boolean insertDefaultAccountsTablesContent(Connection conn) { - - try (Statement stmt = conn.createStatement()) { - // Populate the account_types table - for (Account.Type type : Account.Type.PREDEFINED_ACCOUNT_TYPES) { - int correlationTypeId = getCorrelationTypeIdForAccountType(conn, type); - if (correlationTypeId > 0) { - String sqlString = String.format("INSERT INTO account_types (type_name, display_name, correlation_type_id) VALUES ('%s', '%s', %d)" + getOnConflictDoNothingClause(selectedPlatform), - type.getTypeName(), type.getDisplayName(), correlationTypeId); - stmt.execute(sqlString); - } - } - } catch (SQLException ex) { - LOGGER.log(Level.SEVERE, String.format("Failed to populate default data in Accounts tables."), ex); - return false; - } - - return true; - } - /** * Inserts the default content in persona related tables. * * @param conn Database connection to use. + * @param selectedPlatform The selected platform. * * @return True if success, false otherwise. */ @@ -838,11 +845,13 @@ public class RdbmsCentralRepoFactory { // Populate the account_types table for (Account.Type type : Account.Type.PREDEFINED_ACCOUNT_TYPES) { - int correlationTypeId = getCorrelationTypeIdForAccountType(conn, type); - if (correlationTypeId > 0) { - String sqlString = String.format("INSERT INTO account_types (type_name, display_name, correlation_type_id) VALUES ('%s', '%s', %d)" + getOnConflictDoNothingClause(selectedPlatform), - type.getTypeName(), type.getDisplayName(), correlationTypeId); - stmt.execute(sqlString); + if (type != Account.Type.DEVICE) { + int correlationTypeId = getCorrelationTypeIdForAccountType(conn, type); + if (correlationTypeId > 0) { + String sqlString = String.format("INSERT INTO account_types (type_name, display_name, correlation_type_id) VALUES ('%s', '%s', %d)" + getOnConflictDoNothingClause(selectedPlatform), + type.getTypeName(), type.getDisplayName(), correlationTypeId); + stmt.execute(sqlString); + } } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java index 9f888e43f2..da58e8936b 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/eventlisteners/Installer.java @@ -39,16 +39,6 @@ import org.sleuthkit.autopsy.coreutils.Version; * central repository, sets up a default, single-user SQLite central repository * if no central repository is configured, and updates the central repository * schema as required. - * - * TODO (Jira-6108): At first glance, this package seems to have become a rather - * strange package for the "package installer" for the CR to reside in. The - * org.sleuthkit.autopsy.centralrepository package would seem to be more - * appropriate with so much going on. However, having a central repository - * schema update occur in a "package installer" with no user feedback is not - * optimal. Furthermore, for a multi-user (collaborative) installation, a schema - * update should be done in a more controlled way by acquiring an exclusive - * coordination service lock and requiring shared locks to be acquired by nodes - * with open cases. */ public class Installer extends ModuleInstall { @@ -84,9 +74,8 @@ public class Installer extends ModuleInstall { /* * Adds/removes application event listeners responsible for adding data to - * the central repository, sets up a default, single-user SQLite central - * repository if no central repository is configured, and updates the - * central repository schema as required. + * the central repository and sets up a default, single-user SQLite central + * repository if no central repository is configured. * * Called by the registered Installer for the Autopsy-Core module located in * the org.sleuthkit.autopsy.core package when the already installed @@ -105,8 +94,6 @@ public class Installer extends ModuleInstall { if (Version.getBuildType() == Version.Type.RELEASE) { setupDefaultCentralRepository(); } - - updateCentralRepoSchema(); } /** @@ -198,20 +185,6 @@ public class Installer extends ModuleInstall { manager.setupDefaultSqliteDb(); } - /** - * Update the central repository schema. - */ - private void updateCentralRepoSchema() { - try { - CentralRepoDbManager.upgradeDatabase(); - } catch (CentralRepoException ex) { - logger.log(Level.SEVERE, "An error occurred updating the central repository schema", ex); - if (RuntimeProperties.runningWithGUI()) { - doMessageBoxIfRunningInGUI(ex); - } - } - } - /** * Display a central repository exception in a message box if running with a * GUI. diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties-MERGED index 9ea90a452a..395330b4db 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/Bundle.properties-MERGED @@ -39,7 +39,6 @@ GlobalSettingsPanel.onMultiUserChange.disabledMu.title=Central Repository Change GlobalSettingsPanel.onMultiUserChange.enable.description=Do you want to update the Central Repository to use this PostgreSQL database? GlobalSettingsPanel.onMultiUserChange.enable.description2=The Central Repository stores hash values and accounts from past cases. GlobalSettingsPanel.onMultiUserChange.enable.title=Use with Central Repository? -GlobalSettingsPanel.updateFailed.title=Central repository disabled GlobalSettingsPanel.validationErrMsg.ingestRunning=You cannot change settings while ingest is running. GlobalSettingsPanel.validationerrMsg.mustConfigure=Configure the database to enable this module. ManageCasesDialog.title.text=Manage Cases diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.java index 3b047b0e10..4c70d6aa5c 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.java @@ -24,6 +24,7 @@ import java.awt.Cursor; import java.awt.HeadlessException; import java.io.File; import java.io.IOException; +import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -31,14 +32,15 @@ import java.util.logging.Level; import javax.swing.JDialog; import javax.swing.JFileChooser; import javax.swing.JFrame; +import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JOptionPane; import javax.swing.JTextField; +import javax.swing.ListCellRenderer; import javax.swing.SwingUtilities; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.filechooser.FileFilter; -import javax.swing.plaf.basic.BasicComboBoxRenderer; import org.netbeans.spi.options.OptionsPanelController; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; @@ -65,18 +67,18 @@ public class EamDbSettingsDialog extends JDialog { /** * This class handles displaying and rendering drop down menu for database choices in central repo. */ - private class DbChoiceRenderer extends BasicComboBoxRenderer { + private class DbChoiceRenderer extends JLabel implements ListCellRenderer, Serializable { private static final long serialVersionUID = 1L; - public Component getListCellRendererComponent(JList list, Object value, + @Override + public Component getListCellRendererComponent( + JList list, CentralRepoDbChoice value, int index, boolean isSelected, boolean cellHasFocus) { - CentralRepoDbChoice item = (CentralRepoDbChoice) value; - // disable cell if it is the db connection from multi user settings // and that option is not enabled in multi user settings - setText(item.getTitle()); - setEnabled(isDbChoiceSelectable(item)); + setText(value.getTitle()); + setEnabled(isDbChoiceSelectable(value)); return this; } } @@ -135,7 +137,7 @@ public class EamDbSettingsDialog extends JDialog { valid(); display(); } - + private void setupDbChoice(CentralRepoDbChoice initialMenuItem) { // setup initially selected item @@ -144,10 +146,8 @@ public class EamDbSettingsDialog extends JDialog { manager.getSelectedDbChoice() : CentralRepoDbChoice.DB_CHOICES[0] : initialMenuItem; - - // set the renderer so item is unselectable if inappropriate + cbDatabaseType.setRenderer(DB_CHOICE_RENDERER); - changeDbSelection(toSelect); } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java index 78b2e9ca87..bdf1839526 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/GlobalSettingsPanel.java @@ -50,20 +50,18 @@ import java.util.logging.Level; */ @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel implements OptionsPanel { - + private static final long serialVersionUID = 1L; private static final Logger logger = Logger.getLogger(GlobalSettingsPanel.class.getName()); private static final Set INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.STARTED, IngestManager.IngestJobEvent.CANCELLED, IngestManager.IngestJobEvent.COMPLETED); private final IngestJobEventPropertyChangeListener ingestJobEventListener; - - - + /** * Creates new form EamOptionsPanel */ public GlobalSettingsPanel() { ingestJobEventListener = new IngestJobEventPropertyChangeListener(); - + // listen for change events in currently saved choice CentralRepoDbManager.addPropertyChangeListener((PropertyChangeEvent evt) -> ingestStateUpdated(Case.isCaseOpen())); initComponents(); @@ -74,8 +72,7 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i ingestStateUpdated(evt.getNewValue() != null); }); } - - + private void customizeComponents() { setName(NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.pnCorrelationProperties.border.title")); } @@ -85,39 +82,36 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i ingestStateUpdated(Case.isCaseOpen()); } - private void updateDatabase() { - updateDatabase(this); - } - /** - * This method invokes central repository database choice selection as well as input for necessary configuration. - * @param parent The parent component for displaying dialogs. - * @param initialSelection If non-null, the menu item will be set to this choice; if null, - * the currently selected db choice will be selected. - * @return True if there was a change. + * This method invokes central repository database choice selection as well + * as input for necessary configuration. + * + * @param parent The parent component for displaying dialogs. + * @param initialSelection If non-null, the menu item will be set to this + * choice; if null, the currently selected db choice + * will be selected. + * + * @return True if there was a change. */ private static boolean invokeCrChoice(Component parent, CentralRepoDbChoice initialSelection) { - EamDbSettingsDialog dialog = (initialSelection != null) ? - new EamDbSettingsDialog(initialSelection) : - new EamDbSettingsDialog(); - - if (dialog.wasConfigurationChanged()) { - updateDatabase(parent); - return true; - } - - return false; + EamDbSettingsDialog dialog = (initialSelection != null) + ? new EamDbSettingsDialog(initialSelection) + : new EamDbSettingsDialog(); + return dialog.wasConfigurationChanged(); } - - + /** - * When multi user settings are updated, this function triggers pertinent updates for central repository. - * NOTE: If multi user settings were previously enabled and multi user settings are currently selected, this function assumes - * there is a change in the postgres connectivity. - * - * @param parent The swing component that serves as a parent for dialogs that may arise. - * @param muPreviouslySelected If multi user settings were previously enabled. - * @param muCurrentlySelected If multi user settings are currently enabled as of most recent change. + * When multi user settings are updated, this function triggers pertinent + * updates for central repository. NOTE: If multi user settings were + * previously enabled and multi user settings are currently selected, this + * function assumes there is a change in the postgres connectivity. + * + * @param parent The swing component that serves as a parent + * for dialogs that may arise. + * @param muPreviouslySelected If multi user settings were previously + * enabled. + * @param muCurrentlySelected If multi user settings are currently enabled + * as of most recent change. */ @NbBundle.Messages({ "GlobalSettingsPanel.onMultiUserChange.enable.title=Use with Central Repository?", @@ -128,33 +122,31 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i boolean crEnabled = CentralRepoDbUtil.allowUseOfCentralRepository(); boolean crMultiUser = CentralRepoDbManager.getSavedDbChoice() == CentralRepoDbChoice.POSTGRESQL_MULTIUSER; boolean crDisabledDueToFailure = CentralRepoDbManager.isDisabledDueToFailure(); - + if (!muPreviouslySelected && muCurrentlySelected) { SwingUtilities.invokeLater(() -> { - if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(parent, - "" + - "
" + - "

" + NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.onMultiUserChange.enable.description") + "

" + - "

" + NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.onMultiUserChange.enable.description2") + "

" + - "
" + - "", - NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.onMultiUserChange.enable.title"), - JOptionPane.YES_NO_OPTION)) { - - // setup database for CR - CentralRepoDbUtil.setUseCentralRepo(true); - CentralRepoDbManager.saveDbChoice(CentralRepoDbChoice.POSTGRESQL_MULTIUSER); - handleDbChange(parent); - } + if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(parent, + "" + + "
" + + "

" + NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.onMultiUserChange.enable.description") + "

" + + "

" + NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.onMultiUserChange.enable.description2") + "

" + + "
" + + "", + NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.onMultiUserChange.enable.title"), + JOptionPane.YES_NO_OPTION)) { + + // setup database for CR + CentralRepoDbUtil.setUseCentralRepo(true); + CentralRepoDbManager.saveDbChoice(CentralRepoDbChoice.POSTGRESQL_MULTIUSER); + handleDbChange(parent); + } }); - } - // moving from selected to not selected && 'PostgreSQL using multi-user settings' is selected + } // moving from selected to not selected && 'PostgreSQL using multi-user settings' is selected else if (muPreviouslySelected && !muCurrentlySelected && crEnabled && crMultiUser) { SwingUtilities.invokeLater(() -> { askForCentralRepoDbChoice(parent); }); - } - // changing multi-user settings connection && 'PostgreSQL using multi-user settings' is selected && + } // changing multi-user settings connection && 'PostgreSQL using multi-user settings' is selected && // central repo either enabled or was disabled due to error else if (muPreviouslySelected && muCurrentlySelected && crMultiUser && (crEnabled || crDisabledDueToFailure)) { // test databse for CR change @@ -163,10 +155,12 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i } } - /** - * This method is called when a user must select a new database other than using database from multi user settings. - * @param parent The parent component to use for displaying dialogs in reference. + * This method is called when a user must select a new database other than + * using database from multi user settings. + * + * @param parent The parent component to use for displaying dialogs in + * reference. */ @NbBundle.Messages({ "GlobalSettingsPanel.onMultiUserChange.disabledMu.title=Central Repository Change Necessary", @@ -177,21 +171,21 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i // disable central repository until user makes choice CentralRepoDbUtil.setUseCentralRepo(false); CentralRepoDbManager.saveDbChoice(CentralRepoDbChoice.DISABLED, false); - + Object[] options = { "Use SQLite", "Configure PostgreSQL", "Disable Central Repository" }; - + int result = JOptionPane.showOptionDialog( parent, - "" + - "
" + - "

" + NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.onMultiUserChange.disabledMu.description") + "

" + - "

" + NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.onMultiUserChange.disabledMu.description2") + "

" + - "
" + - "", + "" + + "
" + + "

" + NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.onMultiUserChange.disabledMu.description") + "

" + + "

" + NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.onMultiUserChange.disabledMu.description2") + "

" + + "
" + + "", NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.onMultiUserChange.disabledMu.title"), JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE, @@ -199,51 +193,21 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i options, options[0] ); - + if (JOptionPane.YES_OPTION == result) { invokeCrChoice(parent, CentralRepoDbChoice.SQLITE); - } - else if (JOptionPane.NO_OPTION == result) { + } else if (JOptionPane.NO_OPTION == result) { invokeCrChoice(parent, CentralRepoDbChoice.POSTGRESQL_CUSTOM); } } - - + private static void handleDbChange(Component parent) { SwingUtilities.invokeLater(() -> { - boolean successful = EamDbSettingsDialog.testStatusAndCreate(parent, new CentralRepoDbManager()); - if (successful) { - updateDatabase(parent); - } - else { - // disable central repository due to error + if (!EamDbSettingsDialog.testStatusAndCreate(parent, new CentralRepoDbManager())) { CentralRepoDbManager.disableDueToFailure(); } }); } - - - @Messages({"GlobalSettingsPanel.updateFailed.title=Central repository disabled"}) - private static void updateDatabase(Component parent) { - if (CentralRepoDbChoice.DISABLED.equals(CentralRepoDbManager.getSavedDbChoice())) { - return; - } - parent.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - - try { - CentralRepoDbManager.upgradeDatabase(); - parent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - } catch (CentralRepoException ex) { - parent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - JOptionPane.showMessageDialog(parent, - ex.getUserMessage(), - NbBundle.getMessage(GlobalSettingsPanel.class, - "GlobalSettingsPanel.updateFailed.title"), - JOptionPane.WARNING_MESSAGE); - } finally { - parent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - } - } /** * This method is called from within the constructor to initialize the form. @@ -589,17 +553,15 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i private void cbUseCentralRepoActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cbUseCentralRepoActionPerformed //if saved setting is disabled checkbox should be disabled already store(); - + // if moving to using CR, multi-user mode is disabled and selection is multiuser settings, set to disabled - if (cbUseCentralRepo.isSelected() && - !CentralRepoDbManager.isPostgresMultiuserAllowed() && - CentralRepoDbManager.getSavedDbChoice() == CentralRepoDbChoice.POSTGRESQL_MULTIUSER) { - + if (cbUseCentralRepo.isSelected() + && !CentralRepoDbManager.isPostgresMultiuserAllowed() + && CentralRepoDbManager.getSavedDbChoice() == CentralRepoDbChoice.POSTGRESQL_MULTIUSER) { + CentralRepoDbManager.saveDbChoice(CentralRepoDbChoice.DISABLED); } - - updateDatabase(); load(); this.ingestStateUpdated(Case.isCaseOpen()); firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); @@ -620,20 +582,17 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i lbDbNameValue.setText(""); lbDbLocationValue.setText(""); tbOops.setText(Bundle.GlobalSettingsPanel_validationerrMsg_mustConfigure()); - } - else { + } else { enableButtonSubComponents(cbUseCentralRepo.isSelected()); if (selectedDb == CentralRepoPlatforms.POSTGRESQL) { try { - PostgresCentralRepoSettings dbSettingsPg = new PostgresCentralRepoSettings(); + PostgresCentralRepoSettings dbSettingsPg = new PostgresCentralRepoSettings(); lbDbNameValue.setText(dbSettingsPg.getDbName()); lbDbLocationValue.setText(dbSettingsPg.getHost()); - } - catch (CentralRepoException e) { + } catch (CentralRepoException e) { logger.log(Level.WARNING, "Unable to load settings into global panel for postgres settings", e); } - } - else if (selectedDb == CentralRepoPlatforms.SQLITE) { + } else if (selectedDb == CentralRepoPlatforms.SQLITE) { SqliteCentralRepoSettings dbSettingsSqlite = new SqliteCentralRepoSettings(); lbDbNameValue.setText(dbSettingsSqlite.getDbName()); lbDbLocationValue.setText(dbSettingsSqlite.getDbDirectory()); @@ -647,7 +606,8 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i } /** - * This method validates that the dialog/panel is filled out correctly for our usage. + * This method validates that the dialog/panel is filled out correctly for + * our usage. * * @return True if it is okay, false otherwise. */ @@ -731,9 +691,9 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i enableButtonSubComponents(cbUseCentralRepo.isSelected()); } else { load(); - enableDatabaseConfigureButton(cbUseCentralRepo.isSelected() && !caseIsOpen); } + enableDatabaseConfigureButton(cbUseCentralRepo.isSelected() && !caseIsOpen); } /** diff --git a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributeCaseSearchResults.java b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributeCaseSearchResults.java index e096638434..6b1fe1ab72 100644 --- a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributeCaseSearchResults.java +++ b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributeCaseSearchResults.java @@ -123,7 +123,7 @@ final public class CommonAttributeCaseSearchResults { if (currentCaseDataSourceMap == null) { //there are no results return filteredCaseNameToDataSourcesTree; } - CorrelationAttributeInstance.Type attributeType = CentralRepository.getInstance().getCorrelationTypes() + CorrelationAttributeInstance.Type attributeType = CentralRepository.getInstance().getDefinedCorrelationTypes() .stream() .filter(filterType -> filterType.getId() == resultTypeId) .findFirst().get(); diff --git a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributeCountSearchResults.java b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributeCountSearchResults.java index 85923e53b6..80a65b2835 100644 --- a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributeCountSearchResults.java +++ b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributeCountSearchResults.java @@ -129,7 +129,7 @@ final public class CommonAttributeCountSearchResults { } CentralRepository eamDb = CentralRepository.getInstance(); - CorrelationAttributeInstance.Type attributeType = eamDb.getCorrelationTypes() + CorrelationAttributeInstance.Type attributeType = eamDb.getDefinedCorrelationTypes() .stream() .filter(filterType -> filterType.getId() == this.resultTypeId) .findFirst().get(); diff --git a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributePanel.java b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributePanel.java index 43d834e39d..5e7aa3adb4 100644 --- a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributePanel.java +++ b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/CommonAttributePanel.java @@ -255,7 +255,7 @@ final class CommonAttributePanel extends javax.swing.JDialog implements Observer filterByDocuments = interCasePanel.documentsCheckboxIsSelected(); } if (corType == null) { - corType = CentralRepository.getInstance().getCorrelationTypes().get(0); + corType = CentralRepository.getInstance().getDefinedCorrelationTypes().get(0); } if (caseId == InterCasePanel.NO_CASE_SELECTED) { builder = new AllInterCaseCommonAttributeSearcher(filterByMedia, filterByDocuments, corType, percentageThreshold); @@ -366,7 +366,7 @@ final class CommonAttributePanel extends javax.swing.JDialog implements Observer filterByDocuments = interCasePanel.documentsCheckboxIsSelected(); } if (corType == null) { - corType = CentralRepository.getInstance().getCorrelationTypes().get(0); + corType = CentralRepository.getInstance().getDefinedCorrelationTypes().get(0); } if (caseId == InterCasePanel.NO_CASE_SELECTED) { builder = new AllInterCaseCommonAttributeSearcher(filterByMedia, filterByDocuments, corType, percentageThreshold); diff --git a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/InterCasePanel.java b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/InterCasePanel.java index a2c1c01529..f36f8ab520 100644 --- a/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/InterCasePanel.java +++ b/Core/src/org/sleuthkit/autopsy/commonpropertiessearch/InterCasePanel.java @@ -118,7 +118,7 @@ public final class InterCasePanel extends javax.swing.JPanel { void setupCorrelationTypeFilter() { this.correlationTypeFilters = new HashMap<>(); try { - List types = CentralRepository.getInstance().getCorrelationTypes(); + List types = CentralRepository.getInstance().getDefinedCorrelationTypes(); for (CorrelationAttributeInstance.Type type : types) { correlationTypeFilters.put(type.getDisplayName(), type); this.correlationTypeComboBox.addItem(type.getDisplayName()); diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/AccountSummary.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/AccountSummary.java index 0888a105a3..5401f1a839 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/AccountSummary.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/AccountSummary.java @@ -33,6 +33,7 @@ import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.blackboardutils.attributes.MessageAttachments.FileAttachment; import org.sleuthkit.datamodel.blackboardutils.attributes.MessageAttachments; +import org.sleuthkit.datamodel.CommunicationsUtils; /** * @@ -97,20 +98,27 @@ class AccountSummary { boolean isReference = false; - for (BlackboardAttribute attribute: attributes) { + for (BlackboardAttribute attribute : attributes) { + String attributeTypeName = attribute.getAttributeType().getTypeName(); String attributeValue = attribute.getValueString(); - - if (attributeTypeName.contains("PHONE")) { - attributeValue = RelationshipsNodeUtilities.normalizePhoneNum(attributeValue); - } else if (attributeTypeName.contains("EMAIL")) { - attributeValue = RelationshipsNodeUtilities.normalizeEmailAddress(attributeValue); - } - - if ( typeSpecificID.equals(attributeValue) ) { - isReference = true; - break; + try { + if (attributeTypeName.contains("PHONE")) { + attributeValue = CommunicationsUtils.normalizePhoneNum(attributeValue); + } else if (attributeTypeName.contains("EMAIL")) { + attributeValue = CommunicationsUtils.normalizeEmailAddress(attributeValue); + } + + if (typeSpecificID.equals(attributeValue)) { + isReference = true; + break; + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, String.format("Exception thrown " + + "in trying to normalize attribute value: %s", + attributeValue), ex); //NON-NLS } + } if (isReference) { referenceCnt++; diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/CorrelationCaseChildNodeFactory.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/CorrelationCaseChildNodeFactory.java index 49617c9dfb..67e0366a9b 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/CorrelationCaseChildNodeFactory.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/CorrelationCaseChildNodeFactory.java @@ -111,7 +111,7 @@ final class CorrelationCaseChildNodeFactory extends ChildFactory(); - List correcationTypeList = CentralRepository.getInstance().getCorrelationTypes(); + List correcationTypeList = CentralRepository.getInstance().getDefinedCorrelationTypes(); correcationTypeList.forEach((type) -> { correlationTypeMap.put(type.getId(), type); }); diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipsNodeUtilities.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipsNodeUtilities.java index 252b4ee1ff..be150dd590 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipsNodeUtilities.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/RelationshipsNodeUtilities.java @@ -69,42 +69,4 @@ final class RelationshipsNodeUtilities { } } - /** - * Normalize the phone number by removing all non numeric characters, except - * for leading +. - * - * This function copied from CommunicationManager. - * - * @param phoneNum The phone number to normalize - * - * @return The normalized phone number. - */ - static String normalizePhoneNum(String phoneNum) { - String normailzedPhoneNum = phoneNum.replaceAll("\\D", ""); - - if (phoneNum.startsWith("+")) { - normailzedPhoneNum = "+" + normailzedPhoneNum; - } - - if (normailzedPhoneNum.isEmpty()) { - normailzedPhoneNum = phoneNum; - } - - return normailzedPhoneNum; - } - - /** - * Normalize the given email address by converting it to lowercase. - * - * This function copied from CommunicationManager. - * - * @param emailAddress The email address tot normalize - * - * @return The normalized email address. - */ - static String normalizeEmailAddress(String emailAddress) { - String normailzedEmailAddr = emailAddress.toLowerCase(); - - return normailzedEmailAddr; - } } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED index 737b6900d4..c0a6f73aaf 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED @@ -41,6 +41,7 @@ MediaFileViewer.AccessibleContext.accessibleDescription= MediaFileViewer.title=Media MediaFileViewer.toolTip=Displays supported multimedia files (images, videos, audio) MediaPlayerPanel.noSupport=File not supported. +MediaPlayerPanel.playbackDisabled=A problem was encountered with the video and audio playback service. Video and audio playback will be disabled for the remainder of the session. MediaPlayerPanel.timeFormat=%02d:%02d:%02d MediaPlayerPanel.unknownTime=Unknown MediaViewImagePanel.createTagOption=Create @@ -168,7 +169,7 @@ MediaPlayerPanel.playBackSpeedLabel.text=Speed: SQLiteViewer.readTable.errorText=Error getting rows for table: {0} # {0} - tableName SQLiteViewer.selectTable.errorText=Error getting row count for table: {0} -TextTranslatableComponent.setPanelContent.onSetContentError=Unable to display text at this time. -TextTranslatableComponent.setTranslated.onTranslateError=Unable to translate text at this time. TranslatablePanel.comboBoxOption.originalText=Original Text TranslatablePanel.comboBoxOption.translatedText=Translated Text +# {0} - exception message +TranslatablePanel.onSetContentError.text=There was an error displaying the text: {0} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaFileViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaFileViewer.java index 56dd8e9ebe..9decc79175 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaFileViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaFileViewer.java @@ -194,6 +194,6 @@ class MediaFileViewer extends javax.swing.JPanel implements FileTypeViewer { @Override public boolean isSupported(AbstractFile file){ - return true; + return mediaPlayerPanel.isSupported(file) || imagePanel.isSupported(file); } } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form index 0b00c43d8a..793e31830b 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.form @@ -63,7 +63,7 @@ - + @@ -129,12 +129,6 @@ - - - - - - diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java index 53cf9e8bf4..40122acea7 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MediaPlayerPanel.java @@ -74,7 +74,9 @@ import org.freedesktop.gstreamer.Format; import org.freedesktop.gstreamer.GstException; import org.freedesktop.gstreamer.event.SeekFlags; import org.freedesktop.gstreamer.event.SeekType; -import org.sleuthkit.autopsy.coreutils.PlatformUtil; +import org.sleuthkit.autopsy.contentviewers.utils.GstLoader; +import org.sleuthkit.autopsy.contentviewers.utils.GstLoader.GstStatus; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; /** * This is a video player that is part of the Media View layered pane. It uses @@ -213,6 +215,8 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie //and the TrackListener on the slider itself. private final Semaphore sliderLock; + private static volatile boolean IS_GST_ENABLED = true; + /** * Creates a new MediaPlayerPanel */ @@ -222,18 +226,10 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie //True for fairness. In other words, //acquire() calls are processed in order of invocation. sliderLock = new Semaphore(1, true); - - /** - * See JIRA-5888 for details. Initializing gstreamer here is more stable - * on Windows. - */ - if (PlatformUtil.isWindowsOS()) { - Gst.init(); - } } private void customizeComponents() { - progressSlider.setEnabled(false); // disable slider; enable after user plays vid + enableComponents(false); progressSlider.setMinimum(0); progressSlider.setMaximum(PROGRESS_SLIDER_SIZE); progressSlider.setValue(0); @@ -390,13 +386,16 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie } timer.stop(); if (gstPlayBin != null) { - gstPlayBin.stop(); - gstPlayBin.getBus().disconnect(endOfStreamListener); - gstPlayBin.getBus().disconnect(stateChangeListener); - gstPlayBin.getBus().disconnect(errorListener); - gstPlayBin.dispose(); - fxAppSink.clear(); - gstPlayBin = null; + Gst.getExecutor().submit(() -> { + gstPlayBin.stop(); + gstPlayBin.getBus().disconnect(endOfStreamListener); + gstPlayBin.getBus().disconnect(stateChangeListener); + gstPlayBin.getBus().disconnect(errorListener); + gstPlayBin.getBus().dispose(); + gstPlayBin.dispose(); + fxAppSink.clear(); + gstPlayBin = null; + }); } videoPanel.removeAll(); resetComponents(); @@ -425,6 +424,10 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie @Override public boolean isSupported(AbstractFile file) { + if (!IS_GST_ENABLED) { + return false; + } + String extension = file.getNameExtension(); /** * Although it seems too restrictive, requiring both a supported @@ -542,6 +545,11 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie /* * Initialize the playback components if the extraction was successful. */ + @NbBundle.Messages({ + "MediaPlayerPanel.playbackDisabled=A problem was encountered with" + + " the video and audio playback service. Video and audio " + + "playback will be disabled for the remainder of the session." + }) @Override protected void done() { try { @@ -551,44 +559,49 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie return; } - // Initialize Gstreamer. It is safe to call this for every file. - // It was moved here from the constructor because having it happen - // earlier resulted in conflicts on Linux. See JIRA-5888. - if (!PlatformUtil.isWindowsOS()) { - Gst.init(); + GstStatus loadStatus = GstLoader.tryLoad(); + if (loadStatus == GstStatus.FAILURE) { + MessageNotifyUtil.Message.error(Bundle.MediaPlayerPanel_playbackDisabled()); + + // This will disable the panel for future use. + IS_GST_ENABLED = false; + return; } - //Video is ready for playback. Create new components - gstPlayBin = new PlayBin("VideoPlayer", tempFile.toURI()); - //Configure event handling - if (gstPlayBin != null) { + Gst.getExecutor().submit(() -> { + //Video is ready for playback. Create new components + gstPlayBin = new PlayBin("VideoPlayer", tempFile.toURI()); + //Configure event handling Bus playBinBus = gstPlayBin.getBus(); playBinBus.connect(endOfStreamListener); playBinBus.connect(stateChangeListener); playBinBus.connect(errorListener); - } - if (this.isCancelled()) { - return; - } + if (this.isCancelled()) { + return; + } - JFXPanel fxPanel = new JFXPanel(); - videoPanel.removeAll(); - videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS)); - videoPanel.add(fxPanel); - fxAppSink = new JavaFxAppSink("JavaFxAppSink", fxPanel); - if (gstPlayBin != null) { - gstPlayBin.setVideoSink(fxAppSink); - } - if (this.isCancelled()) { - return; - } - if (gstPlayBin != null) { - gstPlayBin.setVolume((audioSlider.getValue() * 2.0) / 100.0); - gstPlayBin.pause(); - } - timer.start(); - enableComponents(true); + JFXPanel fxPanel = new JFXPanel(); + videoPanel.removeAll(); + videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS)); + videoPanel.add(fxPanel); + fxAppSink = new JavaFxAppSink("JavaFxAppSink", fxPanel); + if (gstPlayBin != null) { + gstPlayBin.setVideoSink(fxAppSink); + } + if (this.isCancelled()) { + return; + } + if (gstPlayBin != null) { + gstPlayBin.setVolume((audioSlider.getValue() * 2.0) / 100.0); + gstPlayBin.pause(); + } + + timer.start(); + SwingUtilities.invokeLater(() -> { + enableComponents(true); + }); + }); } catch (CancellationException ex) { logger.log(Level.INFO, "Media buffering was canceled."); //NON-NLS } catch (InterruptedException ex) { @@ -607,23 +620,29 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie @Override public void actionPerformed(ActionEvent e) { if (!progressSlider.getValueIsAdjusting() && gstPlayBin != null) { - sliderLock.acquireUninterruptibly(); - long position = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); - long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS); - /** - * Duration may not be known until there is video data in the - * pipeline. We start this updater when data-flow has just been - * initiated so buffering may still be in progress. - */ - if (duration >= 0 && position >= 0) { - double relativePosition = (double) position / duration; - progressSlider.setValue((int) (relativePosition * PROGRESS_SLIDER_SIZE)); - } + Gst.getExecutor().submit(() -> { + try { + sliderLock.acquireUninterruptibly(); + long position = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); + long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS); + /** + * Duration may not be known until there is video data + * in the pipeline. We start this updater when data-flow + * has just been initiated so buffering may still be in + * progress. + */ + if (duration >= 0 && position >= 0) { + double relativePosition = (double) position / duration; + progressSlider.setValue((int) (relativePosition * PROGRESS_SLIDER_SIZE)); + } - SwingUtilities.invokeLater(() -> { - updateTimeLabel(position, duration); + SwingUtilities.invokeLater(() -> { + updateTimeLabel(position, duration); + }); + } finally { + sliderLock.release(); + } }); - sliderLock.release(); } } } @@ -643,7 +662,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie * thumb at the given width and height. It also paints the track blue as * the thumb progresses. * - * @param slider JSlider component + * @param slider JSlider component * @param thumbDimension */ public CircularJSliderUI(JSlider slider, Dimension thumbDimension) { @@ -991,96 +1010,104 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie }// //GEN-END:initComponents private void rewindButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_rewindButtonActionPerformed - if (gstPlayBin != null) { - long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); - //Skip 30 seconds. - long rewindDelta = TimeUnit.NANOSECONDS.convert(SKIP_IN_SECONDS, TimeUnit.SECONDS); - //Ensure new video position is within bounds - long newTime = Math.max(currentTime - rewindDelta, 0); - double playBackRate = getPlayBackRate(); - gstPlayBin.seek(playBackRate, - Format.TIME, - //FLUSH - flushes the pipeline - //ACCURATE - video will seek exactly to the position requested - EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE), - //Set the start position to newTime - SeekType.SET, newTime, - //Do nothing for the end position - SeekType.NONE, -1); - } - }//GEN-LAST:event_rewindButtonActionPerformed - - private void fastForwardButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_fastForwardButtonActionPerformed - if (gstPlayBin != null) { - long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS); - long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); - //Skip 30 seconds. - long fastForwardDelta = TimeUnit.NANOSECONDS.convert(SKIP_IN_SECONDS, TimeUnit.SECONDS); - //Don't allow skipping within 2 seconds of video ending. Skipping right to - //the end causes undefined behavior for some gstreamer plugins. - long twoSecondsInNano = TimeUnit.NANOSECONDS.convert(2, TimeUnit.SECONDS); - if ((duration - currentTime) <= twoSecondsInNano) { - return; - } - - long newTime; - if (currentTime + fastForwardDelta >= duration) { - //If there are less than 30 seconds left, only fast forward to the midpoint. - newTime = currentTime + (duration - currentTime) / 2; - } else { - newTime = currentTime + fastForwardDelta; - } - - double playBackRate = getPlayBackRate(); - gstPlayBin.seek(playBackRate, - Format.TIME, - //FLUSH - flushes the pipeline - //ACCURATE - video will seek exactly to the position requested - EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE), - //Set the start position to newTime - SeekType.SET, newTime, - //Do nothing for the end position - SeekType.NONE, -1); - } - }//GEN-LAST:event_fastForwardButtonActionPerformed - - private void playButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_playButtonActionPerformed - if (gstPlayBin != null) { - if (gstPlayBin.isPlaying()) { - gstPlayBin.pause(); - } else { - double playBackRate = getPlayBackRate(); + Gst.getExecutor().submit(() -> { + if (gstPlayBin != null) { long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); - //Set playback rate before play. + //Skip 30 seconds. + long rewindDelta = TimeUnit.NANOSECONDS.convert(SKIP_IN_SECONDS, TimeUnit.SECONDS); + //Ensure new video position is within bounds + long newTime = Math.max(currentTime - rewindDelta, 0); + double playBackRate = getPlayBackRate(); gstPlayBin.seek(playBackRate, Format.TIME, //FLUSH - flushes the pipeline //ACCURATE - video will seek exactly to the position requested EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE), //Set the start position to newTime - SeekType.SET, currentTime, + SeekType.SET, newTime, //Do nothing for the end position SeekType.NONE, -1); - gstPlayBin.play(); } - } - }//GEN-LAST:event_playButtonActionPerformed + }); + }//GEN-LAST:event_rewindButtonActionPerformed - private void playBackSpeedComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_playBackSpeedComboBoxActionPerformed - if (gstPlayBin != null) { - double playBackRate = getPlayBackRate(); - long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); - gstPlayBin.seek(playBackRate, - Format.TIME, - //FLUSH - flushes the pipeline - //ACCURATE - video will seek exactly to the position requested - EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE), - //Set the position to the currentTime, we are only adjusting the - //playback rate. - SeekType.SET, currentTime, - SeekType.NONE, 0); - } - }//GEN-LAST:event_playBackSpeedComboBoxActionPerformed + private void fastForwardButtonActionPerformed(java.awt.event.ActionEvent evt) { + Gst.getExecutor().submit(() -> { + if (gstPlayBin != null) { + long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS); + long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); + //Skip 30 seconds. + long fastForwardDelta = TimeUnit.NANOSECONDS.convert(SKIP_IN_SECONDS, TimeUnit.SECONDS); + //Don't allow skipping within 2 seconds of video ending. Skipping right to + //the end causes undefined behavior for some gstreamer plugins. + long twoSecondsInNano = TimeUnit.NANOSECONDS.convert(2, TimeUnit.SECONDS); + if ((duration - currentTime) <= twoSecondsInNano) { + return; + } + + long newTime; + if (currentTime + fastForwardDelta >= duration) { + //If there are less than 30 seconds left, only fast forward to the midpoint. + newTime = currentTime + (duration - currentTime) / 2; + } else { + newTime = currentTime + fastForwardDelta; + } + + double playBackRate = getPlayBackRate(); + gstPlayBin.seek(playBackRate, + Format.TIME, + //FLUSH - flushes the pipeline + //ACCURATE - video will seek exactly to the position requested + EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE), + //Set the start position to newTime + SeekType.SET, newTime, + //Do nothing for the end position + SeekType.NONE, -1); + } + }); + } + + private void playButtonActionPerformed(java.awt.event.ActionEvent evt) { + Gst.getExecutor().submit(() -> { + if (gstPlayBin != null) { + if (gstPlayBin.isPlaying()) { + gstPlayBin.pause(); + } else { + double playBackRate = getPlayBackRate(); + long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); + //Set playback rate before play. + gstPlayBin.seek(playBackRate, + Format.TIME, + //FLUSH - flushes the pipeline + //ACCURATE - video will seek exactly to the position requested + EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE), + //Set the start position to newTime + SeekType.SET, currentTime, + //Do nothing for the end position + SeekType.NONE, -1); + gstPlayBin.play(); + } + } + }); + } + + private void playBackSpeedComboBoxActionPerformed(java.awt.event.ActionEvent evt) { + Gst.getExecutor().submit(() -> { + if (gstPlayBin != null) { + double playBackRate = getPlayBackRate(); + long currentTime = gstPlayBin.queryPosition(TimeUnit.NANOSECONDS); + gstPlayBin.seek(playBackRate, + Format.TIME, + //FLUSH - flushes the pipeline + //ACCURATE - video will seek exactly to the position requested + EnumSet.of(SeekFlags.FLUSH, SeekFlags.ACCURATE), + //Set the position to the currentTime, we are only adjusting the + //playback rate. + SeekType.SET, currentTime, + SeekType.NONE, 0); + } + }); + } // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JLabel VolumeIcon; diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/utils/GstLoader.java b/Core/src/org/sleuthkit/autopsy/contentviewers/utils/GstLoader.java new file mode 100755 index 0000000000..b6804d488a --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/utils/GstLoader.java @@ -0,0 +1,74 @@ +/* + * 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.contentviewers.utils; + +import java.util.logging.Level; +import org.freedesktop.gstreamer.Gst; +import org.sleuthkit.autopsy.coreutils.Logger; + +/** + * A utility class that loads the gstreamer bindings. + */ +public final class GstLoader { + + private static final Logger logger = Logger.getLogger(GstLoader.class.getName()); + private static GstStatus status; + + /** + * Attempts to load the gstreamer bindings. Only one attempt will be + * performed per Autopsy process. Clients should not attempt to interact + * with the gstreamer bindings unless the load was successful. + * + * @return Status - SUCCESS or FAILURE + */ + public synchronized static GstStatus tryLoad() { + // Null is our 'unknown' status. Prior to the first call, the status + // is unknown. + if (status != null) { + return status; + } + + try { + // Setting the following property causes the GST + // Java bindings to call dispose() on the GST + // service thread instead of running it in the GST + // Native Object Reaper thread. + System.setProperty("glib.reapOnEDT", "true"); + Gst.init(); + status = GstStatus.SUCCESS; + } catch (Throwable ex) { + status = GstStatus.FAILURE; + logger.log(Level.WARNING, "Failed to load gsteamer bindings", ex); + } + + return status; + } + + /** + * The various init statuses that tryLoad can return. + */ + public enum GstStatus { + SUCCESS, FAILURE + } + + private GstLoader() { + + } +} diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/PlatformUtil.java b/Core/src/org/sleuthkit/autopsy/coreutils/PlatformUtil.java index 12415fac87..e729248b47 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/PlatformUtil.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/PlatformUtil.java @@ -69,7 +69,7 @@ public class PlatformUtil { * @return absolute path string to the install root dir */ public static String getInstallPath() { - File coreFolder = InstalledFileLocator.getDefault().locate("core", "org.sleuthkit.autopsy.core", false); //NON-NLS + File coreFolder = InstalledFileLocator.getDefault().locate("core", PlatformUtil.class.getPackage().getName(), false); //NON-NLS File rootPath = coreFolder.getParentFile().getParentFile(); return rootPath.getAbsolutePath(); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java index 1d53e46ca6..74ac603e0b 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.datamodel; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.lang.ref.WeakReference; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.EnumSet; import java.util.List; @@ -28,7 +29,6 @@ import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.stream.Collectors; -import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.openide.nodes.Sheet; @@ -66,6 +66,7 @@ import org.sleuthkit.datamodel.Tag; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; +import org.sleuthkit.autopsy.texttranslation.utils.FileNameTranslationUtil; /** * An abstract node that encapsulates AbstractFile data @@ -94,15 +95,15 @@ public abstract class AbstractAbstractFileNode extends A IngestManager.getInstance().addIngestModuleEventListener(INGEST_MODULE_EVENTS_OF_INTEREST, weakPcl); } } - + try { //See JIRA-5971 //Attempt to cache file path during construction of this UI component. this.content.getUniquePath(); } catch (TskCoreException ex) { logger.log(Level.SEVERE, String.format("Failed attempt to cache the " - + "unique path of the abstract file instance. Name: %s (objID=%d)", - this.content.getName(), this.content.getId()), ex); + + "unique path of the abstract file instance. Name: %s (objID=%d)", + this.content.getName(), this.content.getId()), ex); } if (TextTranslationService.getInstance().hasProvider() && UserPreferences.displayTranslatedFileNames()) { @@ -490,39 +491,18 @@ public abstract class AbstractAbstractFileNode extends A } /** - * Translates this nodes content name. Doesn't attempt translation if the - * name is in english or if there is now translation service available. + * Translates the name of the file this node represents. An empty string + * will be returned if the translation fails for any reason. + * + * @return The translated file name or the empty string. */ String getTranslatedFileName() { - //If already in complete English, don't translate. - if (content.getName().matches("^\\p{ASCII}+$")) { + try { + return FileNameTranslationUtil.translate(content.getName()); + } catch (NoServiceProviderException | TranslationException ex) { + logger.log(Level.WARNING, MessageFormat.format("Error translating file name (objID={0}))", content.getId()), ex); return ""; } - TextTranslationService tts = TextTranslationService.getInstance(); - if (tts.hasProvider()) { - //Seperate out the base and ext from the contents file name. - String base = FilenameUtils.getBaseName(content.getName()); - try { - String translation = tts.translate(base); - String ext = FilenameUtils.getExtension(content.getName()); - - //If we have no extension, then we shouldn't add the . - String extensionDelimiter = (ext.isEmpty()) ? "" : "."; - - //Talk directly to this nodes pcl, fire an update when the translation - //is complete. - if (!translation.isEmpty()) { - return translation + extensionDelimiter + ext; - } - } catch (NoServiceProviderException noServiceEx) { - logger.log(Level.WARNING, "Translate unsuccessful because no TextTranslator " - + "implementation was provided.", noServiceEx.getMessage()); - } catch (TranslationException noTranslationEx) { - logger.log(Level.WARNING, "Could not successfully translate file name " - + content.getName(), noTranslationEx.getMessage()); - } - } - return ""; } /** diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index b4aafc2bcc..8358dd7d34 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -76,6 +76,9 @@ import org.sleuthkit.datamodel.Tag; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; +import static org.sleuthkit.autopsy.datamodel.AbstractContentNode.NO_DESCR; +import org.sleuthkit.autopsy.texttranslation.TextTranslationService; +import org.sleuthkit.autopsy.datamodel.utils.FileNameTransTask; /** * A BlackboardArtifactNode is an AbstractNode implementation that can be used @@ -124,7 +127,8 @@ public class BlackboardArtifactNode extends AbstractContentNode> customProperties; - private final PropertyChangeListener appEventListener = new PropertyChangeListener() { + private final PropertyChangeListener listener = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { String eventType = evt.getPropertyName(); @@ -148,25 +152,19 @@ public class BlackboardArtifactNode extends AbstractContentNode(Bundle.BlackboardArtifactNode_createSheet_score_name(), Bundle.BlackboardArtifactNode_createSheet_score_displayName(), scoData.getScoreAndDescription().getRight(), scoData.getScoreAndDescription().getLeft())); + updateSheet(new NodeProperty<>( + Bundle.BlackboardArtifactNode_createSheet_score_name(), + Bundle.BlackboardArtifactNode_createSheet_score_displayName(), + scoData.getScoreAndDescription().getRight(), + scoData.getScoreAndDescription().getLeft())); } if (scoData.getComment() != null) { - updateSheet(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_comment_name(), Bundle.BlackboardArtifactNode_createSheet_comment_displayName(), NO_DESCR, scoData.getComment())); + updateSheet(new NodeProperty<>( + Bundle.BlackboardArtifactNode_createSheet_comment_name(), + Bundle.BlackboardArtifactNode_createSheet_comment_displayName(), + NO_DESCR, scoData.getComment())); } if (scoData.getCountAndDescription() != null) { - updateSheet(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_count_name(), Bundle.BlackboardArtifactNode_createSheet_count_displayName(), scoData.getCountAndDescription().getRight(), scoData.getCountAndDescription().getLeft())); + updateSheet(new NodeProperty<>( + Bundle.BlackboardArtifactNode_createSheet_count_name(), + Bundle.BlackboardArtifactNode_createSheet_count_displayName(), + scoData.getCountAndDescription().getRight(), + scoData.getCountAndDescription().getLeft())); } + } else if (eventType.equals(FileNameTransTask.getPropertyName())) { + /* + * Replace the value of the Source File property with the + * translated name via setDisplayName (see note in createSheet), + * and put the untranslated name in the Original Name property + * and in the tooltip. + */ + String originalName = evt.getOldValue().toString(); + translatedSourceName = evt.getNewValue().toString(); + setDisplayName(translatedSourceName); + setShortDescription(originalName); + updateSheet(new NodeProperty<>( + Bundle.BlackboardArtifactNode_createSheet_srcFile_origName(), + Bundle.BlackboardArtifactNode_createSheet_srcFile_origDisplayName(), + NO_DESCR, + originalName)); } } }; @@ -198,7 +223,7 @@ public class BlackboardArtifactNode extends AbstractContentNode(NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.srcFile.name"), - NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.srcFile.displayName"), + sheetSet.put(new NodeProperty<>( + Bundle.BlackboardArtifactNode_createSheet_srcFile_name(), + Bundle.BlackboardArtifactNode_createSheet_srcFile_displayName(), NO_DESCR, - this.getSourceName())); + getDisplayName())); + + if (TextTranslationService.getInstance().hasProvider() && UserPreferences.displayTranslatedFileNames()) { + /* + * If machine translation is configured, add the original name of + * the of the source content of the artifact represented by this + * node to the sheet. + */ + sheetSet.put(new NodeProperty<>( + Bundle.BlackboardArtifactNode_createSheet_srcFile_origName(), + Bundle.BlackboardArtifactNode_createSheet_srcFile_origDisplayName(), + NO_DESCR, + translatedSourceName != null ? srcContent.getName() : "")); + if (translatedSourceName == null) { + /* + * NOTE: The task makes its own weak reference to the listener. + */ + new FileNameTransTask(srcContent.getName(), this, listener).submit(); + } + } if (!UserPreferences.getHideSCOColumns()) { /* @@ -396,12 +449,24 @@ public class BlackboardArtifactNode extends AbstractContentNode(Bundle.BlackboardArtifactNode_createSheet_score_name(), Bundle.BlackboardArtifactNode_createSheet_score_displayName(), VALUE_LOADING, "")); - sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_comment_name(), Bundle.BlackboardArtifactNode_createSheet_comment_displayName(), VALUE_LOADING, "")); + sheetSet.put(new NodeProperty<>( + Bundle.BlackboardArtifactNode_createSheet_score_name(), + Bundle.BlackboardArtifactNode_createSheet_score_displayName(), + VALUE_LOADING, + "")); + sheetSet.put(new NodeProperty<>( + Bundle.BlackboardArtifactNode_createSheet_comment_name(), + Bundle.BlackboardArtifactNode_createSheet_comment_displayName(), + VALUE_LOADING, + "")); if (CentralRepository.isEnabled()) { - sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_count_name(), Bundle.BlackboardArtifactNode_createSheet_count_displayName(), VALUE_LOADING, "")); + sheetSet.put(new NodeProperty<>( + Bundle.BlackboardArtifactNode_createSheet_count_name(), + Bundle.BlackboardArtifactNode_createSheet_count_displayName(), + VALUE_LOADING, + "")); } - backgroundTasksPool.submit(new GetSCOTask(new WeakReference<>(this), weakAppEventListener)); + backgroundTasksPool.submit(new GetSCOTask(new WeakReference<>(this), weakListener)); } /* @@ -414,11 +479,13 @@ public class BlackboardArtifactNode extends AbstractContentNode(NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.artifactType.name"), + sheetSet.put(new NodeProperty<>( + NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.artifactType.name"), NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.artifactType.displayName"), NO_DESCR, associatedArtifact.getDisplayName())); - sheetSet.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.artifactDetails.name"), + sheetSet.put(new NodeProperty<>( + NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.artifactDetails.name"), NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.artifactDetails.displayName"), NO_DESCR, associatedArtifact.getShortDescription())); @@ -459,15 +526,17 @@ public class BlackboardArtifactNode extends AbstractContentNode(NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.ext.name"), + sheetSet.put(new NodeProperty<>( + NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.ext.name"), NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.ext.displayName"), NO_DESCR, ext)); @@ -484,12 +553,11 @@ public class BlackboardArtifactNode extends AbstractContentNode(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileModifiedTime.name"), + AbstractFile file = srcContent instanceof AbstractFile ? (AbstractFile) srcContent : null; + sheetSet.put(new NodeProperty<>( + NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileModifiedTime.name"), NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileModifiedTime.displayName"), "", file == null ? "" : ContentUtils.getStringTime(file.getMtime(), file))); - sheetSet.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileChangedTime.name"), + sheetSet.put(new NodeProperty<>( + NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileChangedTime.name"), NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileChangedTime.displayName"), "", file == null ? "" : ContentUtils.getStringTime(file.getCtime(), file))); - sheetSet.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileAccessedTime.name"), + sheetSet.put(new NodeProperty<>( + NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileAccessedTime.name"), NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileAccessedTime.displayName"), "", file == null ? "" : ContentUtils.getStringTime(file.getAtime(), file))); - sheetSet.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileCreatedTime.name"), + sheetSet.put(new NodeProperty<>( + NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileCreatedTime.name"), NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileCreatedTime.displayName"), "", file == null ? "" : ContentUtils.getStringTime(file.getCrtime(), file))); - sheetSet.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileSize.name"), + sheetSet.put(new NodeProperty<>( + NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileSize.name"), NbBundle.getMessage(BlackboardArtifactNode.class, "ContentTagNode.createSheet.fileSize.displayName"), "", file == null ? "" : file.getSize())); - sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_artifactMD5_name(), + sheetSet.put(new NodeProperty<>( + Bundle.BlackboardArtifactNode_createSheet_artifactMD5_name(), Bundle.BlackboardArtifactNode_createSheet_artifactMD5_displayName(), "", file == null ? "" : StringUtils.defaultString(file.getMd5Hash()))); } } else { String dataSourceStr = ""; - if (srcContent != null) { - try { - Content dataSource = srcContent.getDataSource(); - if (dataSource != null) { - dataSourceStr = dataSource.getName(); - } else { - dataSourceStr = getRootAncestorName(); - } - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, MessageFormat.format("Error getting source data source name (artifact objID={0})", artifact.getId()), ex); //NON-NLS + try { + Content dataSource = srcContent.getDataSource(); + if (dataSource != null) { + dataSourceStr = dataSource.getName(); + } else { + dataSourceStr = getRootAncestorName(); } + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, MessageFormat.format("Error getting source data source name (artifact objID={0})", artifact.getId()), ex); //NON-NLS + } if (dataSourceStr.isEmpty() == false) { @@ -563,24 +636,27 @@ public class BlackboardArtifactNode extends AbstractContentNode(NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.fileSize.name"), + sheetSet.put(new NodeProperty<>( + NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.fileSize.name"), NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.fileSize.displayName"), NO_DESCR, size)); - sheetSet.put(new NodeProperty<>( - NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.path.name"), - NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.path.displayName"), - NO_DESCR, - path)); + sheetSet + .put(new NodeProperty<>( + NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.path.name"), + NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.path.displayName"), + NO_DESCR, + path)); } return sheet; @@ -597,9 +673,7 @@ public class BlackboardArtifactNode extends AbstractContentNode tags = new ArrayList<>(); try { tags.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsByArtifact(artifact)); - if (srcContent != null) { - tags.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByContent(srcContent)); - } + tags.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByContent(srcContent)); } catch (TskCoreException | NoCurrentCaseException ex) { logger.log(Level.SEVERE, MessageFormat.format("Error getting tags for artifact and its source content (artifact objID={0})", artifact.getId()), ex); } @@ -617,7 +691,7 @@ public class BlackboardArtifactNode extends AbstractContentNode sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,13 +24,11 @@ import java.util.Arrays; import java.util.List; import java.util.logging.Level; import javax.swing.Action; -import org.openide.nodes.Children; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.timeline.actions.ViewArtifactInTimelineAction; import org.sleuthkit.autopsy.timeline.actions.ViewFileInTimelineAction; import org.sleuthkit.datamodel.AbstractFile; @@ -46,14 +44,14 @@ import static org.sleuthkit.autopsy.datamodel.Bundle.*; * tag name nodes have tag type child nodes; tag type nodes are the parents of * either content or blackboard artifact tag nodes. */ -public class BlackboardArtifactTagNode extends DisplayableItemNode { +public class BlackboardArtifactTagNode extends TagNode { private static final Logger LOGGER = Logger.getLogger(BlackboardArtifactTagNode.class.getName()); private static final String ICON_PATH = "org/sleuthkit/autopsy/images/green-tag-icon-16.png"; //NON-NLS private final BlackboardArtifactTag tag; public BlackboardArtifactTagNode(BlackboardArtifactTag tag) { - super(Children.LEAF, Lookups.fixed(tag, tag.getArtifact(), tag.getContent())); + super(Lookups.fixed(tag, tag.getArtifact(), tag.getContent()), tag.getContent()); super.setName(tag.getContent().getName()); super.setDisplayName(tag.getContent().getName()); this.setIconBaseWithExtension(ICON_PATH); @@ -75,6 +73,7 @@ public class BlackboardArtifactTagNode extends DisplayableItemNode { NbBundle.getMessage(this.getClass(), "BlackboardArtifactTagNode.createSheet.srcFile.text"), "", tag.getContent().getName())); + addOriginalNameProp(properties); String contentPath; try { contentPath = tag.getContent().getUniquePath(); @@ -82,7 +81,6 @@ public class BlackboardArtifactTagNode extends DisplayableItemNode { Logger.getLogger(ContentTagNode.class.getName()).log(Level.SEVERE, "Failed to get path for content (id = " + tag.getContent().getId() + ")", ex); //NON-NLS contentPath = NbBundle.getMessage(this.getClass(), "BlackboardArtifactTagNode.createSheet.unavail.text"); } - properties.put(new NodeProperty<>( NbBundle.getMessage(this.getClass(), "BlackboardArtifactTagNode.createSheet.srcFilePath.text"), NbBundle.getMessage(this.getClass(), "BlackboardArtifactTagNode.createSheet.srcFilePath.text"), @@ -146,11 +144,6 @@ public class BlackboardArtifactTagNode extends DisplayableItemNode { return visitor.visit(this); } - @Override - public boolean isLeafTypeNode() { - return true; - } - @Override public String getItemType() { return getClass().getName(); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties index 3fa8cd0341..83225be1c2 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties @@ -11,8 +11,6 @@ ArtifactTypeNode.createSheet.childCnt.name=Child Count ArtifactTypeNode.createSheet.childCnt.displayName=Child Count ArtifactTypeNode.createSheet.childCnt.desc=no description BlackboardArtifactNode.noDesc.text=no description -BlackboardArtifactNode.createSheet.srcFile.name=Source File -BlackboardArtifactNode.createSheet.srcFile.displayName=Source File BlackboardArtifactNode.createSheet.ext.name=Extension BlackboardArtifactNode.createSheet.ext.displayName=Extension BlackboardArtifactNode.createSheet.mimeType.name=MIME Type diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED index 8289f947da..e7310882a3 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties-MERGED @@ -74,10 +74,12 @@ BlackboardArtifactNode.createSheet.path.displayName=Path BlackboardArtifactNode.createSheet.path.name=Path BlackboardArtifactNode.createSheet.score.displayName=S BlackboardArtifactNode.createSheet.score.name=S +BlackboardArtifactNode.createSheet.srcFile.displayName=Source File +BlackboardArtifactNode.createSheet.srcFile.name=Source File +BlackboardArtifactNode.createSheet.srcFile.origDisplayName=Original Name +BlackboardArtifactNode.createSheet.srcFile.origName=Original Name BlackboardArtifactNode.createSheet.taggedItem.description=Result or associated file has been tagged. BlackboardArtifactNode.createSheet.tags.displayName=Tags -# {0} - artifactDisplayName -BlackboardArtifactNode.displayName.artifact={0} Artifact BlackboardArtifactTagNode.createSheet.userName.text=User Name BlackboardArtifactTagNode.viewSourceArtifact.text=View Source Result Category.five=CAT-5: Non-pertinent @@ -88,6 +90,7 @@ Category.two=CAT-2: Child Exploitation (Non-Illegal/Age Difficult) Category.zero=CAT-0: Uncategorized ContentTagNode.createSheet.artifactMD5.displayName=MD5 Hash ContentTagNode.createSheet.artifactMD5.name=MD5 Hash +ContentTagNode.createSheet.origFileName=Original Name ContentTagNode.createSheet.userName.text=User Name DeletedContent.allDelFilter.text=All DeletedContent.createSheet.filterType.desc=no description @@ -175,8 +178,6 @@ ArtifactTypeNode.createSheet.childCnt.name=Child Count ArtifactTypeNode.createSheet.childCnt.displayName=Child Count ArtifactTypeNode.createSheet.childCnt.desc=no description BlackboardArtifactNode.noDesc.text=no description -BlackboardArtifactNode.createSheet.srcFile.name=Source File -BlackboardArtifactNode.createSheet.srcFile.displayName=Source File BlackboardArtifactNode.createSheet.ext.name=Extension BlackboardArtifactNode.createSheet.ext.displayName=Extension BlackboardArtifactNode.createSheet.mimeType.name=MIME Type @@ -345,6 +346,8 @@ TagNameNode.bbArtTagTypeNodeKey.text=Result Tags TagNameNode.bookmark.text=Bookmark TagNameNode.createSheet.name.name=Name TagNameNode.createSheet.name.displayName=Name +TagNode.propertySheet.origName=Original Name +TagNode.propertySheet.origNameDisplayName=Original Name TagsNode.displayName.text=Tags TagsNode.createSheet.name.name=Name TagsNode.createSheet.name.displayName=Name diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java index e213e460cb..89d6c2fae2 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-2016 Basis Technology Corp. + * Copyright 2013-2020 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,7 +24,6 @@ import java.util.List; import java.util.logging.Level; import javax.swing.Action; import org.apache.commons.lang3.StringUtils; -import org.openide.nodes.Children; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; @@ -39,18 +38,16 @@ import org.sleuthkit.datamodel.TskCoreException; /** * Instances of this class wrap ContentTag objects. In the Autopsy presentation * of the SleuthKit data model, they are leaf nodes of a tree consisting of - * content and blackboard artifact tags, grouped first by tag type, then by tag - * name. + * content and artifact tags, grouped first by tag type, then by tag name. */ -class ContentTagNode extends DisplayableItemNode { +class ContentTagNode extends TagNode { private static final Logger LOGGER = Logger.getLogger(ContentTagNode.class.getName()); - private static final String ICON_PATH = "org/sleuthkit/autopsy/images/blue-tag-icon-16.png"; //NON-NLS private final ContentTag tag; - public ContentTagNode(ContentTag tag) { - super(Children.LEAF, Lookups.fixed(tag, tag.getContent())); + ContentTagNode(ContentTag tag) { + super(Lookups.fixed(tag, tag.getContent()), tag.getContent()); super.setName(tag.getContent().getName()); super.setDisplayName(tag.getContent().getName()); this.setIconBaseWithExtension(ICON_PATH); @@ -58,6 +55,7 @@ class ContentTagNode extends DisplayableItemNode { } @Messages({ + "ContentTagNode.createSheet.origFileName=Original Name", "ContentTagNode.createSheet.artifactMD5.displayName=MD5 Hash", "ContentTagNode.createSheet.artifactMD5.name=MD5 Hash", "ContentTagNode.createSheet.userName.text=User Name"}) @@ -79,15 +77,19 @@ class ContentTagNode extends DisplayableItemNode { properties = Sheet.createPropertiesSet(); propertySheet.put(properties); } - properties.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.file.name"), + properties.put(new NodeProperty<>( + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.file.name"), NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.file.displayName"), "", content.getName())); - properties.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.filePath.name"), + addOriginalNameProp(properties); + properties.put(new NodeProperty<>( + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.filePath.name"), NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.filePath.displayName"), "", contentPath)); - properties.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.comment.name"), + properties.put(new NodeProperty<>( + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.comment.name"), NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.comment.displayName"), "", tag.getComment())); @@ -95,23 +97,28 @@ class ContentTagNode extends DisplayableItemNode { NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileModifiedTime.displayName"), "", file != null ? ContentUtils.getStringTime(file.getMtime(), file) : "")); - properties.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileChangedTime.name"), + properties.put(new NodeProperty<>( + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileChangedTime.name"), NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileChangedTime.displayName"), "", file != null ? ContentUtils.getStringTime(file.getCtime(), file) : "")); - properties.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileAccessedTime.name"), + properties.put(new NodeProperty<>( + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileAccessedTime.name"), NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileAccessedTime.displayName"), "", file != null ? ContentUtils.getStringTime(file.getAtime(), file) : "")); - properties.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileCreatedTime.name"), + properties.put(new NodeProperty<>( + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileCreatedTime.name"), NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileCreatedTime.displayName"), "", file != null ? ContentUtils.getStringTime(file.getCrtime(), file) : "")); - properties.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileSize.name"), + properties.put(new NodeProperty<>( + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileSize.name"), NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileSize.displayName"), "", content.getSize())); - properties.put(new NodeProperty<>(Bundle.ContentTagNode_createSheet_artifactMD5_name(), + properties.put(new NodeProperty<>( + Bundle.ContentTagNode_createSheet_artifactMD5_name(), Bundle.ContentTagNode_createSheet_artifactMD5_displayName(), "", file != null ? StringUtils.defaultString(file.getMd5Hash()) : "")); @@ -128,8 +135,7 @@ class ContentTagNode extends DisplayableItemNode { List actions = new ArrayList<>(); actions.addAll(Arrays.asList(super.getActions(context))); - AbstractFile file = getLookup().lookup(AbstractFile.class - ); + AbstractFile file = getLookup().lookup(AbstractFile.class); if (file != null) { actions.add(ViewFileInTimelineAction.createViewFileAction(file)); } @@ -144,13 +150,9 @@ class ContentTagNode extends DisplayableItemNode { return visitor.visit(this); } - @Override - public boolean isLeafTypeNode() { - return true; - } - @Override public String getItemType() { return getClass().getName(); } + } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNode.java index c723d99b55..c6bc88129a 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2012-2020 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.datamodel; import org.openide.nodes.AbstractNode; import org.openide.nodes.Children; +import org.openide.nodes.Sheet; import org.openide.util.Lookup; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -142,4 +143,26 @@ public abstract class DisplayableItemNode extends AbstractNode { return selectedChildNodeInfo; } + /** + * Updates the node property sheet by replacing existing properties with new + * properties with the same property name. + * + * @param newProps The replacement property objects. + */ + protected synchronized final void updatePropertySheet(NodeProperty... newProps) { + Sheet currentSheet = this.getSheet(); + Sheet.Set currentPropsSet = currentSheet.get(Sheet.PROPERTIES); + Property[] currentProps = currentPropsSet.getProperties(); + for (NodeProperty newProp : newProps) { + for (int i = 0; i < currentProps.length; i++) { + if (currentProps[i].getName().equals(newProp.getName())) { + currentProps[i] = newProp; + } + } + } + currentPropsSet.put(currentProps); + currentSheet.put(currentPropsSet); + this.setSheet(currentSheet); + } + } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/TagNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/TagNode.java new file mode 100755 index 0000000000..9653c44d04 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/TagNode.java @@ -0,0 +1,128 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020-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.datamodel; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import org.openide.nodes.Children; +import org.openide.nodes.Sheet; +import org.openide.util.Lookup; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.core.UserPreferences; +import org.sleuthkit.autopsy.datamodel.utils.FileNameTransTask; +import org.sleuthkit.autopsy.texttranslation.TextTranslationService; +import org.sleuthkit.datamodel.Content; + +/** + * An abstract superclass for a node that represents a tag, uses the name of a + * given Content object as its display name, and has a property sheet with an + * original name property when machine translation is enabled. + * + * The translation of the Content name is done in a background thread. The + * translated name is made the display name of the node and the untranslated + * name is put into both the original name property and into the node's tooltip. + * + * TODO (Jira-6174): Consider modifying this class to be able to use it more broadly + * within the Autopsy data model (i.e., AbstractNode suclasses). It's not really + * specific to a tag node. + */ +@NbBundle.Messages({ + "TagNode.propertySheet.origName=Original Name", + "TagNode.propertySheet.origNameDisplayName=Original Name" +}) +abstract class TagNode extends DisplayableItemNode { + + private final static String ORIG_NAME_PROP_NAME = Bundle.TagNode_propertySheet_origName(); + private final static String ORIG_NAME_PROP_DISPLAY_NAME = Bundle.TagNode_propertySheet_origNameDisplayName(); + + private final String originalName; + private volatile String translatedName; + + /** + * An abstract superclass for a node that represents a tag, uses the name of + * a given Content object as its display name, and has a property sheet with + * an untranslated file name property when machine translation is enabled. + * + * @param lookup The Lookup of the node. + * @param content The Content to use for the node display name. + */ + TagNode(Lookup lookup, Content content) { + super(Children.LEAF, lookup); + originalName = content.getName(); + } + + @Override + public boolean isLeafTypeNode() { + return true; + } + + @Override + abstract public String getItemType(); + + @Override + abstract public T accept(DisplayableItemNodeVisitor visitor); + + /** + * Adds an original name property to the node's property sheet and submits + * an original name translation task. + * + * The translation of the original name is done in a background thread. The + * translated name is made the display name of the node and the untranslated + * name is put into both the original name property and into the node's + * tooltip. + * + * @param properties The node's property sheet. + */ + protected void addOriginalNameProp(Sheet.Set properties) { + if (TextTranslationService.getInstance().hasProvider() && UserPreferences.displayTranslatedFileNames()) { + properties.put(new NodeProperty<>( + ORIG_NAME_PROP_NAME, + ORIG_NAME_PROP_DISPLAY_NAME, + "", + translatedName != null ? originalName : "")); + if (translatedName == null) { + new FileNameTransTask(originalName, this, new NameTranslationListener()).submit(); + } + } + } + + /** + * A listener for PropertyChangeEvents from a background task used to + * translate the original display name associated with the node. + */ + private class NameTranslationListener implements PropertyChangeListener { + + @Override + public void propertyChange(PropertyChangeEvent evt) { + String eventType = evt.getPropertyName(); + if (eventType.equals(FileNameTransTask.getPropertyName())) { + translatedName = evt.getNewValue().toString(); + String originalName = evt.getOldValue().toString(); + setDisplayName(translatedName); + setShortDescription(originalName); + updatePropertySheet(new NodeProperty<>( + ORIG_NAME_PROP_NAME, + ORIG_NAME_PROP_DISPLAY_NAME, + "", + originalName)); + } + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/utils/AbstractNodePropertySheetTask.java b/Core/src/org/sleuthkit/autopsy/datamodel/utils/AbstractNodePropertySheetTask.java index 368b09b142..0a7e0a6f3f 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/utils/AbstractNodePropertySheetTask.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/utils/AbstractNodePropertySheetTask.java @@ -64,7 +64,7 @@ public abstract class AbstractNodePropertySheetTask impl * @return The Future of the task, may be used for task cancellation by * calling Future.cancel(true). */ - public static Future submitTask(AbstractNodePropertySheetTask task) { + private static Future submitTask(AbstractNodePropertySheetTask task) { return executor.submit(task); } @@ -104,12 +104,22 @@ public abstract class AbstractNodePropertySheetTask impl * * @param node The AbstractNode. * - * @return The result of the computation as a PropertyChangeEvent. + * @return The result of the computation as a PropertyChangeEvent, may be + * null. */ protected abstract PropertyChangeEvent computePropertyValue(T node) throws Exception; + /** + * Submits this task to the ExecutorService for the thread pool. + * + * @return The task's Future from the ExecutorService. + */ + public final Future submit() { + return submitTask(this); + } + @Override - final public void run() { + public final void run() { try { T node = this.weakNodeRef.get(); PropertyChangeListener listener = this.weakListenerRef.get(); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/utils/FileNameTransTask.java b/Core/src/org/sleuthkit/autopsy/datamodel/utils/FileNameTransTask.java new file mode 100755 index 0000000000..8b755ec3dc --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/utils/FileNameTransTask.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.datamodel.utils; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import org.openide.nodes.AbstractNode; +import org.sleuthkit.autopsy.texttranslation.utils.FileNameTranslationUtil; + +/** + * An AbstractNodePropertySheetTask that translates a file name for an + * AbstractNode's property sheet. + */ +public class FileNameTransTask extends AbstractNodePropertySheetTask { + + private final static String EVENT_SOURCE = FileNameTransTask.class.getName(); + private final static String PROPERTY_NAME = EVENT_SOURCE + ".TranslatedFileName"; + private final String fileName; + + public static String getPropertyName() { + return PROPERTY_NAME; + } + + /** + * Constructs an AbstractNodePropertySheetTask that translates a file name + * for an AbstractNode's property sheet. When the translation is complete, a + * PropertyChangeEvent will be fired to the node's PropertyChangeListener. + * Call getPropertyName() to identify the property. + * + * @param node The node. + * @param listener The node's PropertyChangeListener. + * @param fileName THe file name. + */ + public FileNameTransTask(String fileName, AbstractNode node, PropertyChangeListener listener) { + super(node, listener); + this.fileName = fileName; + } + + @Override + protected PropertyChangeEvent computePropertyValue(AbstractNode node) throws Exception { + String translatedFileName = FileNameTranslationUtil.translate(fileName); + return translatedFileName.isEmpty() ? null : new PropertyChangeEvent(EVENT_SOURCE, PROPERTY_NAME, fileName, translatedFileName); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java index 522d3836c3..a412bb5970 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2019 Basis Technology Corp. + * Copyright 2012-2020 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -187,27 +187,6 @@ public class DataResultFilterNode extends FilterNode { return propertySets; } - /** - * Gets the display name for the wrapped node. - * - * OutlineView used in the DataResult table uses getDisplayName() to - * populate the first column, which is Source File. - * - * Hence this override to return the 'correct' displayName for the wrapped - * node. - * - * @return The display name for the node. - */ - @Override - public String getDisplayName() { - final Node orig = getOriginal(); - String name = orig.getDisplayName(); - if ((orig instanceof BlackboardArtifactNode)) { - name = ((BlackboardArtifactNode) orig).getSourceName(); - } - return name; - } - /** * Adds information about which child node of this node, if any, should be * selected. Can be null. diff --git a/Core/src/org/sleuthkit/autopsy/filequery/Bundle.properties b/Core/src/org/sleuthkit/autopsy/filequery/Bundle.properties index 108b3608fa..284e78aead 100644 --- a/Core/src/org/sleuthkit/autopsy/filequery/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/filequery/Bundle.properties @@ -44,7 +44,6 @@ FileSearchPanel.hashSetCheckbox.text=Hash Set: FileSearchPanel.tagsCheckbox.text=Tag: FileSearchPanel.interestingItemsCheckbox.text=Interesting Item: FileSearchPanel.scoreCheckbox.text=Has Score: -FileSearchPanel.exifCheckbox.text=Possibly User Created FileSearchPanel.notableCheckbox.text=Must have been tagged as notable FileSearchPanel.objectsCheckbox.text=Object Detected: ResultsPanel.currentPageLabel.text=Page: - @@ -61,10 +60,10 @@ GroupListPanel.groupKeyList.border.title=Groups DiscoveryTopComponent.imagesButton.text=Images DiscoveryTopComponent.videosButton.text=Videos ResultsPanel.resultsSplitPane.toolTipText= -FileSearchPanel.stepTwoLabel.text=Step 2: Filter which images to show FileSearchPanel.stepThreeLabel.text=Step 3: Choose display settings DiscoveryTopComponent.stepOneLabel.text=Step 1: Pick File Type DiscoveryTopComponent.documentsButton.text=Documents DocumentPanel.fileSizeLabel.toolTipText= DocumentPanel.isDeletedLabel.toolTipText= ImageThumbnailPanel.isDeletedLabel.toolTipText= +FileSearchPanel.userCreatedCheckbox.text=Possibly User Created diff --git a/Core/src/org/sleuthkit/autopsy/filequery/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/filequery/Bundle.properties-MERGED index ea9e846568..ec44af6ee0 100644 --- a/Core/src/org/sleuthkit/autopsy/filequery/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/filequery/Bundle.properties-MERGED @@ -16,6 +16,10 @@ DiscoveryUiUtility.sizeLabel.text=Size: {0} {1} DiscoveryUiUtility.terraBytes.text=TB # {0} - otherInstanceCount DocumentPanel.nameLabel.more.text=\ and {0} more +DocumentPanel.noImageExtraction.text=0 of ? images +DocumentPanel.numberOfImages.noImages=No images +# {0} - numberOfImages +DocumentPanel.numberOfImages.text=1 of {0} images DocumentWrapper.previewInitialValue=Preview not generated yet. FileGroup.groupSortingAlgorithm.groupName.text=Group Name FileGroup.groupSortingAlgorithm.groupSize.text=Group Size @@ -96,7 +100,6 @@ FileSearchFiltering.DataSourceFilter.datasource={0}({1}) # {0} - filters FileSearchFiltering.DataSourceFilter.desc=Files in data source(s): {0} FileSearchFiltering.DataSourceFilter.or=\ or -FileSearchFiltering.ExifFilter.desc=Files that contain EXIF data # {0} - filters FileSearchFiltering.FileTypeFilter.desc=Files with type: {0} FileSearchFiltering.FileTypeFilter.or=\ or @@ -133,10 +136,14 @@ FileSearchFiltering.SizeFilter.range=({0} to {1}) # {0} - tag names FileSearchFiltering.TagsFilter.desc=Files that have been tagged {0} FileSearchFiltering.TagsFilter.or=\ or +FileSearchFiltering.UserCreatedFilter.desc=Files that contain EXIF data FileSearchPanel.dialogTitle.text=Test file search FileSearchPanel.sortingPanel.border.title=Grouping FileSearchPanel.searchButton.text=Show FileSearchPanel.addButton.text=Add +FileSearchPanel.steptwo.documents=Step 2: Filter which documents to show +FileSearchPanel.steptwo.images=Step 2: Filter which images to show +FileSearchPanel.steptwo.videos=Step 2: Filter which videos to show FileSearchPanel.substringRadioButton.text=Substring FileSearchPanel.fullRadioButton.text=Full FileSearchPanel.parentCheckbox.text=Parent Folder: @@ -163,7 +170,6 @@ FileSearchPanel.hashSetCheckbox.text=Hash Set: FileSearchPanel.tagsCheckbox.text=Tag: FileSearchPanel.interestingItemsCheckbox.text=Interesting Item: FileSearchPanel.scoreCheckbox.text=Has Score: -FileSearchPanel.exifCheckbox.text=Possibly User Created FileSearchPanel.notableCheckbox.text=Must have been tagged as notable FileSearchPanel.objectsCheckbox.text=Object Detected: FileSorter.SortingMethod.datasource.displayName=Data Source @@ -206,13 +212,13 @@ GroupListPanel.groupKeyList.border.title=Groups DiscoveryTopComponent.imagesButton.text=Images DiscoveryTopComponent.videosButton.text=Videos ResultsPanel.resultsSplitPane.toolTipText= -FileSearchPanel.stepTwoLabel.text=Step 2: Filter which images to show FileSearchPanel.stepThreeLabel.text=Step 3: Choose display settings DiscoveryTopComponent.stepOneLabel.text=Step 1: Pick File Type DiscoveryTopComponent.documentsButton.text=Documents DocumentPanel.fileSizeLabel.toolTipText= DocumentPanel.isDeletedLabel.toolTipText= ImageThumbnailPanel.isDeletedLabel.toolTipText= +FileSearchPanel.userCreatedCheckbox.text=Possibly User Created ResultsPanel.unableToCreate.text=Unable to create summary. ResultsPanel.viewFileInDir.name=View File in Directory VideoThumbnailPanel.bytes.text=bytes diff --git a/Core/src/org/sleuthkit/autopsy/filequery/DiscoveryUiUtils.java b/Core/src/org/sleuthkit/autopsy/filequery/DiscoveryUiUtils.java index fc2752a659..c9ad380a32 100644 --- a/Core/src/org/sleuthkit/autopsy/filequery/DiscoveryUiUtils.java +++ b/Core/src/org/sleuthkit/autopsy/filequery/DiscoveryUiUtils.java @@ -35,9 +35,11 @@ final class DiscoveryUiUtils { private static final String RED_CIRCLE_ICON_PATH = "org/sleuthkit/autopsy/images/red-circle-exclamation.png"; private static final String YELLOW_CIRCLE_ICON_PATH = "org/sleuthkit/autopsy/images/yellow-circle-yield.png"; private static final String DELETE_ICON_PATH = "/org/sleuthkit/autopsy/images/file-icon-deleted.png"; + private static final String UNSUPPORTED_DOC_PATH = "/org/sleuthkit/autopsy/images/image-extraction-not-supported.png"; private static final ImageIcon INTERESTING_SCORE_ICON = new ImageIcon(ImageUtilities.loadImage(YELLOW_CIRCLE_ICON_PATH, false)); private static final ImageIcon NOTABLE_SCORE_ICON = new ImageIcon(ImageUtilities.loadImage(RED_CIRCLE_ICON_PATH, false)); private static final ImageIcon DELETED_ICON = new ImageIcon(ImageUtilities.loadImage(DELETE_ICON_PATH, false)); + private static final ImageIcon UNSUPPORTED_DOCUMENT_THUMBNAIL = new ImageIcon(ImageUtilities.loadImage(UNSUPPORTED_DOC_PATH, false)); @NbBundle.Messages({"# {0} - fileSize", "# {1} - units", @@ -83,6 +85,16 @@ final class DiscoveryUiUtils { return Bundle.DiscoveryUiUtility_sizeLabel_text(size, units); } + /** + * Get the image to use when the document type does not support image + * extraction. + * + * @return An image that indicates we don't know if there are images. + */ + static ImageIcon getUnsupportedImageThumbnail() { + return UNSUPPORTED_DOCUMENT_THUMBNAIL; + } + /** * Helper method to see if point is on the icon. * diff --git a/Core/src/org/sleuthkit/autopsy/filequery/DocumentPanel.form b/Core/src/org/sleuthkit/autopsy/filequery/DocumentPanel.form index 05ed6fc0c8..beacd1f271 100644 --- a/Core/src/org/sleuthkit/autopsy/filequery/DocumentPanel.form +++ b/Core/src/org/sleuthkit/autopsy/filequery/DocumentPanel.form @@ -24,19 +24,28 @@ - + - + - + - - + + + + + + + + + + + - + @@ -44,9 +53,15 @@ - + + + + - + + + + @@ -104,36 +119,39 @@ - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + - - - - - - - - - - - - - - - - - - - - - - + diff --git a/Core/src/org/sleuthkit/autopsy/filequery/DocumentPanel.java b/Core/src/org/sleuthkit/autopsy/filequery/DocumentPanel.java index 849d3843ec..87919cab87 100644 --- a/Core/src/org/sleuthkit/autopsy/filequery/DocumentPanel.java +++ b/Core/src/org/sleuthkit/autopsy/filequery/DocumentPanel.java @@ -23,10 +23,12 @@ import java.awt.Component; import java.awt.Dimension; import java.awt.Point; import java.awt.event.MouseEvent; +import javax.swing.ImageIcon; import javax.swing.JComponent; import javax.swing.JList; import javax.swing.ListCellRenderer; import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.corecomponents.AutoWrappingJTextPane; /** * Class which displays a preview and details about a document. @@ -56,8 +58,9 @@ public class DocumentPanel extends javax.swing.JPanel implements ListCellRendere scoreLabel = new javax.swing.JLabel(); fileSizeLabel = new javax.swing.JLabel(); nameLabel = new javax.swing.JLabel(); - javax.swing.JScrollPane previewScrollPane = new javax.swing.JScrollPane(); - previewTextArea = new javax.swing.JTextArea(); + sampleImageLabel = new javax.swing.JLabel(); + numberOfImagesLabel = new javax.swing.JLabel(); + previewTextPane = new AutoWrappingJTextPane(); setBorder(javax.swing.BorderFactory.createEtchedBorder()); @@ -75,18 +78,14 @@ public class DocumentPanel extends javax.swing.JPanel implements ListCellRendere fileSizeLabel.setToolTipText(org.openide.util.NbBundle.getMessage(DocumentPanel.class, "DocumentPanel.fileSizeLabel.toolTipText")); // NOI18N - previewScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER); + sampleImageLabel.setBorder(javax.swing.BorderFactory.createEtchedBorder()); + sampleImageLabel.setIconTextGap(0); + sampleImageLabel.setMaximumSize(new java.awt.Dimension(100, 100)); + sampleImageLabel.setMinimumSize(new java.awt.Dimension(100, 100)); + sampleImageLabel.setPreferredSize(new java.awt.Dimension(100, 100)); - previewTextArea.setEditable(false); - previewTextArea.setColumns(20); - previewTextArea.setFont(new java.awt.Font("Tahoma", 0, 11)); // NOI18N - previewTextArea.setLineWrap(true); - previewTextArea.setRows(5); - previewTextArea.setWrapStyleWord(true); - previewTextArea.setEnabled(false); - previewTextArea.setFocusable(false); - previewTextArea.setMaximumSize(new java.awt.Dimension(164, 94)); - previewScrollPane.setViewportView(previewTextArea); + previewTextPane.setEditable(false); + previewTextPane.setBorder(javax.swing.BorderFactory.createEtchedBorder()); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); @@ -97,21 +96,31 @@ public class DocumentPanel extends javax.swing.JPanel implements ListCellRendere .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addComponent(fileSizeLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(isDeletedLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(scoreLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addComponent(previewScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 649, Short.MAX_VALUE) - .addComponent(nameLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(nameLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 586, Short.MAX_VALUE) + .addComponent(previewTextPane, javax.swing.GroupLayout.DEFAULT_SIZE, 586, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(numberOfImagesLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(sampleImageLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))) .addContainerGap()) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addContainerGap() - .addComponent(nameLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(numberOfImagesLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 17, Short.MAX_VALUE) + .addComponent(nameLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(previewScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(sampleImageLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(previewTextPane, javax.swing.GroupLayout.PREFERRED_SIZE, 98, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(scoreLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) @@ -126,12 +135,20 @@ public class DocumentPanel extends javax.swing.JPanel implements ListCellRendere private javax.swing.JLabel fileSizeLabel; private javax.swing.JLabel isDeletedLabel; private javax.swing.JLabel nameLabel; - private javax.swing.JTextArea previewTextArea; + private javax.swing.JLabel numberOfImagesLabel; + private javax.swing.JTextPane previewTextPane; + private javax.swing.JLabel sampleImageLabel; private javax.swing.JLabel scoreLabel; // End of variables declaration//GEN-END:variables - @Messages({"# {0} - otherInstanceCount", - "DocumentPanel.nameLabel.more.text= and {0} more"}) + @Messages({ + "# {0} - otherInstanceCount", + "DocumentPanel.nameLabel.more.text= and {0} more", + "# {0} - numberOfImages", + "DocumentPanel.numberOfImages.text=1 of {0} images", + "DocumentPanel.numberOfImages.noImages=No images", + "DocumentPanel.noImageExtraction.text=0 of ? images"}) + @Override public Component getListCellRendererComponent(JList list, DocumentWrapper value, int index, boolean isSelected, boolean cellHasFocus) { fileSizeLabel.setText(DiscoveryUiUtils.getFileSizeString(value.getResultFile().getFirstInstance().getSize())); @@ -139,9 +156,19 @@ public class DocumentPanel extends javax.swing.JPanel implements ListCellRendere if (value.getResultFile().getAllInstances().size() > 1) { nameText += Bundle.DocumentPanel_nameLabel_more_text(value.getResultFile().getAllInstances().size() - 1); } + if (value.getSummary().getNumberOfImages() > 0) { + numberOfImagesLabel.setText(Bundle.DocumentPanel_numberOfImages_text(value.getSummary().getNumberOfImages())); + sampleImageLabel.setIcon(new ImageIcon(value.getSummary().getSampleImage())); + } else if (FileSearchData.getDocTypesWithoutImageExtraction().contains(value.getResultFile().getFirstInstance().getMIMEType())) { + numberOfImagesLabel.setText(Bundle.DocumentPanel_noImageExtraction_text()); + sampleImageLabel.setIcon(DiscoveryUiUtils.getUnsupportedImageThumbnail()); + } else { + numberOfImagesLabel.setText(Bundle.DocumentPanel_numberOfImages_noImages()); + sampleImageLabel.setIcon(null); + } nameLabel.setText(nameText); - previewTextArea.setText(value.getPreview()); - previewTextArea.setCaretPosition(0); + previewTextPane.setText(value.getSummary().getSummaryText()); + previewTextPane.setCaretPosition(0); DiscoveryUiUtils.setDeletedIcon(value.getResultFile().isDeleted(), isDeletedLabel); DiscoveryUiUtils.setScoreIcon(value.getResultFile(), scoreLabel); setBackground(isSelected ? SELECTION_COLOR : list.getBackground()); diff --git a/Core/src/org/sleuthkit/autopsy/filequery/DocumentWrapper.java b/Core/src/org/sleuthkit/autopsy/filequery/DocumentWrapper.java index a15e151673..9bf3b173ff 100644 --- a/Core/src/org/sleuthkit/autopsy/filequery/DocumentWrapper.java +++ b/Core/src/org/sleuthkit/autopsy/filequery/DocumentWrapper.java @@ -19,55 +19,57 @@ package org.sleuthkit.autopsy.filequery; import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.textsummarizer.TextSummary; /** - * Class to wrap all the information necessary for a document preview to be + * Class to wrap all the information necessary for a document summary to be * displayed. */ public class DocumentWrapper { - private String preview; + private TextSummary summary; private final ResultFile resultFile; /** * Construct a new DocumentWrapper. * * @param file The ResultFile which represents the document which the - * preview summary is created for. + * summary is created for. */ @Messages({"DocumentWrapper.previewInitialValue=Preview not generated yet."}) DocumentWrapper(ResultFile file) { - this.preview = Bundle.DocumentWrapper_previewInitialValue(); + this.summary = new TextSummary(Bundle.DocumentWrapper_previewInitialValue(), null, 0); this.resultFile = file; } /** - * Set the preview summary which exists. + * Set the summary which exists. * - * @param preview The String which should be displayed as a preview for this - * document. + * @param textSummary The TextSummary object which contains the text and + * image which should be displayed as a summary for this + * document. */ - void setPreview(String preview) { - this.preview = preview; + void setSummary(TextSummary textSummary) { + this.summary = textSummary; } /** - * Get the ResultFile which represents the document the preview summary was - * created for. + * Get the ResultFile which represents the document the summary was created + * for. * * @return The ResultFile which represents the document file which the - * preview was created for. + * summary was created for. */ ResultFile getResultFile() { return resultFile; } /** - * Get the preview summary of the document. + * Get the summary of the document. * - * @return The String which is the preview of the document. + * @return The TextSummary which is the summary of the document. */ - String getPreview() { - return preview; + TextSummary getSummary() { + return summary; } } diff --git a/Core/src/org/sleuthkit/autopsy/filequery/FileSearch.java b/Core/src/org/sleuthkit/autopsy/filequery/FileSearch.java index daa4124e8d..5832625b73 100644 --- a/Core/src/org/sleuthkit/autopsy/filequery/FileSearch.java +++ b/Core/src/org/sleuthkit/autopsy/filequery/FileSearch.java @@ -23,7 +23,6 @@ import com.google.common.cache.CacheBuilder; import com.google.common.io.Files; import java.awt.Image; import java.awt.image.BufferedImage; -import java.awt.image.RenderedImage; import java.io.IOException; import java.io.Reader; import java.nio.file.Paths; @@ -95,6 +94,7 @@ class FileSearch { .build(); private static final int PREVIEW_SIZE = 256; private static volatile TextSummarizer summarizerToUse = null; + private static final BufferedImage VIDEO_DEFAULT_IMAGE = getDefaultVideoThumbnail(); /** * Run the file search and returns the SearchResults object for debugging. @@ -280,11 +280,35 @@ class FileSearch { } if (summary == null || StringUtils.isBlank(summary.getSummaryText())) { //summary text was empty grab the beginning of the file - summary = new TextSummary(getFirstLines(file), null, 0); + summary = getDefaultSummary(file); } return summary; } + private static TextSummary getDefaultSummary(AbstractFile file) { + Image image = null; + int countOfImages = 0; + try { + Content largestChild = null; + for (Content child : file.getChildren()) { + if (child instanceof AbstractFile && ImageUtils.isImageThumbnailSupported((AbstractFile) child)) { + countOfImages++; + if (largestChild == null || child.getSize() > largestChild.getSize()) { + largestChild = child; + } + } + } + if (largestChild != null) { + image = ImageUtils.getThumbnail(largestChild, ImageUtils.ICON_SIZE_LARGE); + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Error getting children for file: " + file.getId(), ex); + } + image = image == null ? image : image.getScaledInstance(ImageUtils.ICON_SIZE_MEDIUM, ImageUtils.ICON_SIZE_MEDIUM, + Image.SCALE_SMOOTH); + return new TextSummary(getFirstLines(file), image, countOfImages); + } + /** * Get the beginning of text from the specified AbstractFile. * @@ -456,6 +480,20 @@ class FileSearch { + "AND blackboard_artifacts.obj_id IN (" + objIdList + ") "; // NON-NLS } + /** + * Get the default image to display when a thumbnail is not available. + * + * @return The default video thumbnail. + */ + private static BufferedImage getDefaultVideoThumbnail() { + try { + return ImageIO.read(ImageUtils.class.getResourceAsStream("/org/sleuthkit/autopsy/images/failedToCreateVideoThumb.png"));//NON-NLS + } catch (IOException ex) { + logger.log(Level.SEVERE, "Failed to load 'failed to create video' placeholder.", ex); //NON-NLS + } + return null; + } + /** * Get the video thumbnails for a file which exists in a * VideoThumbnailsWrapper and update the VideoThumbnailsWrapper to include @@ -476,7 +514,6 @@ class FileSearch { cacheDirectory = null; logger.log(Level.WARNING, "Unable to get cache directory, video thumbnails will not be saved", ex); } - if (cacheDirectory == null || file.getMd5Hash() == null || !Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash()).toFile().exists()) { java.io.File tempFile; try { @@ -488,7 +525,7 @@ class FileSearch { 0, 0, 0}; - thumbnailWrapper.setThumbnails(createDefaultThumbnailList(), framePositions); + thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions); return; } if (tempFile.exists() == false || tempFile.length() < file.getSize()) { @@ -502,7 +539,7 @@ class FileSearch { 0, 0, 0}; - thumbnailWrapper.setThumbnails(createDefaultThumbnailList(), framePositions); + thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions); return; } ContentUtils.writeToFile(file, tempFile, progress, null, true); @@ -523,7 +560,7 @@ class FileSearch { 0, 0, 0}; - thumbnailWrapper.setThumbnails(createDefaultThumbnailList(), framePositions); + thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions); return; } double fps = videoFile.get(5); // gets frame per second @@ -535,7 +572,7 @@ class FileSearch { 0, 0, 0}; - thumbnailWrapper.setThumbnails(createDefaultThumbnailList(), framePositions); + thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions); return; } if (Thread.interrupted()) { @@ -544,7 +581,7 @@ class FileSearch { 0, 0, 0}; - thumbnailWrapper.setThumbnails(createDefaultThumbnailList(), framePositions); + thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions); return; } @@ -573,10 +610,10 @@ class FileSearch { logger.log(Level.WARNING, "Error seeking to " + framePositions[i] + "ms in {0}", file.getParentPath() + "/" + file.getName()); //NON-NLS // If we can't set the time, continue to the next frame position and try again. - videoThumbnails.add(ImageUtils.getDefaultThumbnail()); + videoThumbnails.add(VIDEO_DEFAULT_IMAGE); if (cacheDirectory != null) { try { - ImageIO.write((RenderedImage) ImageUtils.getDefaultThumbnail(), THUMBNAIL_FORMAT, + ImageIO.write(VIDEO_DEFAULT_IMAGE, THUMBNAIL_FORMAT, Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash(), i + "-" + framePositions[i] + "." + THUMBNAIL_FORMAT).toFile()); //NON-NLS) } catch (IOException ex) { logger.log(Level.WARNING, "Unable to save default video thumbnail for " + file.getMd5Hash() + " at frame position " + framePositions[i], ex); @@ -588,10 +625,10 @@ class FileSearch { if (!videoFile.read(imageMatrix)) { logger.log(Level.WARNING, "Error reading frame at " + framePositions[i] + "ms from {0}", file.getParentPath() + "/" + file.getName()); //NON-NLS // If the image is bad for some reason, continue to the next frame position and try again. - videoThumbnails.add(ImageUtils.getDefaultThumbnail()); + videoThumbnails.add(VIDEO_DEFAULT_IMAGE); if (cacheDirectory != null) { try { - ImageIO.write((RenderedImage) ImageUtils.getDefaultThumbnail(), THUMBNAIL_FORMAT, + ImageIO.write(VIDEO_DEFAULT_IMAGE, THUMBNAIL_FORMAT, Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash(), i + "-" + framePositions[i] + "." + THUMBNAIL_FORMAT).toFile()); //NON-NLS) } catch (IOException ex) { logger.log(Level.WARNING, "Unable to save default video thumbnail for " + file.getMd5Hash() + " at frame position " + framePositions[i], ex); @@ -602,10 +639,10 @@ class FileSearch { } // If the image is empty, return since no buffered image can be created. if (imageMatrix.empty()) { - videoThumbnails.add(ImageUtils.getDefaultThumbnail()); + videoThumbnails.add(VIDEO_DEFAULT_IMAGE); if (cacheDirectory != null) { try { - ImageIO.write((RenderedImage) ImageUtils.getDefaultThumbnail(), THUMBNAIL_FORMAT, + ImageIO.write(VIDEO_DEFAULT_IMAGE, THUMBNAIL_FORMAT, Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash(), i + "-" + framePositions[i] + "." + THUMBNAIL_FORMAT).toFile()); //NON-NLS) } catch (IOException ex) { logger.log(Level.WARNING, "Unable to save default video thumbnail for " + file.getMd5Hash() + " at frame position " + framePositions[i], ex); @@ -660,7 +697,7 @@ class FileSearch { videoFile.release(); // close the file} } } else { - loadSavedThumbnails(cacheDirectory, thumbnailWrapper); + loadSavedThumbnails(cacheDirectory, thumbnailWrapper, VIDEO_DEFAULT_IMAGE); } } @@ -674,7 +711,7 @@ class FileSearch { * information about the file and the thumbnails * associated with it. */ - private static void loadSavedThumbnails(String cacheDirectory, VideoThumbnailsWrapper thumbnailWrapper) { + private static void loadSavedThumbnails(String cacheDirectory, VideoThumbnailsWrapper thumbnailWrapper, BufferedImage failedVideoThumbImage) { int[] framePositions = new int[4]; List videoThumbnails = new ArrayList<>(); int thumbnailNumber = 0; @@ -683,7 +720,7 @@ class FileSearch { try { videoThumbnails.add(ImageIO.read(Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, md5, fileName).toFile())); } catch (IOException ex) { - videoThumbnails.add(ImageUtils.getDefaultThumbnail()); + videoThumbnails.add(failedVideoThumbImage); logger.log(Level.WARNING, "Unable to read saved video thumbnail " + fileName + " for " + md5, ex); } int framePos = Integer.valueOf(FilenameUtils.getBaseName(fileName).substring(2)); @@ -699,12 +736,12 @@ class FileSearch { * * @return List containing the default thumbnail. */ - private static List createDefaultThumbnailList() { + private static List createDefaultThumbnailList(BufferedImage failedVideoThumbImage) { List videoThumbnails = new ArrayList<>(); - videoThumbnails.add(ImageUtils.getDefaultThumbnail()); - videoThumbnails.add(ImageUtils.getDefaultThumbnail()); - videoThumbnails.add(ImageUtils.getDefaultThumbnail()); - videoThumbnails.add(ImageUtils.getDefaultThumbnail()); + videoThumbnails.add(failedVideoThumbImage); + videoThumbnails.add(failedVideoThumbImage); + videoThumbnails.add(failedVideoThumbImage); + videoThumbnails.add(failedVideoThumbImage); return videoThumbnails; } diff --git a/Core/src/org/sleuthkit/autopsy/filequery/FileSearchData.java b/Core/src/org/sleuthkit/autopsy/filequery/FileSearchData.java index d86d470102..8efabf2704 100644 --- a/Core/src/org/sleuthkit/autopsy/filequery/FileSearchData.java +++ b/Core/src/org/sleuthkit/autopsy/filequery/FileSearchData.java @@ -273,7 +273,6 @@ final class FileSearchData { = new ImmutableSet.Builder() .add("text/html", //NON-NLS "text/csv", //NON-NLS - "text/x-log", //NON-NLS "application/rtf", //NON-NLS "application/pdf", //NON-NLS "application/xhtml+xml", //NON-NLS @@ -292,6 +291,14 @@ final class FileSearchData { "application/vnd.oasis.opendocument.text" //NON-NLS ).build(); + private static final ImmutableSet IMAGE_UNSUPPORTED_DOC_TYPES + = new ImmutableSet.Builder() + .add("application/pdf", //NON-NLS + "application/xhtml+xml").build(); //NON-NLS + + static Collection getDocTypesWithoutImageExtraction(){ + return Collections.unmodifiableCollection(IMAGE_UNSUPPORTED_DOC_TYPES); + } /** * Enum representing the file type. We don't simply use * FileTypeUtils.FileTypeCategory because: - Some file types categories diff --git a/Core/src/org/sleuthkit/autopsy/filequery/FileSearchFiltering.java b/Core/src/org/sleuthkit/autopsy/filequery/FileSearchFiltering.java index 7dab641c79..a96db2fd35 100644 --- a/Core/src/org/sleuthkit/autopsy/filequery/FileSearchFiltering.java +++ b/Core/src/org/sleuthkit/autopsy/filequery/FileSearchFiltering.java @@ -842,14 +842,14 @@ class FileSearchFiltering { } /** - * A filter for specifying that the file must have EXIF data. + * A filter for specifying that the file must have user content suspected data. */ - static class ExifFilter extends FileFilter { + static class UserCreatedFilter extends FileFilter { /** * Create the ExifFilter */ - ExifFilter() { + UserCreatedFilter() { // Nothing to save } @@ -857,14 +857,14 @@ class FileSearchFiltering { String getWhereClause() { return "(obj_id IN (SELECT obj_id from blackboard_artifacts WHERE artifact_id IN " + "(SELECT artifact_id FROM blackboard_attributes WHERE artifact_type_id = " - + BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF.getTypeID() + ")))"; + + BlackboardArtifact.ARTIFACT_TYPE.TSK_USER_CONTENT_SUSPECTED.getTypeID() + ")))"; } @NbBundle.Messages({ - "FileSearchFiltering.ExifFilter.desc=Files that contain EXIF data",}) + "FileSearchFiltering.UserCreatedFilter.desc=Files that contain EXIF data",}) @Override String getDesc() { - return Bundle.FileSearchFiltering_ExifFilter_desc(); + return Bundle.FileSearchFiltering_UserCreatedFilter_desc(); } } diff --git a/Core/src/org/sleuthkit/autopsy/filequery/FileSearchPanel.form b/Core/src/org/sleuthkit/autopsy/filequery/FileSearchPanel.form index ead4b9f264..5ea238b49a 100644 --- a/Core/src/org/sleuthkit/autopsy/filequery/FileSearchPanel.form +++ b/Core/src/org/sleuthkit/autopsy/filequery/FileSearchPanel.form @@ -70,7 +70,7 @@ - + @@ -516,10 +516,10 @@ - + - + @@ -968,15 +968,6 @@ - - - - - - - - - diff --git a/Core/src/org/sleuthkit/autopsy/filequery/FileSearchPanel.java b/Core/src/org/sleuthkit/autopsy/filequery/FileSearchPanel.java index 58403878cc..eb823f0530 100644 --- a/Core/src/org/sleuthkit/autopsy/filequery/FileSearchPanel.java +++ b/Core/src/org/sleuthkit/autopsy/filequery/FileSearchPanel.java @@ -408,16 +408,16 @@ final class FileSearchPanel extends javax.swing.JPanel implements ActionListener } /** - * Setup the exif filter settings. + * Setup the user created filter settings. * * @param visible Boolean indicating if the filter should be visible. * @param enabled Boolean indicating if the filter should be enabled. * @param selected Boolean indicating if the filter should be selected. */ - private void exifFilterSettings(boolean visible, boolean enabled, boolean selected) { - exifCheckbox.setVisible(visible); - exifCheckbox.setEnabled(enabled); - exifCheckbox.setSelected(selected); + private void userCreatedFilterSettings(boolean visible, boolean enabled, boolean selected) { + userCreatedCheckbox.setVisible(visible); + userCreatedCheckbox.setEnabled(enabled); + userCreatedCheckbox.setSelected(selected); } /** @@ -456,7 +456,9 @@ final class FileSearchPanel extends javax.swing.JPanel implements ActionListener * present for images should be reset to their default * status. */ + @NbBundle.Messages({"FileSearchPanel.steptwo.images=Step 2: Filter which images to show"}) private void imagesSelected(boolean enabled, boolean resetSelected) { + stepTwoLabel.setText(Bundle.FileSearchPanel_steptwo_images()); dataSourceFilterSettings(true, enabled, !resetSelected && dataSourceCheckbox.isSelected(), null); int[] selectedSizeIndices = {1, 2, 3, 4, 5}; sizeFilterSettings(true, enabled, resetSelected || sizeCheckbox.isSelected(), resetSelected == true ? selectedSizeIndices : null); @@ -467,7 +469,7 @@ final class FileSearchPanel extends javax.swing.JPanel implements ActionListener selectedFrequencyIndices = new int[]{1, 2, 3, 4, 5, 6, 7}; } crFrequencyFilterSettings(true, enabled, resetSelected || crFrequencyCheckbox.isSelected(), resetSelected == true ? selectedFrequencyIndices : null); - exifFilterSettings(true, enabled, !resetSelected && exifCheckbox.isSelected()); + userCreatedFilterSettings(true, enabled, !resetSelected && userCreatedCheckbox.isSelected()); objectsFilterSettings(true, enabled, !resetSelected && objectsCheckbox.isSelected(), null); hashSetFilterSettings(true, enabled, !resetSelected && hashSetCheckbox.isSelected(), null); interestingItemsFilterSettings(true, enabled, !resetSelected && interestingItemsCheckbox.isSelected(), null); @@ -489,7 +491,9 @@ final class FileSearchPanel extends javax.swing.JPanel implements ActionListener * present for videos should be reset to their default * status. */ + @NbBundle.Messages({"FileSearchPanel.steptwo.videos=Step 2: Filter which videos to show"}) private void videosSelected(boolean enabled, boolean resetSelected) { + stepTwoLabel.setText(Bundle.FileSearchPanel_steptwo_videos()); dataSourceFilterSettings(true, enabled, !resetSelected && dataSourceCheckbox.isSelected(), null); sizeFilterSettings(true, enabled, !resetSelected && sizeCheckbox.isSelected(), null); int[] selectedFrequencyIndices; @@ -499,7 +503,7 @@ final class FileSearchPanel extends javax.swing.JPanel implements ActionListener selectedFrequencyIndices = new int[]{1, 2, 3, 4, 5, 6, 7}; } crFrequencyFilterSettings(true, enabled, resetSelected || crFrequencyCheckbox.isSelected(), resetSelected == true ? selectedFrequencyIndices : null); - exifFilterSettings(true, enabled, !resetSelected && exifCheckbox.isSelected()); + userCreatedFilterSettings(true, enabled, !resetSelected && userCreatedCheckbox.isSelected()); objectsFilterSettings(true, enabled, !resetSelected && objectsCheckbox.isSelected(), null); hashSetFilterSettings(true, enabled, !resetSelected && hashSetCheckbox.isSelected(), null); interestingItemsFilterSettings(true, enabled, !resetSelected && interestingItemsCheckbox.isSelected(), null); @@ -511,17 +515,19 @@ final class FileSearchPanel extends javax.swing.JPanel implements ActionListener notableFilterSettings(false, false, false); } - /** + /** * Set the UI elements available to be the set of UI elements available when * a Document search is being performed. * - * @param enabled Boolean indicating if the filters present for documents - * should be enabled. + * @param enabled Boolean indicating if the filters present for + * documents should be enabled. * @param resetSelected Boolean indicating if selection of the filters - * present for documents should be reset to their default - * status. + * present for documents should be reset to their + * default status. */ + @NbBundle.Messages({"FileSearchPanel.steptwo.documents=Step 2: Filter which documents to show"}) private void documentsSelected(boolean enabled, boolean resetSelected) { + stepTwoLabel.setText(Bundle.FileSearchPanel_steptwo_documents()); dataSourceFilterSettings(true, enabled, !resetSelected && dataSourceCheckbox.isSelected(), null); sizeFilterSettings(true, enabled, !resetSelected && sizeCheckbox.isSelected(), null); int[] selectedFrequencyIndices; @@ -531,7 +537,7 @@ final class FileSearchPanel extends javax.swing.JPanel implements ActionListener selectedFrequencyIndices = new int[]{1, 2, 3, 4, 5, 6, 7}; } crFrequencyFilterSettings(true, enabled, resetSelected || crFrequencyCheckbox.isSelected(), resetSelected == true ? selectedFrequencyIndices : null); - exifFilterSettings(true, enabled, !resetSelected && exifCheckbox.isSelected()); + userCreatedFilterSettings(false, false, false); objectsFilterSettings(false, false, false, null); hashSetFilterSettings(true, enabled, !resetSelected && hashSetCheckbox.isSelected(), null); interestingItemsFilterSettings(true, enabled, !resetSelected && interestingItemsCheckbox.isSelected(), null); @@ -542,7 +548,7 @@ final class FileSearchPanel extends javax.swing.JPanel implements ActionListener knownFilesFilterSettings(false, false, false); notableFilterSettings(false, false, false); } - + /** * Set the type of search to perform. * @@ -941,8 +947,8 @@ final class FileSearchPanel extends javax.swing.JPanel implements ActionListener filters.add(new FileSearchFiltering.TagsFilter(tagsList.getSelectedValuesList())); } - if (exifCheckbox.isSelected()) { - filters.add(new FileSearchFiltering.ExifFilter()); + if (userCreatedCheckbox.isSelected()) { + filters.add(new FileSearchFiltering.UserCreatedFilter()); } if (notableCheckbox.isSelected()) { @@ -1148,7 +1154,7 @@ final class FileSearchPanel extends javax.swing.JPanel implements ActionListener tagsCheckbox = new javax.swing.JCheckBox(); interestingItemsCheckbox = new javax.swing.JCheckBox(); scoreCheckbox = new javax.swing.JCheckBox(); - exifCheckbox = new javax.swing.JCheckBox(); + userCreatedCheckbox = new javax.swing.JCheckBox(); notableCheckbox = new javax.swing.JCheckBox(); objectsScrollPane = new javax.swing.JScrollPane(); objectsList = new javax.swing.JList<>(); @@ -1177,7 +1183,7 @@ final class FileSearchPanel extends javax.swing.JPanel implements ActionListener groupSortingComboBox = new javax.swing.JComboBox<>(); errorLabel = new javax.swing.JLabel(); cancelButton = new javax.swing.JButton(); - javax.swing.JLabel stepTwoLabel = new javax.swing.JLabel(); + stepTwoLabel = new javax.swing.JLabel(); javax.swing.JLabel stepThreeLabel = new javax.swing.JLabel(); setMinimumSize(new java.awt.Dimension(10, 0)); @@ -1474,14 +1480,14 @@ final class FileSearchPanel extends javax.swing.JPanel implements ActionListener gridBagConstraints.insets = new java.awt.Insets(0, 6, 4, 0); filtersPanel.add(scoreCheckbox, gridBagConstraints); - org.openide.awt.Mnemonics.setLocalizedText(exifCheckbox, org.openide.util.NbBundle.getMessage(FileSearchPanel.class, "FileSearchPanel.exifCheckbox.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(userCreatedCheckbox, org.openide.util.NbBundle.getMessage(FileSearchPanel.class, "FileSearchPanel.userCreatedCheckbox.text")); // NOI18N gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; gridBagConstraints.gridy = 3; gridBagConstraints.gridwidth = 4; gridBagConstraints.anchor = java.awt.GridBagConstraints.FIRST_LINE_START; gridBagConstraints.insets = new java.awt.Insets(0, 6, 4, 6); - filtersPanel.add(exifCheckbox, gridBagConstraints); + filtersPanel.add(userCreatedCheckbox, gridBagConstraints); org.openide.awt.Mnemonics.setLocalizedText(notableCheckbox, org.openide.util.NbBundle.getMessage(FileSearchPanel.class, "FileSearchPanel.notableCheckbox.text")); // NOI18N gridBagConstraints = new java.awt.GridBagConstraints(); @@ -1721,8 +1727,6 @@ final class FileSearchPanel extends javax.swing.JPanel implements ActionListener } }); - org.openide.awt.Mnemonics.setLocalizedText(stepTwoLabel, org.openide.util.NbBundle.getMessage(FileSearchPanel.class, "FileSearchPanel.stepTwoLabel.text")); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(stepThreeLabel, org.openide.util.NbBundle.getMessage(FileSearchPanel.class, "FileSearchPanel.stepThreeLabel.text")); // NOI18N javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); @@ -1757,7 +1761,7 @@ final class FileSearchPanel extends javax.swing.JPanel implements ActionListener .addGap(6, 6, 6) .addComponent(stepTwoLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(filtersScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 201, Short.MAX_VALUE) + .addComponent(filtersScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 207, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(stepThreeLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) @@ -1948,7 +1952,6 @@ final class FileSearchPanel extends javax.swing.JPanel implements ActionListener private javax.swing.JButton deleteButton; private javax.swing.JLabel errorLabel; private javax.swing.JRadioButton excludeRadioButton; - private javax.swing.JCheckBox exifCheckbox; private javax.swing.JRadioButton fullRadioButton; private javax.swing.JComboBox groupByCombobox; private javax.swing.JComboBox groupSortingComboBox; @@ -1979,10 +1982,12 @@ final class FileSearchPanel extends javax.swing.JPanel implements ActionListener private javax.swing.JCheckBox sizeCheckbox; private javax.swing.JList sizeList; private javax.swing.JScrollPane sizeScrollPane; + private javax.swing.JLabel stepTwoLabel; private javax.swing.JRadioButton substringRadioButton; private javax.swing.JCheckBox tagsCheckbox; private javax.swing.JList tagsList; private javax.swing.JScrollPane tagsScrollPane; + private javax.swing.JCheckBox userCreatedCheckbox; // End of variables declaration//GEN-END:variables } diff --git a/Core/src/org/sleuthkit/autopsy/filequery/ResultsPanel.java b/Core/src/org/sleuthkit/autopsy/filequery/ResultsPanel.java index 7ba0e452ca..7a750f50eb 100644 --- a/Core/src/org/sleuthkit/autopsy/filequery/ResultsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/filequery/ResultsPanel.java @@ -782,7 +782,7 @@ public class ResultsPanel extends javax.swing.JPanel { if (preview == null) { preview = new TextSummary(Bundle.ResultsPanel_unableToCreate_text(), null, 0); } - documentWrapper.setPreview(preview.getSummaryText()); + documentWrapper.setSummary(preview); return null; } @@ -792,10 +792,10 @@ public class ResultsPanel extends javax.swing.JPanel { try { get(); } catch (InterruptedException | ExecutionException ex) { - documentWrapper.setPreview(ex.getMessage()); + documentWrapper.setSummary(new TextSummary(ex.getMessage(), null, 0)); logger.log(Level.WARNING, "Document Worker Exception", ex); } catch (CancellationException ignored) { - documentWrapper.setPreview(Bundle.ResultsPanel_documentPreview_text()); + documentWrapper.setSummary(new TextSummary(Bundle.ResultsPanel_documentPreview_text(), null, 0)); //we want to do nothing in response to this since we allow it to be cancelled } documentPreviewViewer.repaint(); diff --git a/Core/src/org/sleuthkit/autopsy/images/failedToCreateVideoThumb.png b/Core/src/org/sleuthkit/autopsy/images/failedToCreateVideoThumb.png new file mode 100644 index 0000000000..876402c150 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/images/failedToCreateVideoThumb.png differ diff --git a/Core/src/org/sleuthkit/autopsy/images/image-extraction-not-supported.png b/Core/src/org/sleuthkit/autopsy/images/image-extraction-not-supported.png new file mode 100644 index 0000000000..99a40745a7 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/images/image-extraction-not-supported.png differ diff --git a/Core/src/org/sleuthkit/autopsy/modules/plaso/PlasoIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/plaso/PlasoIngestModule.java index fbc4ebc12b..d359332f84 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/plaso/PlasoIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/plaso/PlasoIngestModule.java @@ -269,7 +269,7 @@ public class PlasoIngestModule implements DataSourceIngestModule { String architectureFolder = PlatformUtil.is64BitOS() ? PLASO64 : PLASO32; String executableToFindName = Paths.get(PLASO, architectureFolder, executableName).toString(); - File exeFile = InstalledFileLocator.getDefault().locate(executableToFindName, "org.sleuthkit.autopsy.core", false); + File exeFile = InstalledFileLocator.getDefault().locate(executableToFindName, PlasoIngestModule.class.getPackage().getName(), false); if (null == exeFile || exeFile.canExecute() == false) { throw new FileNotFoundException(executableName + " executable not found."); } diff --git a/Core/src/org/sleuthkit/autopsy/textextractors/TikaTextExtractor.java b/Core/src/org/sleuthkit/autopsy/textextractors/TikaTextExtractor.java index 6a84a2285c..a8bf0591fb 100644 --- a/Core/src/org/sleuthkit/autopsy/textextractors/TikaTextExtractor.java +++ b/Core/src/org/sleuthkit/autopsy/textextractors/TikaTextExtractor.java @@ -423,7 +423,7 @@ final class TikaTextExtractor implements TextExtractor { } String executableToFindName = Paths.get(TESSERACT_DIR_NAME, TESSERACT_EXECUTABLE).toString(); - File exeFile = InstalledFileLocator.getDefault().locate(executableToFindName, "org.sleuthkit.autopsy.core", false); + File exeFile = InstalledFileLocator.getDefault().locate(executableToFindName, TikaTextExtractor.class.getPackage().getName(), false); if (null == exeFile) { return null; } diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java index 4d8dea3d4a..845679b79b 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java @@ -55,16 +55,19 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { authenticationKeyField.getDocument().addDocumentListener(new DocumentListener() { @Override public void insertUpdate(DocumentEvent e) { + testResultValueLabel.setText(""); firePropertyChange("SettingChanged", true, false); } @Override public void removeUpdate(DocumentEvent e) { + testResultValueLabel.setText(""); firePropertyChange("SettingChanged", true, false); } @Override public void changedUpdate(DocumentEvent e) { + testResultValueLabel.setText(""); firePropertyChange("SettingChanged", true, false); } @@ -256,6 +259,7 @@ public class BingTranslatorSettingsPanel extends javax.swing.JPanel { String selectedCode = ((LanguageWrapper) targetLanguageComboBox.getSelectedItem()).getLanguageCode(); if (!StringUtils.isBlank(selectedCode) && !selectedCode.equals(targetLanguageCode)) { targetLanguageCode = selectedCode; + testResultValueLabel.setText(""); firePropertyChange("SettingChanged", true, false); } }//GEN-LAST:event_targetLanguageComboBoxSelected diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.java index b81ce9fdb1..26387e9353 100644 --- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.java @@ -337,6 +337,7 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel { if (dialogResult == JFileChooser.APPROVE_OPTION) { credentialsPathField.setText(fileChooser.getSelectedFile().getPath()); populateTargetLanguageComboBox(); + testResultValueLabel.setText(""); firePropertyChange("SettingChanged", true, false); } }//GEN-LAST:event_browseButtonActionPerformed @@ -407,6 +408,7 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel { if (!StringUtils.isBlank(selectedCode) && !selectedCode.equals(targetLanguageCode)) { targetLanguageCode = selectedCode; populateTargetLanguageComboBox(); + testResultValueLabel.setText(""); firePropertyChange("SettingChanged", true, false); } } diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/utils/FileNameTranslationUtil.java b/Core/src/org/sleuthkit/autopsy/texttranslation/utils/FileNameTranslationUtil.java new file mode 100755 index 0000000000..752e981990 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/texttranslation/utils/FileNameTranslationUtil.java @@ -0,0 +1,74 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020-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.texttranslation.utils; + +import org.apache.commons.io.FilenameUtils; +import org.sleuthkit.autopsy.texttranslation.NoServiceProviderException; +import org.sleuthkit.autopsy.texttranslation.TextTranslationService; +import org.sleuthkit.autopsy.texttranslation.TranslationException; + +/** + * A utility to translate file names. + */ +public final class FileNameTranslationUtil { + + /** + * Translates a file name using the configured machine translation service. + * + * @param fileName The file name. + * + * @return The translation of the file name. + * + * @throws NoServiceProviderException If machine translation is not + * configured. + * @throws TranslationException If there is an error doing the + * translation. + */ + public static String translate(String fileName) throws NoServiceProviderException, TranslationException { + /* + * Don't attempt translation if the characters of the file name are all + * ASCII chars. + * + * TODO (Jira-6175): This filter prevents translation of many + * non-English file names composed entirely of Latin chars. + */ + if (fileName.matches("^\\p{ASCII}+$")) { + return ""; + } + + TextTranslationService translator = TextTranslationService.getInstance(); + String baseName = FilenameUtils.getBaseName(fileName); + String translation = translator.translate(baseName); + if (!translation.isEmpty()) { + String extension = FilenameUtils.getExtension(fileName); + if (!extension.isEmpty()) { + String extensionDelimiter = (extension.isEmpty()) ? "" : "."; + translation += extensionDelimiter + extension; + } + } + return translation; + } + + /** + * Prevent instantiation of this utility class + */ + private FileNameTranslationUtil() { + } + +} diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoAccountsTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoAccountsTest.java new file mode 100755 index 0000000000..42a0517d98 --- /dev/null +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoAccountsTest.java @@ -0,0 +1,174 @@ +/* + * Central Repository + * + * 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.centralrepository.datamodel; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import junit.framework.Assert; +import junit.framework.TestCase; +import junit.framework.Test; +import org.apache.commons.io.FileUtils; + +import org.netbeans.junit.NbModuleSuite; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoAccount.CentralRepoAccountType; +import org.sleuthkit.datamodel.Account; + +/** + * Tests the Account APIs on the Central Repository. + */ +public class CentralRepoAccountsTest extends TestCase { + + private final Path testDirectory = Paths.get(System.getProperty("java.io.tmpdir"), "CentralRepoDatamodelTest"); + + // NbModuleSuite requires these tests use Junit 3.8 + // Extension of the TestCase class is how tests were defined and used + // in Junit 3.8 + public static Test suite() { + NbModuleSuite.Configuration conf = NbModuleSuite.createConfiguration(CentralRepoAccountsTest.class). + clusters(".*"). + enableModules(".*"); + return conf.suite(); + } + + // This function is run before every test, NOT before the entire collection of + // tests defined in this class are run. + @Override + public void setUp() throws CentralRepoException, IOException { + // Tear down the previous run, if need be. + if (Files.exists(testDirectory)) { + tearDown(); + } + + // Create the test directory + Files.createDirectory(testDirectory); + + final String CR_DB_NAME = "testcentralrepo.db"; + + SqliteCentralRepoSettings sqliteSettings = new SqliteCentralRepoSettings(); + sqliteSettings.setDbName(CR_DB_NAME); + sqliteSettings.setDbDirectory(testDirectory.toString()); + + if (!sqliteSettings.dbDirectoryExists() && !sqliteSettings.createDbDirectory()) { + Assert.fail("Failed to create central repo directory."); + } + + RdbmsCentralRepoFactory factory = new RdbmsCentralRepoFactory(CentralRepoPlatforms.SQLITE, sqliteSettings); + if (!factory.initializeDatabaseSchema() || !factory.insertDefaultDatabaseContent()) { + Assert.fail("Failed to initialize central repo database"); + } + + sqliteSettings.saveSettings(); + CentralRepoDbUtil.setUseCentralRepo(true); + CentralRepoDbManager.saveDbChoice(CentralRepoDbChoice.SQLITE); + + Path crDbFilePath = Paths.get(testDirectory.toString(), CR_DB_NAME); + if (!Files.exists(crDbFilePath)) { + Assert.fail("Failed to create central repo database, should be located at + " + crDbFilePath); + } + } + + // This function is run after every test, NOT after the entire collection of + // tests defined in the class are run. + @Override + public void tearDown() throws CentralRepoException, IOException { + // Close and delete the test case and central repo db + if (CentralRepository.isEnabled()) { + CentralRepository.getInstance().shutdownConnections(); + } + FileUtils.deleteDirectory(testDirectory.toFile()); + } + + public void testPredefinedAccountTypes() { + for (Account.Type expectedAccountType : Account.Type.PREDEFINED_ACCOUNT_TYPES) { + // Skip DEVICE account, should not be in central repository. + if(expectedAccountType == Account.Type.DEVICE) continue; + + try { + CentralRepoAccountType crAccountType = CentralRepository.getInstance() + .getAccountTypeByName(expectedAccountType.getTypeName()); + + Account.Type actualAccountType = crAccountType.getAcctType(); + Assert.assertEquals(expectedAccountType, actualAccountType); + } catch (CentralRepoException ex) { + Assert.fail("Didn't expect an exception here. Exception: " + ex); + } + } + } + + public void testRejectionOfDeviceAccountType() { + try { + Account.Type deviceAccount = Account.Type.DEVICE; + CentralRepository.getInstance() + .getAccountTypeByName(deviceAccount.getTypeName()); + Assert.fail("Expected an exception from getAccountTypeByName() when" + + " querying the device account type"); + } catch (CentralRepoException ex) { + // Pass + } + } + + public void testNonExistentAccountType() { + try { + CentralRepository.getInstance() + .getAccountTypeByName("NotARealAccountType"); + Assert.fail("Expected an exception from getAccountTypeByName()" + + " when querying a non-existent account type"); + } catch (CentralRepoException ex) { + // Pass + } + } + + public void testCreatingAccount() { + try { + Account.Type facebookAccountType = Account.Type.FACEBOOK; + CentralRepoAccountType expectedAccountType = CentralRepository.getInstance() + .getAccountTypeByName(facebookAccountType.getTypeName()); + + // Create the account + CentralRepository.getInstance() + .getOrCreateAccount(expectedAccountType, "+1 401-231-2552"); + } catch (CentralRepoException ex) { + Assert.fail("Didn't expect an exception here. Exception: " + ex); + } + } + + public void testRetreivingAnAccount() { + try { + Account.Type facebookAccountType = Account.Type.FACEBOOK; + CentralRepoAccountType expectedAccountType = CentralRepository + .getInstance() + .getAccountTypeByName(facebookAccountType.getTypeName()); + + // Create the account + CentralRepository.getInstance() + .getOrCreateAccount(expectedAccountType, "+1 441-231-2552"); + + // Retrieve the account + CentralRepoAccount actualAccount = CentralRepository.getInstance() + .getOrCreateAccount(expectedAccountType, "+1 441-231-2552"); + + Assert.assertEquals(expectedAccountType, actualAccount.getAccountType()); + Assert.assertEquals("+1 441-231-2552", actualAccount.getTypeSpecificId()); + } catch (CentralRepoException ex) { + Assert.fail("Didn't expect an exception here. Exception: " + ex); + } + } +} diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java index 0016428a0d..bf71e5f6a7 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoDatamodelTest.java @@ -123,8 +123,9 @@ public class CentralRepoDatamodelTest extends TestCase { assertTrue("Failed to created central repo directory " + dbSettingsSqlite.getDbDirectory(), dbSettingsSqlite.dbDirectoryExists()); - boolean result = dbSettingsSqlite.initializeDatabaseSchema() - && dbSettingsSqlite.insertDefaultDatabaseContent(); + RdbmsCentralRepoFactory factory = new RdbmsCentralRepoFactory(CentralRepoPlatforms.SQLITE, dbSettingsSqlite); + boolean result = factory.initializeDatabaseSchema() + && factory.insertDefaultDatabaseContent(); assertTrue("Failed to initialize central repo database", result); diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonpropertiessearch/IngestedWithHashAndFileTypeIntraCaseTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonpropertiessearch/IngestedWithHashAndFileTypeIntraCaseTest.java index 457001a540..bc208b7f2f 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonpropertiessearch/IngestedWithHashAndFileTypeIntraCaseTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonpropertiessearch/IngestedWithHashAndFileTypeIntraCaseTest.java @@ -126,7 +126,7 @@ public class IngestedWithHashAndFileTypeIntraCaseTest extends NbTestCase { assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET3, 0)); assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET4, 0)); - } catch (NoCurrentCaseException | TskCoreException | SQLException | CentralRepoExceptionex) { + } catch (NoCurrentCaseException | TskCoreException | SQLException | CentralRepoException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex.getMessage()); } @@ -167,7 +167,7 @@ public class IngestedWithHashAndFileTypeIntraCaseTest extends NbTestCase { assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET3, 0)); assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET4, 0)); - } catch (NoCurrentCaseException | TskCoreException | SQLException | CentralRepoExceptionex) { + } catch (NoCurrentCaseException | TskCoreException | SQLException | CentralRepoException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex.getMessage()); } @@ -208,7 +208,7 @@ public class IngestedWithHashAndFileTypeIntraCaseTest extends NbTestCase { assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET3, 0)); assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET4, 0)); - } catch (NoCurrentCaseException | TskCoreException | SQLException | CentralRepoExceptionex) { + } catch (NoCurrentCaseException | TskCoreException | SQLException | CentralRepoException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex.getMessage()); } @@ -250,7 +250,7 @@ public class IngestedWithHashAndFileTypeIntraCaseTest extends NbTestCase { assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET3, 0)); assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET4, 0)); - } catch (NoCurrentCaseException | TskCoreException | SQLException | CentralRepoExceptionex) { + } catch (NoCurrentCaseException | TskCoreException | SQLException | CentralRepoException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex.getMessage()); } @@ -292,7 +292,7 @@ public class IngestedWithHashAndFileTypeIntraCaseTest extends NbTestCase { assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET3, 0)); assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET4, 0)); - } catch (NoCurrentCaseException | TskCoreException | SQLException | CentralRepoExceptionex) { + } catch (NoCurrentCaseException | TskCoreException | SQLException | CentralRepoException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex.getMessage()); } @@ -334,7 +334,7 @@ public class IngestedWithHashAndFileTypeIntraCaseTest extends NbTestCase { assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET3, 0)); assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET4, 0)); - } catch (NoCurrentCaseException | TskCoreException | SQLException | CentralRepoExceptionex) { + } catch (NoCurrentCaseException | TskCoreException | SQLException | CentralRepoException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex.getMessage()); } @@ -376,7 +376,7 @@ public class IngestedWithHashAndFileTypeIntraCaseTest extends NbTestCase { assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET3, 0)); assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET4, 0)); - } catch (NoCurrentCaseException | TskCoreException | SQLException | CentralRepoExceptionex) { + } catch (NoCurrentCaseException | TskCoreException | SQLException | CentralRepoException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex.getMessage()); } @@ -417,7 +417,7 @@ public class IngestedWithHashAndFileTypeIntraCaseTest extends NbTestCase { assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET3, 0)); assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET4, 0)); - } catch (NoCurrentCaseException | TskCoreException | SQLException | CentralRepoExceptionex) { + } catch (NoCurrentCaseException | TskCoreException | SQLException | CentralRepoException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex.getMessage()); } @@ -458,7 +458,7 @@ public class IngestedWithHashAndFileTypeIntraCaseTest extends NbTestCase { assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET3, 0)); assertTrue(verifyInstanceExistanceAndCount(files, objectIdToDataSource, EMPTY, SET4, 0)); - } catch (NoCurrentCaseException | TskCoreException | SQLException | CentralRepoExceptionex) { + } catch (NoCurrentCaseException | TskCoreException | SQLException | CentralRepoException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex.getMessage()); } diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonpropertiessearch/InterCaseTestUtils.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonpropertiessearch/InterCaseTestUtils.java index bd297579a7..cb270a6082 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonpropertiessearch/InterCaseTestUtils.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonpropertiessearch/InterCaseTestUtils.java @@ -58,7 +58,6 @@ import org.sleuthkit.autopsy.modules.dataSourceIntegrity.DataSourceIntegrityModu import org.sleuthkit.autopsy.modules.embeddedfileextractor.EmbeddedFileExtractorModuleFactory; import org.sleuthkit.autopsy.modules.exif.ExifParserModuleFactory; import org.sleuthkit.autopsy.modules.fileextmismatch.FileExtMismatchDetectorModuleFactory; -import org.sleuthkit.autopsy.modules.iOS.iOSModuleFactory; import org.sleuthkit.autopsy.modules.interestingitems.InterestingItemsIngestModuleFactory; import org.sleuthkit.autopsy.modules.photoreccarver.PhotoRecCarverIngestModuleFactory; import org.sleuthkit.autopsy.modules.vmextractor.VMExtractorIngestModuleFactory; @@ -170,7 +169,6 @@ class InterCaseTestUtils { this.imageDSProcessor = new ImageDSProcessor(); final IngestModuleTemplate exifTemplate = IngestUtils.getIngestModuleTemplate(new ExifParserModuleFactory()); - final IngestModuleTemplate iOsTemplate = IngestUtils.getIngestModuleTemplate(new iOSModuleFactory()); final IngestModuleTemplate embeddedFileExtractorTemplate = IngestUtils.getIngestModuleTemplate(new EmbeddedFileExtractorModuleFactory()); final IngestModuleTemplate interestingItemsTemplate = IngestUtils.getIngestModuleTemplate(new InterestingItemsIngestModuleFactory()); final IngestModuleTemplate mimeTypeLookupTemplate = IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory()); @@ -204,7 +202,6 @@ class InterCaseTestUtils { //kitchen sink ArrayList kitchenSink = new ArrayList<>(); kitchenSink.add(exifTemplate); - kitchenSink.add(iOsTemplate); kitchenSink.add(embeddedFileExtractorTemplate); kitchenSink.add(interestingItemsTemplate); kitchenSink.add(mimeTypeLookupTemplate); @@ -223,7 +220,7 @@ class InterCaseTestUtils { this.kitchenShink = new IngestJobSettings(InterCaseTestUtils.class.getCanonicalName(), IngestType.ALL_MODULES, kitchenSink); try { - Collection types = CentralRepository.getInstance().getCorrelationTypes(); + Collection types = CentralRepository.getInstance().getDefinedCorrelationTypes(); //TODO use ids instead of strings FILE_TYPE = types.stream().filter(type -> type.getDisplayName().equals("Files")).findAny().get(); diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonpropertiessearch/MatchesInAtLeastTwoSourcesIntraCaseTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonpropertiessearch/MatchesInAtLeastTwoSourcesIntraCaseTest.java index 4848064308..e2d636c4e9 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonpropertiessearch/MatchesInAtLeastTwoSourcesIntraCaseTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/commonpropertiessearch/MatchesInAtLeastTwoSourcesIntraCaseTest.java @@ -121,7 +121,7 @@ public class MatchesInAtLeastTwoSourcesIntraCaseTest extends NbTestCase { assertTrue(IntraCaseTestUtils.verifyInstanceExistanceAndCount(files, dataSources, EMPTY, SET1, 0)); assertTrue(IntraCaseTestUtils.verifyInstanceExistanceAndCount(files, dataSources, EMPTY, SET4, 0)); - } catch (NoCurrentCaseException | TskCoreException | SQLException | CentralRepoExceptionex) { + } catch (NoCurrentCaseException | TskCoreException | SQLException | CentralRepoException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex.getMessage()); } diff --git a/CoreLibs/ivy.xml b/CoreLibs/ivy.xml index 6819dac82d..4853d1f90e 100644 --- a/CoreLibs/ivy.xml +++ b/CoreLibs/ivy.xml @@ -14,8 +14,7 @@ - - + @@ -73,8 +72,5 @@ - - - diff --git a/CoreLibs/nbproject/project.properties b/CoreLibs/nbproject/project.properties index 677f823e67..42f8292d5b 100644 --- a/CoreLibs/nbproject/project.properties +++ b/CoreLibs/nbproject/project.properties @@ -21,7 +21,6 @@ file.reference.dom4j-1.6.1.jar=release/modules/ext/dom4j-1.6.1.jar file.reference.geronimo-jms_1.1_spec-1.0.jar=release/modules/ext/geronimo-jms_1.1_spec-1.0.jar file.reference.gson-2.8.5.jar=release/modules/ext/gson-2.8.5.jar file.reference.gst1-java-core-1.0.0.jar=release\\modules\\ext\\gst1-java-core-1.0.0.jar -file.reference.jna-3.4.0.jar=release/modules/ext/jna-3.4.0.jar file.reference.guava-19.0.jar=release/modules/ext/guava-19.0.jar file.reference.imageio-bmp-3.2.jar=release/modules/ext/imageio-bmp-3.2.jar file.reference.imageio-core-3.2.jar=release/modules/ext/imageio-core-3.2.jar @@ -44,6 +43,8 @@ file.reference.jfxtras-common-8.0-r4.jar=release/modules/ext/jfxtras-common-8.0- file.reference.jfxtras-controls-8.0-r4.jar=release/modules/ext/jfxtras-controls-8.0-r4.jar file.reference.jfxtras-fxml-8.0-r4.jar=release/modules/ext/jfxtras-fxml-8.0-r4.jar file.reference.jna-3.4.0.jar=release/modules/ext/jna-3.4.0.jar +file.reference.jna-5.5.0.jar=release\\modules\\ext\\jna-5.5.0.jar +file.reference.jna-platform-5.5.0.jar=release\\modules\\ext\\jna-platform-5.5.0.jar file.reference.joda-time-2.4.jar=release/modules/ext/joda-time-2.4.jar file.reference.jsr305-1.3.9.jar=release/modules/ext/jsr305-1.3.9.jar file.reference.LGoodDatePicker-10.3.1.jar=release/modules/ext/LGoodDatePicker-10.3.1.jar @@ -52,7 +53,6 @@ file.reference.logkit-1.0.1.jar=release/modules/ext/logkit-1.0.1.jar file.reference.mail-1.4.3.jar=release/modules/ext/mail-1.4.3.jar file.reference.opencv-248.jar=release/modules/ext/opencv-248.jar file.reference.openjfx-dialogs-1.0.2.jar=release/modules/ext/openjfx-dialogs-1.0.3.jar -file.reference.platform-3.4.0.jar=release/modules/ext/platform-3.4.0.jar file.reference.poi-4.0.1.jar=release\\modules\\ext\\poi-4.0.1.jar file.reference.poi-excelant-4.0.1.jar=release\\modules\\ext\\poi-excelant-4.0.1.jar file.reference.poi-ooxml-4.0.1.jar=release\\modules\\ext\\poi-ooxml-4.0.1.jar diff --git a/CoreLibs/nbproject/project.xml b/CoreLibs/nbproject/project.xml index 0498669b04..d5169a8965 100644 --- a/CoreLibs/nbproject/project.xml +++ b/CoreLibs/nbproject/project.xml @@ -806,10 +806,6 @@ ext/sigar-1.6.4.jar release/modules/ext/sigar-1.6.4.jar
- - ext/jna-3.4.0.jar - release/modules/ext/jna-3.4.0.jar - ext/gson-2.8.5.jar release/modules/ext/gson-2.8.5.jar @@ -902,6 +898,10 @@ ext/commons-csv-1.4.jar release/modules/ext/commons-csv-1.4.jar + + ext/jna-5.5.0.jar + release/modules/ext/jna-5.5.0.jar + ext/imageio-sgi-3.2.jar release/modules/ext/imageio-sgi-3.2.jar @@ -946,10 +946,6 @@ ext/imageio-bmp-3.2.jar release/modules/ext/imageio-bmp-3.2.jar - - ext/platform-3.4.0.jar - release/modules/ext/platform-3.4.0.jar - ext/commons-lang-2.6.jar release/modules/ext/commons-lang-2.6.jar @@ -1018,6 +1014,10 @@ ext/dom4j-1.6.1.jar release/modules/ext/dom4j-1.6.1.jar + + ext/jna-platform-5.5.0.jar + release/modules/ext/jna-platform-5.5.0.jar + ext/imageio-metadata-3.2.jar release/modules/ext/imageio-metadata-3.2.jar diff --git a/InternalPythonModules/android/browserlocation.py b/InternalPythonModules/android/browserlocation.py index 84c2a601e7..c202c36d2a 100644 --- a/InternalPythonModules/android/browserlocation.py +++ b/InternalPythonModules/android/browserlocation.py @@ -95,7 +95,7 @@ class BrowserLocationAnalyzer(general.AndroidComponentAnalyzer): longitude = Double.valueOf(resultSet.getString("longitude")) attributes = ArrayList() - artifact = abstractFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT) + artifact = abstractFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK) attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE, general.MODULE_NAME, latitude)) attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE, general.MODULE_NAME, longitude)) attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, general.MODULE_NAME, timestamp)) diff --git a/InternalPythonModules/android/cachelocation.py b/InternalPythonModules/android/cachelocation.py index 1fb162a817..3697fb44b0 100644 --- a/InternalPythonModules/android/cachelocation.py +++ b/InternalPythonModules/android/cachelocation.py @@ -41,6 +41,7 @@ from org.sleuthkit.datamodel import TskCoreException import traceback import general +import struct """ Parses cache files that Android maintains for Wifi and cell towers. Adds GPS points to blackboard. @@ -74,60 +75,24 @@ class CacheLocationAnalyzer(general.AndroidComponentAnalyzer): def __findGeoLocationsInFile(self, file, abstractFile): - tempBytes = bytearray([0] * 2) # will temporarily hold bytes to be converted into the correct data types - try: - inputStream = FileInputStream(file) - - inputStream.read(tempBytes) # version - - tempBytes = bytearray([0] * 2) - inputStream.read(tempBytes) # number of location entries - - iterations = BigInteger(tempBytes).intValue() - - for i in range(iterations): # loop through every entry - tempBytes = bytearray([0] * 2) - inputStream.read(tempBytes) - - tempBytes = bytearray([0]) - inputStream.read(tempBytes) - - while BigInteger(tempBytes).intValue() != 0: # pass through non important values until the start of accuracy(around 7-10 bytes) - if 0 > inputStream.read(tempBytes): - break # we've passed the end of the file, so stop - - tempBytes = bytearray([0] * 3) - inputStream.read(tempBytes) - if BigInteger(tempBytes).intValue() <= 0: # This refers to a location that could not be calculated - tempBytes = bytearray([0] * 28) # read rest of the row's bytes - inputStream.read(tempBytes) - continue - accuracy = "" + BigInteger(tempBytes).intValue() - - tempBytes = bytearray([0] * 4) - inputStream.read(tempBytes) - confidence = "" + BigInteger(tempBytes).intValue() - - tempBytes = bytearray([0] * 8) - inputStream.read(tempBytes) - latitude = CacheLocationAnalyzer.toDouble(bytes) - - tempBytes = bytearray([0] * 8) - inputStream.read(tempBytes) - longitude = CacheLocationAnalyzer.toDouble(bytes) - - tempBytes = bytearray([0] * 8) - inputStream.read(tempBytes) - timestamp = BigInteger(tempBytes).longValue() / 1000 + # code to parse the cache.wifi and cache.cell taken from https://forensics.spreitzenbarth.de/2011/10/28/decoding-cache-cell-and-cache-wifi-files/ + cacheFile = open(str(file), 'rb') + (version, entries) = struct.unpack('>hh', cacheFile.read(4)) + i = 0 + while i < entries: + key = cacheFile.read(struct.unpack('>h', cacheFile.read(2))[0]) + (accuracy, confidence, latitude, longitude, readtime) = struct.unpack('>iiddQ', cacheFile.read(32)) + timestamp = readtime/1000 + i = i + 1 attributes = ArrayList() - artifact = abstractFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT) - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE, AndroidAnalyzer.MODULE_NAME, latitude)) - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE, AndroidAnalyzer.MODULE_NAME, longitude)) - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, AndroidModuleFactorymodule.Name, timestamp)) - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME, AndroidAnalyzer.MODULE_NAME, - file.getName() + "Location History")) + artifact = abstractFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK) + attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE, general.MODULE_NAME, latitude)) + attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE, general.MODULE_NAME, longitude)) + attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, general.MODULE_NAME, timestamp)) + attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME, general.MODULE_NAME, + abstractFile.getName() + " Location History")) artifact.addAttributes(attributes) #Not storing these for now. @@ -136,15 +101,13 @@ class CacheLocationAnalyzer(general.AndroidComponentAnalyzer): try: # index the artifact for keyword search blackboard = Case.getCurrentCase().getSleuthkitCase().getBlackboard() - blackboard.postArtifact(artifact, MODULE_NAME) + blackboard.postArtifact(artifact, general.MODULE_NAME) except Blackboard.BlackboardException as ex: self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + str(artifact.getArtifactID()), ex) self._logger.log(Level.SEVERE, traceback.format_exc()) MessageNotifyUtil.Notify.error("Failed to index GPS trackpoint artifact for keyword search.", artifact.getDisplayName()) + cacheFile.close() - except SQLException as ex: - # Unable to execute Cached GPS locations SQL query against database. - pass except Exception as ex: self._logger.log(Level.SEVERE, "Error parsing Cached GPS locations to blackboard", ex) self._logger.log(Level.SEVERE, traceback.format_exc()) diff --git a/InternalPythonModules/android/calllog.py b/InternalPythonModules/android/calllog.py index 71c5f112ad..5b3faf0697 100644 --- a/InternalPythonModules/android/calllog.py +++ b/InternalPythonModules/android/calllog.py @@ -105,6 +105,9 @@ class CallLogAnalyzer(general.AndroidComponentAnalyzer): timeStamp = resultSet.getLong("date") / 1000 number = resultSet.getString("number") + if not general.isValidPhoneNumer(number): + number = None + duration = resultSet.getLong("duration") # duration of call is in seconds name = resultSet.getString("name") # name of person dialed or called. None if unregistered diff --git a/InternalPythonModules/android/general.py b/InternalPythonModules/android/general.py index e8cb199b8d..daa789a57c 100644 --- a/InternalPythonModules/android/general.py +++ b/InternalPythonModules/android/general.py @@ -1,7 +1,7 @@ """ Autopsy Forensic Browser -Copyright 2016 Basis Technology Corp. +Copyright 2016-2020 Basis Technology Corp. Contact: carrier sleuthkit org Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,8 +15,12 @@ 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. + """ +from org.sleuthkit.datamodel import TskCoreException +from org.sleuthkit.datamodel import CommunicationsUtils + MODULE_NAME = "Android Analyzer" """ @@ -37,3 +41,23 @@ def appendAttachmentList(msgBody, attachmentsList): body = body + "\n".join(list(filter(None, attachmentsList))) return body + +""" +Checks if the given string might be a phone number. +""" +def isValidPhoneNumer(data): + try: + return CommunicationsUtils.normalizePhoneNum(data) is not None + except TskCoreException as ex: + return False + + +""" +Checks if the given string is a valid email address. +""" +def isValidEmailAddress(data): + try: + return CommunicationsUtils.normalizeEmailAddress(data) is not None + except TskCoreException as ex: + return False + diff --git a/InternalPythonModules/android/oruxmaps.py b/InternalPythonModules/android/oruxmaps.py index 4577ea5f2f..7a939e1972 100644 --- a/InternalPythonModules/android/oruxmaps.py +++ b/InternalPythonModules/android/oruxmaps.py @@ -44,6 +44,8 @@ from org.sleuthkit.datamodel import BlackboardAttribute from org.sleuthkit.datamodel import Content from org.sleuthkit.datamodel import TskCoreException from org.sleuthkit.datamodel.Blackboard import BlackboardException +from org.sleuthkit.datamodel.blackboardutils import GeoArtifactsHelper +from org.sleuthkit.datamodel.blackboardutils.attributes import TskGeoTrackpointsUtil import traceback import general @@ -68,7 +70,10 @@ class OruxMapsAnalyzer(general.AndroidComponentAnalyzer): try: current_case = Case.getCurrentCaseThrows() - poiQueryString = "SELECT poilat, poilon, poitime, poiname FROM pois" + skCase = Case.getCurrentCase().getSleuthkitCase() + geoArtifactHelper = GeoArtifactsHelper(skCase, self._MODULE_NAME, self._PROGRAM_NAME, oruxMapsTrackpointsDb.getDBFile()) + + poiQueryString = "SELECT poilat, poilon, poialt, poitime, poiname FROM pois" poisResultSet = oruxMapsTrackpointsDb.runQuery(poiQueryString) abstractFile = oruxMapsTrackpointsDb.getDBFile() if poisResultSet is not None: @@ -77,12 +82,14 @@ class OruxMapsAnalyzer(general.AndroidComponentAnalyzer): longitude = poisResultSet.getDouble("poilon") time = poisResultSet.getLong("poitime") / 1000 # milliseconds since unix epoch name = poisResultSet.getString("poiname") + altitude = poisResultSet.getDouble("poialt") attributes = ArrayList() - artifact = abstractFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT) + artifact = abstractFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK) attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, self._MODULE_NAME, time)) attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE, self._MODULE_NAME, latitude)) attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE, self._MODULE_NAME, longitude)) + attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE, self._MODULE_NAME, altitude)) attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME, self._MODULE_NAME, name)) attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME, self._MODULE_NAME, self._PROGRAM_NAME)) @@ -96,32 +103,61 @@ class OruxMapsAnalyzer(general.AndroidComponentAnalyzer): self._logger.log(Level.SEVERE, traceback.format_exc()) MessageNotifyUtil.Notify.error("Failed to index trackpoint artifact for keyword search.", artifact.getDisplayName()) - trackpointsQueryString = "SELECT trkptlat, trkptlon, trkpttime FROM trackpoints" - trackpointsResultSet = oruxMapsTrackpointsDb.runQuery(trackpointsQueryString) - if trackpointsResultSet is not None: - while trackpointsResultSet.next(): - latitude = trackpointsResultSet.getDouble("trkptlat") - longitude = trackpointsResultSet.getDouble("trkptlon") - time = trackpointsResultSet.getLong("trkpttime") / 1000 # milliseconds since unix epoch - name = "" - attributes = ArrayList() - artifact = abstractFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT) - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, self._MODULE_NAME, time)) - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE, self._MODULE_NAME, latitude)) - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE, self._MODULE_NAME, longitude)) - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME, self._MODULE_NAME, name)) - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME, self._MODULE_NAME, self._PROGRAM_NAME)) - - artifact.addAttributes(attributes) - try: - # index the artifact for keyword search - blackboard = Case.getCurrentCase().getSleuthkitCase().getBlackboard() - blackboard.postArtifact(artifact, self._MODULE_NAME) - except Blackboard.BlackboardException as ex: - self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + str(artifact.getArtifactID()), ex) - self._logger.log(Level.SEVERE, traceback.format_exc()) - MessageNotifyUtil.Notify.error("Failed to index trackpoint artifact for keyword search.", artifact.getDisplayName()) + # tracks -> segments -> trackpoints + # + # The reason that the track and the segment are put into arrays is that once the segment query is run an error occurs that it cannot find the + # trackname column in the track query. This is avoided if all the tracks/segments are found and put into an array(s) that can then be processed all at once. + trackQueryString = "SELECT _id, trackname, trackciudad FROM tracks" + trackResultSet = oruxMapsTrackpointsDb.runQuery(trackQueryString) + if trackResultSet is not None: + trackResults = ArrayList() + while trackResultSet.next(): + tempTrack = ArrayList() + trackName = trackResultSet.getString("trackname") + " - " + trackResultSet.getString("trackciudad") + trackId = str(trackResultSet.getInt("_id")) + tempTrack.append(trackId) + tempTrack.append(trackName) + trackResults.append(tempTrack) + for trackResult in trackResults: + trackId = trackResult[0] + trackName = trackResult[1] + segmentQueryString = "SELECT _id, segname FROM segments WHERE segtrack = " + trackId + segmentResultSet = oruxMapsTrackpointsDb.runQuery(segmentQueryString) + if segmentResultSet is not None: + segmentResults = ArrayList() + while segmentResultSet.next(): + segmentName = trackName + " - " + segmentResultSet.getString("segname") + segmentId = str(segmentResultSet.getInt("_id")) + tempSegment = ArrayList() + tempSegment.append(segmentId) + tempSegment.append(segmentName) + segmentResults.append(tempSegment) + for segmentResult in segmentResults: + segmentId = segmentResult[0] + segmentName = segmentResult[1] + trackpointsQueryString = "SELECT trkptlat, trkptlon, trkptalt, trkpttime FROM trackpoints WHERE trkptseg = " + segmentId + trackpointsResultSet = oruxMapsTrackpointsDb.runQuery(trackpointsQueryString) + if trackpointsResultSet is not None: + geoPointList = TskGeoTrackpointsUtil.GeoTrackPointList() + while trackpointsResultSet.next(): + latitude = trackpointsResultSet.getDouble("trkptlat") + longitude = trackpointsResultSet.getDouble("trkptlon") + altitude = trackpointsResultSet.getDouble("trkptalt") + time = trackpointsResultSet.getLong("trkpttime") / 1000 # milliseconds since unix epoch + + geoPointList.addPoint(TskGeoTrackpointsUtil.GeoTrackPointList.GeoTrackPoint(latitude, longitude, altitude, segmentName, 0, 0, 0, time)) + + try: + geoartifact = geoArtifactHelper.addTrack(segmentName, geoPointList, None) + except Blackboard.BlackboardException as ex: + self._logger.log(Level.SEVERE, "Error using geo artifact helper with blackboard", ex) + self._logger.log(Level.SEVERE, traceback.format_exc()) + MessageNotifyUtil.Notify.error("Failed to add track artifact.", "geoArtifactHelper") + except TskCoreException as e: + self._logger.log(Level.SEVERE, "Error using geo artifact helper with TskCoreException", ex) + self._logger.log(Level.SEVERE, traceback.format_exc()) + MessageNotifyUtil.Notify.error("Failed to add track artifact with TskCoreException.", "geoArtifactHelper") except SQLException as ex: self._logger.log(Level.WARNING, "Error processing query result for Orux Map trackpoints.", ex) diff --git a/InternalPythonModules/android/textnow.py b/InternalPythonModules/android/textnow.py index 49ab3325c2..e05763cae9 100644 --- a/InternalPythonModules/android/textnow.py +++ b/InternalPythonModules/android/textnow.py @@ -109,13 +109,21 @@ class TextNowAnalyzer(general.AndroidComponentAnalyzer): try: contacts_parser = TextNowContactsParser(textnow_db) while contacts_parser.next(): - helper.addContact( - contacts_parser.get_contact_name(), - contacts_parser.get_phone(), - contacts_parser.get_home_phone(), - contacts_parser.get_mobile_phone(), - contacts_parser.get_email() - ) + name = contacts_parser.get_contact_name() + phone = contacts_parser.get_phone() + home_phone = contacts_parser.get_home_phone() + mobile_phone = contacts_parser.get_mobile_phone() + email = contacts_parser.get_email() + + # add contact if we have at least one valid phone/email + if phone or home_phone or mobile_phone or email: + helper.addContact( + name, + phone, + home_phone, + mobile_phone, + email + ) contacts_parser.close() except SQLException as ex: #Error parsing TextNow db @@ -277,7 +285,13 @@ class TextNowContactsParser(TskContactsParser): return self.result_set.getString("name") def get_phone(self): - return self.result_set.getString("number") + number = self.result_set.getString("number") + return (number if general.isValidPhoneNumer(number) else None) + + def get_email(self): + # occasionally the 'number' column may have an email address instead + value = self.result_set.getString("number") + return (value if general.isValidEmailAddress(value) else None) class TextNowMessagesParser(TskMessagesParser): """ diff --git a/InternalPythonModules/android/whatsapp.py b/InternalPythonModules/android/whatsapp.py index 85d71983d2..5346545450 100644 --- a/InternalPythonModules/android/whatsapp.py +++ b/InternalPythonModules/android/whatsapp.py @@ -172,14 +172,22 @@ class WhatsAppAnalyzer(general.AndroidComponentAnalyzer): try: contacts_parser = WhatsAppContactsParser(contacts_db, self._PARSER_NAME) while contacts_parser.next(): - helper.addContact( - contacts_parser.get_contact_name(), - contacts_parser.get_phone(), - contacts_parser.get_home_phone(), - contacts_parser.get_mobile_phone(), - contacts_parser.get_email(), - contacts_parser.get_other_attributes() - ) + name = contacts_parser.get_contact_name() + phone = contacts_parser.get_phone() + home_phone = contacts_parser.get_home_phone() + mobile_phone = contacts_parser.get_mobile_phone() + email = contacts_parser.get_email() + + # add contact if we have at least one valid phone/email + if phone or home_phone or mobile_phone or email: + helper.addContact( + name, + phone, + home_phone, + mobile_phone, + email, + contacts_parser.get_other_attributes() + ) contacts_parser.close() except SQLException as ex: self._logger.log(Level.WARNING, "Error querying the whatsapp database for contacts.", ex) @@ -426,8 +434,14 @@ class WhatsAppContactsParser(TskContactsParser): return self.result_set.getString("name") def get_phone(self): - return self.result_set.getString("number") + number = self.result_set.getString("number") + return (number if general.isValidPhoneNumer(number) else None) + def get_email(self): + # occasionally the 'number' column may have an email address instead + value = self.result_set.getString("number") + return (value if general.isValidEmailAddress(value) else None) + def get_other_attributes(self): return [BlackboardAttribute( BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ID, diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties index 21cd0d02b2..3dd312becc 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties @@ -6,13 +6,6 @@ OptionsCategory_Name_KeywordSearchOptions=Keyword Search OptionsCategory_Keywords_KeywordSearchOptions=Keyword Search ListBundleName=Keyword Lists ListBundleConfig=Keyword List Configuration -ExtractedContentPanel.hitLabel.text=Matches on page: -ExtractedContentPanel.hitCountLabel.text=- -ExtractedContentPanel.hitOfLabel.text=of -ExtractedContentPanel.hitTotalLabel.text=- -ExtractedContentPanel.hitButtonsLabel.text=Match -ExtractedContentPanel.hitPreviousButton.text= -ExtractedContentPanel.hitNextButton.text= ExtractedContentPanel.copyMenuItem.text=Copy ExtractedContentPanel.selectAllMenuItem.text=Select All KeywordSearchEditListPanel.saveListButton.text=Copy List @@ -33,7 +26,6 @@ KeywordSearchListsManagementPanel.importButton.text=Import List KeywordSearchListsViewerPanel.searchAddButton.text=Search KeywordSearchListsViewerPanel.manageListsButton.text=Manage Lists KeywordSearchListsViewerPanel.ingestIndexLabel.text=Files Indexed: -ExtractedContentPanel.hitLabel.toolTipText= KeywordSearchEditListPanel.ingestMessagesCheckbox.text=Send ingest inbox messages for each hit KeywordSearchEditListPanel.ingestMessagesCheckbox.toolTipText=Send messages during ingest when hits on keywords from this list occur KeywordSearchEditListPanel.keywordOptionsLabel.text=Keyword Options @@ -299,15 +291,6 @@ SolrSearchService.ServiceName=Solr Keyword Search Service SolrSearchService.IndexReadOnlyDialog.title=Text Index Is Read-Only SolrSearchService.IndexReadOnlyDialog.msg=The text index for this case is read-only.
You will be able to see existing keyword search results and perform exact match and substring match keyword searches,
but you will not be able to add new text to the index or perform regex searches. You may instead open the case
with your previous version of this application. SolrSearchService.DeleteDataSource.msg=Error Deleting Solr data for data source id {0} -ExtractedContentPanel.jLabel1.text=Text Source: -ExtractedContentPanel.pagePreviousButton.actionCommand=pagePreviousButton -ExtractedContentPanel.pagePreviousButton.text= -ExtractedContentPanel.pageNextButton.text= -ExtractedContentPanel.pageCurLabel.text=- -ExtractedContentPanel.pageOfLabel.text=of -ExtractedContentPanel.pageTotalLabel.text=- -ExtractedContentPanel.pageButtonsLabel.text=Page -ExtractedContentPanel.pagesLabel.text=Page: DropdownSingleTermSearchPanel.dataSourceCheckBox.text=Restrict search to the selected data sources: DropdownListSearchPanel.dataSourceCheckBox.text=Restrict search to the selected data sources: DropdownSingleTermSearchPanel.ingestIndexLabel.text=Files Indexed: @@ -318,3 +301,21 @@ DropdownListSearchPanel.jSaveSearchResults.text=Save search results GlobalEditListPanel.ingestWarningLabel.text=Ingest is ongoing, some settings will be unavailable until it finishes. KeywordSearchGlobalLanguageSettingsPanel.ingestWarningLabel.text=Ingest is ongoing, some settings will be unavailable until it finishes. KeywordSearchGlobalSearchSettingsPanel.ingestWarningLabel.text=Ingest is ongoing, some settings will be unavailable until it finishes. +ExtractedContentPanel.hitCountLabel.text=- +ExtractedContentPanel.hitPreviousButton.text= +ExtractedContentPanel.hitTotalLabel.text=- +ExtractedContentPanel.hitOfLabel.text=of +ExtractedContentPanel.hitNextButton.text= +ExtractedContentPanel.hitButtonsLabel.text=Match +ExtractedContentPanel.hitLabel.toolTipText= +ExtractedContentPanel.hitLabel.text=Matches on page: +ExtractedContentPanel.pageCurLabel.text=- +ExtractedContentPanel.pagePreviousButton.actionCommand=pagePreviousButton +ExtractedContentPanel.pagePreviousButton.text= +ExtractedContentPanel.pageNextButton.text= +ExtractedContentPanel.pagesLabel.text=Page: +ExtractedContentPanel.pageTotalLabel.text=- +ExtractedContentPanel.pageButtonsLabel.text=Page +ExtractedContentPanel.pageOfLabel.text=of +ExtractedContentPanel.jLabel1.text=Text Source: +ExtractedContentPanel.AccessibleContext.accessibleName= diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED index 27263e03ca..9aeeabc046 100755 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties-MERGED @@ -42,13 +42,6 @@ OptionsCategory_Name_KeywordSearchOptions=Keyword Search OptionsCategory_Keywords_KeywordSearchOptions=Keyword Search ListBundleName=Keyword Lists ListBundleConfig=Keyword List Configuration -ExtractedContentPanel.hitLabel.text=Matches on page: -ExtractedContentPanel.hitCountLabel.text=- -ExtractedContentPanel.hitOfLabel.text=of -ExtractedContentPanel.hitTotalLabel.text=- -ExtractedContentPanel.hitButtonsLabel.text=Match -ExtractedContentPanel.hitPreviousButton.text= -ExtractedContentPanel.hitNextButton.text= ExtractedContentPanel.copyMenuItem.text=Copy ExtractedContentPanel.selectAllMenuItem.text=Select All KeywordSearchEditListPanel.saveListButton.text=Copy List @@ -69,7 +62,6 @@ KeywordSearchListsManagementPanel.importButton.text=Import List KeywordSearchListsViewerPanel.searchAddButton.text=Search KeywordSearchListsViewerPanel.manageListsButton.text=Manage Lists KeywordSearchListsViewerPanel.ingestIndexLabel.text=Files Indexed: -ExtractedContentPanel.hitLabel.toolTipText= KeywordSearchEditListPanel.ingestMessagesCheckbox.text=Send ingest inbox messages for each hit KeywordSearchEditListPanel.ingestMessagesCheckbox.toolTipText=Send messages during ingest when hits on keywords from this list occur KeywordSearchEditListPanel.keywordOptionsLabel.text=Keyword Options @@ -358,15 +350,6 @@ SolrSearchService.ServiceName=Solr Keyword Search Service SolrSearchService.IndexReadOnlyDialog.title=Text Index Is Read-Only SolrSearchService.IndexReadOnlyDialog.msg=The text index for this case is read-only.
You will be able to see existing keyword search results and perform exact match and substring match keyword searches,
but you will not be able to add new text to the index or perform regex searches. You may instead open the case
with your previous version of this application. SolrSearchService.DeleteDataSource.msg=Error Deleting Solr data for data source id {0} -ExtractedContentPanel.jLabel1.text=Text Source: -ExtractedContentPanel.pagePreviousButton.actionCommand=pagePreviousButton -ExtractedContentPanel.pagePreviousButton.text= -ExtractedContentPanel.pageNextButton.text= -ExtractedContentPanel.pageCurLabel.text=- -ExtractedContentPanel.pageOfLabel.text=of -ExtractedContentPanel.pageTotalLabel.text=- -ExtractedContentPanel.pageButtonsLabel.text=Page -ExtractedContentPanel.pagesLabel.text=Page: DropdownSingleTermSearchPanel.dataSourceCheckBox.text=Restrict search to the selected data sources: DropdownListSearchPanel.dataSourceCheckBox.text=Restrict search to the selected data sources: DropdownSingleTermSearchPanel.ingestIndexLabel.text=Files Indexed: @@ -377,3 +360,25 @@ DropdownListSearchPanel.jSaveSearchResults.text=Save search results GlobalEditListPanel.ingestWarningLabel.text=Ingest is ongoing, some settings will be unavailable until it finishes. KeywordSearchGlobalLanguageSettingsPanel.ingestWarningLabel.text=Ingest is ongoing, some settings will be unavailable until it finishes. KeywordSearchGlobalSearchSettingsPanel.ingestWarningLabel.text=Ingest is ongoing, some settings will be unavailable until it finishes. +ExtractedContentPanel.hitCountLabel.text=- +ExtractedContentPanel.hitPreviousButton.text= +ExtractedContentPanel.hitTotalLabel.text=- +ExtractedContentPanel.hitOfLabel.text=of +ExtractedContentPanel.hitNextButton.text= +ExtractedContentPanel.hitButtonsLabel.text=Match +ExtractedContentPanel.hitLabel.toolTipText= +ExtractedContentPanel.hitLabel.text=Matches on page: +ExtractedContentPanel.pageCurLabel.text=- +ExtractedContentPanel.pagePreviousButton.actionCommand=pagePreviousButton +ExtractedContentPanel.pagePreviousButton.text= +ExtractedContentPanel.pageNextButton.text= +ExtractedContentPanel.pagesLabel.text=Page: +ExtractedContentPanel.pageTotalLabel.text=- +ExtractedContentPanel.pageButtonsLabel.text=Page +ExtractedContentPanel.pageOfLabel.text=of +ExtractedContentPanel.jLabel1.text=Text Source: +ExtractedContentPanel.AccessibleContext.accessibleName= +TextZoomPanel.zoomInButton.text= +TextZoomPanel.zoomOutButton.text= +TextZoomPanel.zoomResetButton.text=Reset +TextZoomPanel.zoomTextField.text= diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle_ja.properties b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle_ja.properties index adf1364da2..c131f5c8cf 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle_ja.properties +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle_ja.properties @@ -42,13 +42,6 @@ OptionsCategory_Name_KeywordSearchOptions=\u30ad\u30fc\u30ef\u30fc\u30c9\u691c\u OptionsCategory_Keywords_KeywordSearchOptions=\u30ad\u30fc\u30ef\u30fc\u30c9\u691c\u7d22 ListBundleName=\u30ad\u30fc\u30ef\u30fc\u30c9\u30ea\u30b9\u30c8 ListBundleConfig=\u30ad\u30fc\u30ef\u30fc\u30c9\u30ea\u30b9\u30c8\u69cb\u6210 -ExtractedContentPanel.hitLabel.text=\u30da\u30fc\u30b8\u4e0a\u306e\u4e00\u81f4\u3059\u308b\u7d50\u679c: -ExtractedContentPanel.hitCountLabel.text=- -ExtractedContentPanel.hitOfLabel.text=/ -ExtractedContentPanel.hitTotalLabel.text=- -ExtractedContentPanel.hitButtonsLabel.text=\u4e00\u81f4\u3059\u308b\u7d50\u679c -ExtractedContentPanel.hitPreviousButton.text= -ExtractedContentPanel.hitNextButton.text= ExtractedContentPanel.copyMenuItem.text=\u30b3\u30d4\u30fc ExtractedContentPanel.selectAllMenuItem.text=\u3059\u3079\u3066\u9078\u629e KeywordSearchEditListPanel.saveListButton.text=\u30ea\u30b9\u30c8\u3092\u30b3\u30d4\u30fc @@ -69,7 +62,6 @@ KeywordSearchListsManagementPanel.importButton.text=\u30ea\u30b9\u30c8\u3092\u30 KeywordSearchListsViewerPanel.searchAddButton.text=\u691c\u7d22 KeywordSearchListsViewerPanel.manageListsButton.text=\u30ea\u30b9\u30c8\u3092\u7ba1\u7406 KeywordSearchListsViewerPanel.ingestIndexLabel.text=\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u3055\u308c\u305f\u30d5\u30a1\u30a4\u30eb: -ExtractedContentPanel.hitLabel.toolTipText= KeywordSearchEditListPanel.ingestMessagesCheckbox.text=\u30d2\u30c3\u30c8\u3054\u3068\u306b\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u53d7\u4fe1\u7bb1\u306e\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u9001\u4fe1 KeywordSearchEditListPanel.ingestMessagesCheckbox.toolTipText=\u3053\u306e\u30ea\u30b9\u30c8\u306e\u30ad\u30fc\u30ef\u30fc\u30c9\u3067\u30d2\u30c3\u30c8\u304c\u767a\u751f\u3057\u305f\u3068\u304d\u306b\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u4e2d\u306b\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u9001\u4fe1 KeywordSearchEditListPanel.keywordOptionsLabel.text=\u30ad\u30fc\u30ef\u30fc\u30c9\u30aa\u30d7\u30b7\u30e7\u30f3 @@ -355,15 +347,6 @@ SolrSearchService.exceptionMessage.noIndexMetadata=\u30b1\u30fc\u30b9\u30c7\u30a SolrSearchService.ServiceName=Solr\u30ad\u30fc\u30ef\u30fc\u30c9\u691c\u7d22\u30b5\u30fc\u30d3\u30b9 SolrSearchService.IndexReadOnlyDialog.title=\u30c6\u30ad\u30b9\u30c8\u7d22\u5f15\u306f\u8aad\u307f\u53d6\u308a\u5c02\u7528\u3067\u3059 SolrSearchService.IndexReadOnlyDialog.msg=\u3053\u306e\u30b1\u30fc\u30b9\u306e\u30c6\u30ad\u30b9\u30c8\u7d22\u5f15\u306f\u8aad\u307f\u53d6\u308a\u5c02\u7528\u3067\u3059\u3002
\u30ad\u30fc\u30ef\u30fc\u30c9\u691c\u7d22\u7d50\u679c\u3092\u78ba\u8a8d\u3057\u3001\u5b8c\u5168\u4e00\u81f4\u30ad\u30fc\u30ef\u30fc\u30c9\u691c\u7d22\u3068\u90e8\u5206\u4e00\u81f4\u30ad\u30fc\u30ef\u30fc\u30c9\u691c\u7d22\u3092\u5b9f\u884c\u3067\u304d\u307e\u3059
\u304c\u3001\u7d22\u5f15\u306b\u65b0\u898f\u30c6\u30ad\u30b9\u30c8\u3092\u8ffd\u52a0\u307e\u305f\u306f\u6b63\u898f\u8868\u73fe\u691c\u7d22\u3092\u5b9f\u884c\u3067\u304d\u307e\u305b\u3093\u3002\u305d\u306e\u4ee3\u308f\u308a\u306b\u3001\u3053\u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306e\u4ee5\u524d\u306e\u30d0\u30fc\u30b8\u30e7\u30f3\u3067
\u30b1\u30fc\u30b9\u3092\u958b\u3051\u307e\u3059\u3002 -ExtractedContentPanel.jLabel1.text=\u30c6\u30ad\u30b9\u30c8\u30bd\u30fc\u30b9: -ExtractedContentPanel.pagePreviousButton.actionCommand=pagePreviousButton -ExtractedContentPanel.pagePreviousButton.text= -ExtractedContentPanel.pageNextButton.text= -ExtractedContentPanel.pageCurLabel.text=- -ExtractedContentPanel.pageOfLabel.text=/ -ExtractedContentPanel.pageTotalLabel.text=- -ExtractedContentPanel.pageButtonsLabel.text=\u30da\u30fc\u30b8 -ExtractedContentPanel.pagesLabel.text=\u30da\u30fc\u30b8: DropdownSingleTermSearchPanel.dataSourceCheckBox.text=\u9078\u629e\u3057\u305f\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u306b\u691c\u7d22\u3092\u5236\u9650: DropdownListSearchPanel.dataSourceCheckBox.text=\u9078\u629e\u3057\u305f\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u306b\u691c\u7d22\u3092\u5236\u9650: DropdownSingleTermSearchPanel.ingestIndexLabel.text=\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u3055\u308c\u305f\u30d5\u30a1\u30a4\u30eb: @@ -374,3 +357,20 @@ DropdownListSearchPanel.jSaveSearchResults.text=\u691c\u7d22\u7d50\u679c\u3092\u GlobalEditListPanel.ingestWarningLabel.text=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u304c\u9032\u884c\u4e2d\u3067\u3059\u3002\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u304c\u5b8c\u4e86\u3059\u308b\u307e\u3067\u4e00\u90e8\u306e\u8a2d\u5b9a\u3092\u5229\u7528\u3067\u304d\u307e\u305b\u3093\u3002 KeywordSearchGlobalLanguageSettingsPanel.ingestWarningLabel.text=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u304c\u9032\u884c\u4e2d\u3067\u3059\u3002\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u304c\u5b8c\u4e86\u3059\u308b\u307e\u3067\u4e00\u90e8\u306e\u8a2d\u5b9a\u3092\u5229\u7528\u3067\u304d\u307e\u305b\u3093\u3002 KeywordSearchGlobalSearchSettingsPanel.ingestWarningLabel.text=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u304c\u9032\u884c\u4e2d\u3067\u3059\u3002\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u304c\u5b8c\u4e86\u3059\u308b\u307e\u3067\u4e00\u90e8\u306e\u8a2d\u5b9a\u3092\u5229\u7528\u3067\u304d\u307e\u305b\u3093\u3002 +ExtractedContentPanel.hitCountLabel.text=- +ExtractedContentPanel.hitPreviousButton.text= +ExtractedContentPanel.hitTotalLabel.text=- +ExtractedContentPanel.hitOfLabel.text=/ +ExtractedContentPanel.hitNextButton.text= +ExtractedContentPanel.hitButtonsLabel.text=\u4e00\u81f4\u3059\u308b\u7d50\u679c +ExtractedContentPanel.hitLabel.toolTipText= +ExtractedContentPanel.hitLabel.text=\u30da\u30fc\u30b8\u4e0a\u306e\u4e00\u81f4\u3059\u308b\u7d50\u679c: +ExtractedContentPanel.pageCurLabel.text=- +ExtractedContentPanel.pagePreviousButton.actionCommand=pagePreviousButton +ExtractedContentPanel.pagePreviousButton.text= +ExtractedContentPanel.pageNextButton.text= +ExtractedContentPanel.pagesLabel.text=\u30da\u30fc\u30b8: +ExtractedContentPanel.pageTotalLabel.text=- +ExtractedContentPanel.pageButtonsLabel.text=\u30da\u30fc\u30b8 +ExtractedContentPanel.pageOfLabel.text=/ +ExtractedContentPanel.jLabel1.text=\u30c6\u30ad\u30b9\u30c8\u30bd\u30fc\u30b9: diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.form b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.form index de0bedc049..2ed8bb9073 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.form +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.form @@ -26,6 +26,9 @@ + + + @@ -45,8 +48,8 @@ - - + + @@ -104,7 +107,7 @@ - + @@ -146,7 +149,9 @@ - + + + @@ -165,7 +170,6 @@ - @@ -177,6 +181,8 @@ + + @@ -300,11 +306,6 @@ - - - - - @@ -426,6 +427,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java index 8cad2a6140..6cc5e01d42 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2019 Basis Technology Corp. + * Copyright 2011-2020 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.keywordsearch; import java.awt.ComponentOrientation; import java.awt.EventQueue; +import java.awt.Font; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.util.ArrayList; @@ -27,7 +28,9 @@ import java.util.List; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.logging.Level; +import javax.swing.JLabel; import javax.swing.SizeRequirements; +import javax.swing.SwingUtilities; import javax.swing.SwingWorker; import javax.swing.text.Element; import javax.swing.text.View; @@ -36,6 +39,7 @@ import javax.swing.text.html.HTMLEditorKit; import javax.swing.text.html.HTMLEditorKit.HTMLFactory; import javax.swing.text.html.InlineView; import javax.swing.text.html.ParagraphView; +import javax.swing.text.html.StyleSheet; import org.apache.commons.lang3.StringUtils; import org.netbeans.api.progress.ProgressHandle; import org.openide.util.NbBundle; @@ -48,11 +52,20 @@ import org.sleuthkit.autopsy.coreutils.TextUtil; * combo-box to select between multiple sources. */ @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives -class ExtractedContentPanel extends javax.swing.JPanel { +class ExtractedContentPanel extends javax.swing.JPanel implements ResizableTextPanel { private static final Logger logger = Logger.getLogger(ExtractedContentPanel.class.getName()); + + // set font as close as possible to default + private static final Font DEFAULT_FONT = new JLabel().getFont(); + private static final long serialVersionUID = 1L; private String contentName; + private int curSize; + + private final StyleSheet styleSheet; + private final HTMLEditorKit editorKit; + private String lastKnownAnchor = null; ExtractedContentPanel() { initComponents(); @@ -65,7 +78,7 @@ class ExtractedContentPanel extends javax.swing.JPanel { * extractedTextPane taken form this website: * http://java-sl.com/tip_html_letter_wrap.html. */ - HTMLEditorKit editorKit = new HTMLEditorKit() { + editorKit = new HTMLEditorKit() { private static final long serialVersionUID = 1L; @Override @@ -119,13 +132,9 @@ class ExtractedContentPanel extends javax.swing.JPanel { }; } }; - /* - * set font size manually in an effort to get fonts in this panel to - * look similar to what is in the 'String View' content viewer. - */ - editorKit.getStyleSheet().addRule("body {font-size: 8.5px;}"); //NON-NLS - extractedTextPane.setEditorKit(editorKit); - + // get the style sheet for editing font size + styleSheet = editorKit.getStyleSheet(); + sourceComboBox.addItemListener(itemEvent -> { if (itemEvent.getStateChange() == ItemEvent.SELECTED) { refreshCurrentMarkup(); @@ -134,7 +143,48 @@ class ExtractedContentPanel extends javax.swing.JPanel { extractedTextPane.setComponentPopupMenu(rightClickMenu); copyMenuItem.addActionListener(actionEvent -> extractedTextPane.copy()); selectAllMenuItem.addActionListener(actionEvent -> extractedTextPane.selectAll()); + + // TextZoomPanel could not be directly instantiated in Swing WYSIWYG editor + // (because it was package private, couldn't use constructor, etc.) + // so it was identified as a JPanel for the WYSIWYG. This function is called for + // initial setup so the font size of this panel as well as the font size indicated + // in the TextZoomPanel are correct + SwingUtilities.invokeLater(() -> { + if (zoomPanel instanceof TextZoomPanel) + ((TextZoomPanel) this.zoomPanel).resetSize(); + }); } + + + private void setStyleSheetSize(StyleSheet styleSheet, int size) { + styleSheet.addRule("body {font-family:\"" + DEFAULT_FONT.getFamily() + "\"; font-size:" + size + "pt; } "); + } + + + @Override + public int getTextSize() { + return curSize; + } + + @Override + public void setTextSize(int newSize) { + curSize = newSize; + + String curText = extractedTextPane.getText(); + + setStyleSheetSize(styleSheet, curSize); + + editorKit.setStyleSheet(styleSheet); + extractedTextPane.setEditorKit(editorKit); + + extractedTextPane.setText(curText); + if (lastKnownAnchor != null) + scrollToAnchor(lastKnownAnchor); + } + + + + /** * This method is called from within the constructor to initialize the form. @@ -161,7 +211,6 @@ class ExtractedContentPanel extends javax.swing.JPanel { pageNextButton = new javax.swing.JButton(); pagePreviousButton = new javax.swing.JButton(); pageCurLabel = new javax.swing.JLabel(); - jSeparator1 = new javax.swing.JSeparator(); hitLabel = new javax.swing.JLabel(); hitButtonsLabel = new javax.swing.JLabel(); hitNextButton = new javax.swing.JButton(); @@ -170,6 +219,8 @@ class ExtractedContentPanel extends javax.swing.JPanel { hitPreviousButton = new javax.swing.JButton(); hitCountLabel = new javax.swing.JLabel(); jSeparator2 = new javax.swing.JSeparator(); + jSeparator3 = new javax.swing.JSeparator(); + zoomPanel = new TextZoomPanel(this); copyMenuItem.setText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.copyMenuItem.text")); // NOI18N rightClickMenu.add(copyMenuItem); @@ -177,6 +228,7 @@ class ExtractedContentPanel extends javax.swing.JPanel { selectAllMenuItem.setText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.selectAllMenuItem.text")); // NOI18N rightClickMenu.add(selectAllMenuItem); + setMinimumSize(new java.awt.Dimension(100, 0)); setPreferredSize(new java.awt.Dimension(100, 58)); extractedScrollPane.setBackground(new java.awt.Color(255, 255, 255)); @@ -193,7 +245,7 @@ class ExtractedContentPanel extends javax.swing.JPanel { controlScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER); controlScrollPane.setPreferredSize(new java.awt.Dimension(600, 100)); - controlPanel.setMinimumSize(new java.awt.Dimension(0, 0)); + controlPanel.setMinimumSize(new java.awt.Dimension(0, 20)); controlPanel.setPreferredSize(new java.awt.Dimension(600, 81)); sourceComboBox.setModel(new javax.swing.DefaultComboBoxModel()); @@ -233,8 +285,6 @@ class ExtractedContentPanel extends javax.swing.JPanel { pageCurLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); pageCurLabel.setText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.pageCurLabel.text")); // NOI18N - jSeparator1.setOrientation(javax.swing.SwingConstants.VERTICAL); - hitLabel.setText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.hitLabel.text")); // NOI18N hitLabel.setToolTipText(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.hitLabel.toolTipText")); // NOI18N @@ -276,6 +326,12 @@ class ExtractedContentPanel extends javax.swing.JPanel { jSeparator2.setOrientation(javax.swing.SwingConstants.VERTICAL); + jSeparator3.setOrientation(javax.swing.SwingConstants.VERTICAL); + + zoomPanel.setMinimumSize(new java.awt.Dimension(150, 20)); + zoomPanel.setName(""); // NOI18N + zoomPanel.setPreferredSize(new java.awt.Dimension(200, 20)); + javax.swing.GroupLayout controlPanelLayout = new javax.swing.GroupLayout(controlPanel); controlPanel.setLayout(controlPanelLayout); controlPanelLayout.setHorizontalGroup( @@ -312,7 +368,9 @@ class ExtractedContentPanel extends javax.swing.JPanel { .addGap(0, 0, 0) .addComponent(pageNextButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(18, 18, 18) - .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jSeparator3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(18, 18, 18) + .addComponent(zoomPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(jLabel1) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) @@ -329,7 +387,6 @@ class ExtractedContentPanel extends javax.swing.JPanel { .addComponent(pageButtonsLabel) .addComponent(pagePreviousButton) .addComponent(pageNextButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(sourceComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(pagesLabel) .addComponent(hitLabel) @@ -340,11 +397,15 @@ class ExtractedContentPanel extends javax.swing.JPanel { .addComponent(pageTotalLabel) .addComponent(hitOfLabel) .addComponent(hitTotalLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(hitButtonsLabel)) + .addComponent(hitButtonsLabel) + .addComponent(jSeparator3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(zoomPanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addGap(0, 0, 0)) ); - controlPanelLayout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {hitButtonsLabel, hitCountLabel, hitLabel, hitNextButton, hitOfLabel, hitPreviousButton, hitTotalLabel, jLabel1, jSeparator1, jSeparator2, pageButtonsLabel, pageCurLabel, pageNextButton, pageOfLabel, pagePreviousButton, pageTotalLabel, pagesLabel, sourceComboBox}); + controlPanelLayout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {hitButtonsLabel, hitCountLabel, hitLabel, hitNextButton, hitOfLabel, hitPreviousButton, hitTotalLabel, jLabel1, jSeparator2, jSeparator3, pageButtonsLabel, pageCurLabel, pageNextButton, pageOfLabel, pagePreviousButton, pageTotalLabel, pagesLabel, sourceComboBox, zoomPanel}); + + zoomPanel.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(ExtractedContentPanel.class, "ExtractedContentPanel.AccessibleContext.accessibleName")); // NOI18N controlScrollPane.setViewportView(controlPanel); @@ -352,8 +413,8 @@ class ExtractedContentPanel extends javax.swing.JPanel { this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(controlScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 54, Short.MAX_VALUE) - .addComponent(extractedScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 54, Short.MAX_VALUE) + .addComponent(controlScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 980, Short.MAX_VALUE) + .addComponent(extractedScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 980, Short.MAX_VALUE) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -377,8 +438,8 @@ class ExtractedContentPanel extends javax.swing.JPanel { private javax.swing.JButton hitPreviousButton; private javax.swing.JLabel hitTotalLabel; private javax.swing.JLabel jLabel1; - private javax.swing.JSeparator jSeparator1; private javax.swing.JSeparator jSeparator2; + private javax.swing.JSeparator jSeparator3; private javax.swing.JLabel pageButtonsLabel; private javax.swing.JLabel pageCurLabel; private javax.swing.JButton pageNextButton; @@ -389,6 +450,7 @@ class ExtractedContentPanel extends javax.swing.JPanel { private javax.swing.JPopupMenu rightClickMenu; private javax.swing.JMenuItem selectAllMenuItem; private javax.swing.JComboBox sourceComboBox; + private javax.swing.JPanel zoomPanel; // End of variables declaration//GEN-END:variables void refreshCurrentMarkup() { @@ -404,6 +466,7 @@ class ExtractedContentPanel extends javax.swing.JPanel { * the content. */ final void setSources(String contentName, List sources) { + this.lastKnownAnchor = null; this.contentName = contentName; setPanelText(null, false); @@ -451,6 +514,7 @@ class ExtractedContentPanel extends javax.swing.JPanel { } void scrollToAnchor(String anchor) { + lastKnownAnchor = anchor; extractedTextPane.scrollToReference(anchor); } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ResizableTextPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ResizableTextPanel.java new file mode 100644 index 0000000000..290ed2b633 --- /dev/null +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ResizableTextPanel.java @@ -0,0 +1,39 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-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.keywordsearch; + +/** + * This interface allows for retrieving current text size and setting the new text size + * for a panel. + */ +interface ResizableTextPanel { + + /** + * Retrieves the font size (in px). + * @return the font size (in px). + */ + int getTextSize(); + + /** + * Sets the font size (in px). + * @param newSize the new font size (in px). + */ + void setTextSize(int newSize); + +} diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TextZoomPanel.form b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TextZoomPanel.form new file mode 100644 index 0000000000..46d8e3d66f --- /dev/null +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TextZoomPanel.form @@ -0,0 +1,163 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TextZoomPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TextZoomPanel.java new file mode 100644 index 0000000000..eb716df8df --- /dev/null +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TextZoomPanel.java @@ -0,0 +1,221 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-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.keywordsearch; + +import javax.swing.JLabel; +import javax.swing.JPanel; +import org.openide.util.NbBundle; + +/** + * This panel shows current text size and allows for increasing or decreasing font size. + */ +class TextZoomPanel extends JPanel { + static final int DEFAULT_SIZE = new JLabel().getFont().getSize(); + + private static final long serialVersionUID = 1L; + + private static final double[] ZOOM_STEPS = { + 0.0625, 0.125, 0.25, 0.375, 0.5, 0.75, + 1, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10}; + + // Identifies the center index in zoom steps (what identifies 100%). + private static final int DEFAULT_STEP_IDX = 6; + + // The component to receive zoom updates. + private final ResizableTextPanel zoomable; + + // On initialization, set to 100%. + private int curStepIndex = DEFAULT_STEP_IDX; + + + /** + * Creates new form TextZoomPanel. + * @param zoomable the component that will receive text resize events + */ + TextZoomPanel(ResizableTextPanel zoomable) { + this.zoomable = zoomable; + initComponents(); + updateEnabled(); + setZoomText(); + } + + + private void updateEnabled() { + boolean shouldEnable = this.zoomable != null; + this.zoomInButton.setEnabled(shouldEnable); + this.zoomOutButton.setEnabled(shouldEnable); + this.zoomResetButton.setEnabled(shouldEnable); + this.zoomTextField.setEnabled(shouldEnable); + } + + /** + * resets the font size displayed and triggers the ResizableTextPanel to + * set their font to default size (i.e. JLabel().getFont().getSize()) + */ + synchronized void resetSize() { + zoomStep(DEFAULT_STEP_IDX); + } + + private synchronized void zoomStep(int newStep) { + if (this.zoomable != null && newStep >= 0 && newStep < ZOOM_STEPS.length) { + curStepIndex = newStep; + zoomable.setTextSize((int)Math.round(ZOOM_STEPS[curStepIndex] * (double)DEFAULT_SIZE)); + setZoomText(); + } + } + + private synchronized void zoomDecrement() { + zoomStep(curStepIndex - 1); + } + + private synchronized void zoomIncrement() { + zoomStep(curStepIndex + 1); + } + + private void setZoomText() { + String percent = Long.toString(Math.round(ZOOM_STEPS[this.curStepIndex] * 100)); + zoomTextField.setText(percent + "%"); + } + + + + @NbBundle.Messages({ + "TextZoomPanel.zoomTextField.text=", + "TextZoomPanel.zoomOutButton.text=", + "TextZoomPanel.zoomInButton.text=", + "TextZoomPanel.zoomResetButton.text=Reset" + }) + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + zoomTextField = new javax.swing.JTextField(); + zoomOutButton = new javax.swing.JButton(); + zoomInButton = new javax.swing.JButton(); + javax.swing.JToolBar.Separator jSeparator2 = new javax.swing.JToolBar.Separator(); + zoomResetButton = new javax.swing.JButton(); + + setMinimumSize(new java.awt.Dimension(150, 20)); + setPreferredSize(new java.awt.Dimension(200, 20)); + + zoomTextField.setEditable(false); + zoomTextField.setHorizontalAlignment(javax.swing.JTextField.RIGHT); + zoomTextField.setText(org.openide.util.NbBundle.getMessage(TextZoomPanel.class, "TextZoomPanel.zoomTextField.text")); // NOI18N + zoomTextField.setMaximumSize(new java.awt.Dimension(50, 2147483647)); + zoomTextField.setMinimumSize(new java.awt.Dimension(50, 20)); + zoomTextField.setPreferredSize(new java.awt.Dimension(50, 20)); + + zoomOutButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/keywordsearch/zoom-out.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(zoomOutButton, org.openide.util.NbBundle.getMessage(TextZoomPanel.class, "TextZoomPanel.zoomOutButton.text")); // NOI18N + zoomOutButton.setBorderPainted(false); + zoomOutButton.setFocusable(false); + zoomOutButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + zoomOutButton.setMargin(new java.awt.Insets(0, 0, 0, 0)); + zoomOutButton.setMaximumSize(new java.awt.Dimension(20, 20)); + zoomOutButton.setMinimumSize(new java.awt.Dimension(20, 20)); + zoomOutButton.setPreferredSize(new java.awt.Dimension(20, 20)); + zoomOutButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + zoomOutButtonActionPerformed(evt); + } + }); + + zoomInButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/keywordsearch/zoom-in.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(zoomInButton, org.openide.util.NbBundle.getMessage(TextZoomPanel.class, "TextZoomPanel.zoomInButton.text")); // NOI18N + zoomInButton.setBorderPainted(false); + zoomInButton.setFocusable(false); + zoomInButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + zoomInButton.setMargin(new java.awt.Insets(0, 0, 0, 0)); + zoomInButton.setMaximumSize(new java.awt.Dimension(20, 20)); + zoomInButton.setMinimumSize(new java.awt.Dimension(20, 20)); + zoomInButton.setPreferredSize(new java.awt.Dimension(20, 20)); + zoomInButton.setRolloverEnabled(true); + zoomInButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + zoomInButtonActionPerformed(evt); + } + }); + + jSeparator2.setOrientation(javax.swing.SwingConstants.VERTICAL); + jSeparator2.setMaximumSize(new java.awt.Dimension(6, 20)); + + org.openide.awt.Mnemonics.setLocalizedText(zoomResetButton, org.openide.util.NbBundle.getMessage(TextZoomPanel.class, "TextZoomPanel.zoomResetButton.text")); // NOI18N + zoomResetButton.setBorderPainted(false); + zoomResetButton.setFocusable(false); + zoomResetButton.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + zoomResetButton.setMargin(new java.awt.Insets(0, 0, 0, 0)); + zoomResetButton.setMinimumSize(new java.awt.Dimension(50, 20)); + zoomResetButton.setPreferredSize(new java.awt.Dimension(70, 20)); + zoomResetButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + zoomResetButtonActionPerformed(evt); + } + }); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(zoomTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0) + .addComponent(zoomOutButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, 0) + .addComponent(zoomInButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(zoomResetButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jSeparator2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jSeparator2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(zoomTextField, javax.swing.GroupLayout.Alignment.CENTER, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(zoomOutButton, javax.swing.GroupLayout.Alignment.CENTER, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(zoomInButton, javax.swing.GroupLayout.Alignment.CENTER, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(zoomResetButton, javax.swing.GroupLayout.Alignment.CENTER, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + + private void zoomOutButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_zoomOutButtonActionPerformed + zoomDecrement(); + }//GEN-LAST:event_zoomOutButtonActionPerformed + + private void zoomInButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_zoomInButtonActionPerformed + zoomIncrement(); + }//GEN-LAST:event_zoomInButtonActionPerformed + + private void zoomResetButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_zoomResetButtonActionPerformed + resetSize(); + }//GEN-LAST:event_zoomResetButtonActionPerformed + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton zoomInButton; + private javax.swing.JButton zoomOutButton; + private javax.swing.JButton zoomResetButton; + private javax.swing.JTextField zoomTextField; + // End of variables declaration//GEN-END:variables +} diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/zoom-in.png b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/zoom-in.png new file mode 100644 index 0000000000..b79109cb64 Binary files /dev/null and b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/zoom-in.png differ diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/zoom-out.png b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/zoom-out.png new file mode 100644 index 0000000000..cf666e044a Binary files /dev/null and b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/zoom-out.png differ diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Chrome.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Chrome.java index 998702aef7..2c7c5dfcb0 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Chrome.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Chrome.java @@ -561,7 +561,8 @@ class Chrome extends Extract { // find the downloaded file and create a TSK_ASSOCIATED_OBJECT for it, associating it with the TSK_WEB_DOWNLOAD artifact. try { - for (AbstractFile downloadedFile : fileManager.findFiles(dataSource, FilenameUtils.getName(fullPath), FilenameUtils.getPath(fullPath))) { + String normalizedFullPath = FilenameUtils.normalize(fullPath, true); + for (AbstractFile downloadedFile : fileManager.findFiles(dataSource, FilenameUtils.getName(normalizedFullPath), FilenameUtils.getPath(normalizedFullPath))) { BlackboardArtifact associatedObjectArtifact = downloadedFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT); associatedObjectArtifact.addAttribute( new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT, diff --git a/docs/doxygen-user/central_repo.dox b/docs/doxygen-user/central_repo.dox index 5583971bfc..c92231e053 100644 --- a/docs/doxygen-user/central_repo.dox +++ b/docs/doxygen-user/central_repo.dox @@ -24,21 +24,39 @@ The following are some use cases for the central repository: \section cr_setup Setup -To start, open the main options panel and select the "Central Repository" icon. +The central repository settings are found on the main options panel (Tools->Options) on the "Central Repository" tab. \image html central_repo_options.png -\subsection cr_db_setup Setting up the Database +\subsection cr_db_setup Database Configuration -On the central repository options panel, check the 'Use a Central Repository' option and then click the Configure button to set up a database. There are two options here: +There are two types of central repository databases: +- SQLite - This type of database is stored in a file. It should only be used when a single client will be accessing the database. You can not use this option with \ref multiuser_page "multi-user cases". +- PostgreSQL - This type of database is stored on a server running either on the user's host or a remote server. This option must be used if multiple users will be using the same database. + +\subsubsection cr_db_setup_auto Automatic Database Configuration + +Starting with Autopsy 4.15, when you load Autopsy and the central repository is not enabled you will be +asked if you want to enable it. Doing so will create a SQLite database in your Autopsy user folder (on Windows this will be in AppData). You will only be prompted to do this once. Whichever option you select, you can change your central repository settings later as described below. + +Since a SQLite database can't be used for multi-user cases, you are also given the option to switch to a PostgreSQL database when you \ref multiuser_install_clients "enable multi-user cases". If you are currently using a SQLite database, when you enable multi-user cases you will be asked if you want to switch to a PostgreSQL database on the same server. Note that the contents of your SQLite database will not be copied over. + +\subsubsection cr_db_setup_manual Manual Database Configuration + +On the central repository options panel, check the 'Use a Central Repository' option and then click the Configure button to set up a database. There are three options here: - SQLite - This option stores the database in a file. It should only be used when a single client will be accessing the database. -- PostgreSQL - This option uses a database server running either on the user's host or a remote server. This option must be used if multiple users will be using the same database. +- PostgreSQL using multi-user settings - This option uses a central repository on the same PostgreSQL server that has been configured for \ref multiuser_page "multi-user cases". This option can not be selected if multi-user cases are not enabled. This is one of the options if multiple users will be using the same database. +- Custom PostgreSQL - This option uses a database server running either on the user's host or a remote server, where the server is specified in the central repository settings. This is one of the options if multiple users will be using the same database. Once a database has been configured, the lower two buttons on the main panel will be enabled, which will be described below. -Setting up PostgreSQL Deployment +Setting up a PostgreSQL deployment using the multi-user settings -If needed, see the \ref install_postgresql_page for help setting up your PostgreSQL server. +See the \ref install_multiuser_page page for instructions on configuring a multi-user environment. Once done, you can select the "PostgreSQL using multi-user settings" option to create/use a central repository on that PostgreSQL server. + +Setting up a custom PostgreSQL deployment + +If needed, see the \ref install_postgresql_page page for help setting up your PostgreSQL server. For PostgreSQL all values are required, but some defaults are provided for convenience. @@ -70,25 +88,29 @@ are saved to the database, so in a multi-user setting any changes will affect al Descriptions of the property types: - Files - - Files are correlated based on MD5 hash and file path and name. The Hash Lookup ingest module must be enabled. + - Files are correlated based on MD5 hash and file path and name. The \ref hash_db_page must be enabled. - Domains - - Domains are extracted from the various web artifacts, which primarily come from the Recent Activity module + - Domains are extracted from the various web artifacts, which primarily come from the \ref recent_activity_page. - Email Addresses - - Email addresses are pulled from Email Address hits from the Keyword Search module. + - Email addresses are created by modules such as the \ref email_parser_page. - Phone Numbers - - Phone numbers are currently only extracted from call logs, contact lists and message, which come from the Android Analyzer module. + - Phone numbers are currently only extracted from call logs, contact lists and message, which come from the \ref android_analyzer_page. - USB Devices - - USB device properties come from the registry parsing in the Recent Activity Module. + - USB device properties come from the registry parsing in the \ref recent_activity_page. - Wireless Networks - - Wireless networks are correlated on SSIDs, and come from the registry parsing in the Recent Activity Module. + - Wireless networks are correlated on SSIDs, and come from the registry parsing in the \ref recent_activity_page. - MAC Addresses - - MAC address properties are currently only created by custom Autopsy modules + - MAC address properties are currently only created by custom Autopsy modules. - IMEI Number - - IMEIs properties are currently only created by custom Autopsy modules + - IMEIs properties are currently only created by custom Autopsy modules. - IMSI Number - - IMSI properties are currently only created by custom Autopsy modules + - IMSI properties are currently only created by custom Autopsy modules. - ICCID Number - - ICCID properties are currently only created by custom Autopsy modules + - ICCID properties are currently only created by custom Autopsy modules. +- Credit Card + - Credid Card properties are created by the \ref keyword_search_page. +- App-specific Accounts (Facebook, Twitter, etc) + - These properties primarily come from the \ref android_analyzer_page. \subsection cr_manage_orgs Manage Organizations diff --git a/docs/doxygen-user/command_line_ingest.dox b/docs/doxygen-user/command_line_ingest.dox index bdb466dfc6..97c521c685 100644 --- a/docs/doxygen-user/command_line_ingest.dox +++ b/docs/doxygen-user/command_line_ingest.dox @@ -14,6 +14,8 @@ Use the ingest module settings to configure how you want to run ingest. This is Use the report module settings to choose and configure a report type. Only the selected report type will be generated. Configuration is generally the same as normal \ref reporting_page "report generation" with some slight differences. This is mainly seen in places where your options are dependent on the open case, such as choosing \ref tagging_page "tags" to report on or \ref interesting_files_identifier_page "interesting file" set names to include. For example, the HTML report normally allows you to choose specific tags to include but for command line ingest it will only have the option to include all tags. +If you would like to create or open multi-user cases, you'll need to \ref install_multiuser_page "configure the multi-user settings". + \section command_line_ingest_commands Command Options In a command prompt, navigate to the Autopsy bin folder. This is normally located at "C:\Program Files\Autopsy-version\bin". @@ -26,7 +28,9 @@ The table below shows a summary of the command line operations. You can run one +--caseBaseDir +--caseType (optional) @@ -55,6 +59,12 @@ could create a case folder "test5_2019_09_20_11_01_29". Note that even though a \image html command_line_ingest_case_folder.png +By default all cases will be single user. If you would like to create a multi-user case you'll need the -caseType field. You should also use the network path to your case folder so the services can access it: + +\verbatim +autopsy64.exe --createCase --caseName="test_multi" --caseBaseDir="\\WIN-2913\work\cases" --caseType="multi" +\endverbatim + Once a case is created you will need to use the full path to the case instead of the case name and base folder. For example, if we created the empty case "test5" as above, we could use the following command to add a data source to it: \verbatim @@ -62,6 +72,8 @@ autopsy64.exe --caseDir="C:\work\Cases\test5_2019_09_20_11_01_29" --addDataSourc --dataSourcePath="R:\work\images\small2.img" \endverbatim +The case type (single or multi-user) does not have to be specified when opening a case. + \subsection command_line_ds Adding a New Data Source and Running Ingest You can add a data source to a new case or an existing case using the --addDataSource option and then giving the path to the data source. If you use the --runIngest option, the ingest modules you selected in the \ref command_line_ingest_config "configuration step" will be run on the data source. Both \ref ds_img "disk images" and \ref ds_log "logical files" are supported. You can only add one data source at a time. diff --git a/docs/doxygen-user/file_discovery.dox b/docs/doxygen-user/file_discovery.dox index a92b41dc43..f711cb0d01 100644 --- a/docs/doxygen-user/file_discovery.dox +++ b/docs/doxygen-user/file_discovery.dox @@ -2,11 +2,11 @@ \section file_disc_overview Overview -The file discovery tool shows images or videos that match a set of filters configured by the user. You can choose how to group and order your results in order to see the most relevant data first. +The file discovery tool shows images, videos, or documents that match a set of filters configured by the user. You can choose how to group and order your results in order to see the most relevant data first. \section file_disc_prereq Prerequisites -We suggest running all \ref ingest_page "ingest modules" before launching file discovery, but if time is a factor the following are the modules that are the most important. You will see a warning if you open file discovery without running the \ref file_type_identification_page and \ref EXIF_parser_page. +We suggest running all \ref ingest_page "ingest modules" before launching file discovery, but if time is a factor the following are the modules that are the most important. You will see a warning if you open file discovery without running the \ref file_type_identification_page, the \ref hash_db_page, and the \ref EXIF_parser_page. Required ingest modules:
    @@ -20,6 +20,8 @@ Optional ingest modules:
  • \ref hash_db_page - Needed to use the \ref file_disc_hash_filter and to de-duplicate files
  • \ref interesting_files_identifier_page - Needed to use the \ref file_disc_int_filter
  • \ref object_detection_page - Needed to use the \ref file_disc_obj_filter +
  • \ref keyword_search_page - Improves document summaries +
  • \ref embedded_file_extractor_page - Allows display of an image contained in a document
\section file_disc_run Running File Discovery @@ -37,17 +39,17 @@ Once everything is set up, use the "Show" button at the bottom of the left panel \subsection file_disc_type File Type -The first step is choosing whether you want to display images or videos. The file type is determined by the MIME type of the file, which is why the file_type_identification_page must be run to see any results. Switching between the file types will clear any results being displayed and reset the filters. +The first step is choosing whether you want to display images, videos, or documents. The file type is determined by the MIME type of the file, which is why the \ref file_type_identification_page must be run to see any results. Switching between the file types will clear any results being displayed and reset the filters. \image html FileDiscovery/fd_fileType.png \subsection file_disc_filtering Filtering -The second step is to select and configure your filters. For most filters, you enable them using the checkbox on the left and then select your options. Multiple options can be selected by using CTRL + left click. Files must pass all enabled filters to be displayed. +The second step is to select and configure your filters. The available filters will vary depending on the file type. For most filters, you enable them using the checkbox on the left and then select your options. Multiple options can be selected by using CTRL + left click. Files must pass all enabled filters to be displayed. \subsubsection file_disc_size_filter File Size Filter -The file size filter lets you restrict the size of your results. The options are different for images and videos - an extra small image might be under 16 KB while an extra small video is anything under 500 KB. +The file size filter lets you restrict the size of your results. The options are different for the different file types - an extra small image might be under 16 KB while an extra small video is anything under 500 KB. \image html FileDiscovery/fd_fileSizeFilter.png @@ -71,7 +73,7 @@ The possibly user created filter restricts the results to files that suspected t \image html FileDiscovery/fd_userCreatedFilter.png -This means the image or video must have a "User Content Suspected" result associated with it. These primarily come from the \ref EXIF_parser_page "Exif parser module". +This means the file must have a "User Content Suspected" result associated with it. These primarily come from the \ref EXIF_parser_page "Exif parser module". \image html FileDiscovery/fd_userContentArtifact.png @@ -147,6 +149,10 @@ If your results are videos, each result will display four thumbnails from the vi \image html FileDiscovery/fd_videos.png +If your results are documents, you'll see part of the document text. If the \ref embedded_file_extractor_page found any images in the document you'll see a thumbnail of the largest of them displayed on the right side along with a count of how many images were extracted from the document. + +\image html FileDiscovery/fd_documents.png + When you select a result from the top of the right panel, you'll see the path to the corresponding file(s) in the "Instances" panel below the thumbnails. There may be more than one file instance associated with a result - see the \ref file_disc_dedupe section below. You can right-click on files in the instances panel to use most of options available in the normal \ref result_viewer_page. \image html FileDiscovery/fd_instanceContext.png @@ -155,9 +161,7 @@ The bottom section of the panel is identical to the standard \ref content_viewer \subsection file_disc_dedupe De-duplication -Assuming the \ref hash_db_page module has been run, all files in a result group with the same hash will be merged together under a single instance. You can see the number of instances of each file under the thumbnail, and each file instance will be displayed in the middle section of the panel. - - +Assuming the \ref hash_db_page module has been run, all files in a result group with the same hash will be merged together under a single instance. The file path to one of the instances will be displayed along with a note such as "and 1 more" indicating how many duplicates were found. Selecting the file will display each instance in the middle section of the panel. \image html FileDiscovery/fd_dupeEx.png diff --git a/docs/doxygen-user/gpx.dox b/docs/doxygen-user/gpx.dox new file mode 100644 index 0000000000..6650639eff --- /dev/null +++ b/docs/doxygen-user/gpx.dox @@ -0,0 +1,49 @@ +/*! \page gpx_page GPX Analyzer + +\section gpx_overview Overview + +The GPX Analyzer modules allows you to import GPS data from a GPX file. Information on the GPX format can be found here. The following is a short sample of a GPX file: + +\verbatim + + + + + + Garmin International + + + + + Example GPX Document + + + 4.46 + + + + 4.94 + + + + 6.87 + + + + + +\endverbatim + +\section gpx_config Running the Module + +To enable the GPX Analyzer ingest module select the checkbox in the \ref ingest_configure "Ingest Modules configuration screen". + +\section gpx_results Viewing Results + +Results are show in the Results tree under "Extracted Content". The GPX data types "wptType", "rteType", and "trkType" produce results of type GPS Bookmark, GPS Route, and GPS Track, respectively. + +\image html gpx_results.png + +GPS results can also be seen in the \ref geolocation_page window and the KML Report. + +*/ \ No newline at end of file diff --git a/docs/doxygen-user/images/FileDiscovery/fd_documents.png b/docs/doxygen-user/images/FileDiscovery/fd_documents.png new file mode 100644 index 0000000000..7170798ce7 Binary files /dev/null and b/docs/doxygen-user/images/FileDiscovery/fd_documents.png differ diff --git a/docs/doxygen-user/images/FileDiscovery/fd_dupeEx.png b/docs/doxygen-user/images/FileDiscovery/fd_dupeEx.png index e711bc95cc..4d15a718e2 100644 Binary files a/docs/doxygen-user/images/FileDiscovery/fd_dupeEx.png and b/docs/doxygen-user/images/FileDiscovery/fd_dupeEx.png differ diff --git a/docs/doxygen-user/images/FileDiscovery/fd_fileType.png b/docs/doxygen-user/images/FileDiscovery/fd_fileType.png index e7ae704fe5..9d988f8308 100644 Binary files a/docs/doxygen-user/images/FileDiscovery/fd_fileType.png and b/docs/doxygen-user/images/FileDiscovery/fd_fileType.png differ diff --git a/docs/doxygen-user/images/FileDiscovery/fd_icon.png b/docs/doxygen-user/images/FileDiscovery/fd_icon.png index 575395bad7..9e29ceaf2b 100644 Binary files a/docs/doxygen-user/images/FileDiscovery/fd_icon.png and b/docs/doxygen-user/images/FileDiscovery/fd_icon.png differ diff --git a/docs/doxygen-user/images/FileDiscovery/fd_main.png b/docs/doxygen-user/images/FileDiscovery/fd_main.png index 1412f43892..6e44376a10 100644 Binary files a/docs/doxygen-user/images/FileDiscovery/fd_main.png and b/docs/doxygen-user/images/FileDiscovery/fd_main.png differ diff --git a/docs/doxygen-user/images/FileDiscovery/fd_paging.png b/docs/doxygen-user/images/FileDiscovery/fd_paging.png index 7a18889e5e..060e66e0ec 100644 Binary files a/docs/doxygen-user/images/FileDiscovery/fd_paging.png and b/docs/doxygen-user/images/FileDiscovery/fd_paging.png differ diff --git a/docs/doxygen-user/images/FileDiscovery/fd_resultGroups.png b/docs/doxygen-user/images/FileDiscovery/fd_resultGroups.png index 02e644a605..b8362e610f 100644 Binary files a/docs/doxygen-user/images/FileDiscovery/fd_resultGroups.png and b/docs/doxygen-user/images/FileDiscovery/fd_resultGroups.png differ diff --git a/docs/doxygen-user/images/FileDiscovery/fd_videos.png b/docs/doxygen-user/images/FileDiscovery/fd_videos.png index eb36a81f41..53dfbd8339 100644 Binary files a/docs/doxygen-user/images/FileDiscovery/fd_videos.png and b/docs/doxygen-user/images/FileDiscovery/fd_videos.png differ diff --git a/docs/doxygen-user/images/central_repo_types.png b/docs/doxygen-user/images/central_repo_types.png index b893abc282..96b70b149e 100644 Binary files a/docs/doxygen-user/images/central_repo_types.png and b/docs/doxygen-user/images/central_repo_types.png differ diff --git a/docs/doxygen-user/images/gpx_results.png b/docs/doxygen-user/images/gpx_results.png new file mode 100644 index 0000000000..0a061f2b2d Binary files /dev/null and b/docs/doxygen-user/images/gpx_results.png differ diff --git a/docs/doxygen-user/images/module_install_netbeans.png b/docs/doxygen-user/images/module_install_netbeans.png new file mode 100644 index 0000000000..db50799d1c Binary files /dev/null and b/docs/doxygen-user/images/module_install_netbeans.png differ diff --git a/docs/doxygen-user/images/module_install_python.png b/docs/doxygen-user/images/module_install_python.png new file mode 100644 index 0000000000..cb393c30b5 Binary files /dev/null and b/docs/doxygen-user/images/module_install_python.png differ diff --git a/docs/doxygen-user/main.dox b/docs/doxygen-user/main.dox index cb0f5e0e9e..424a0323d1 100644 --- a/docs/doxygen-user/main.dox +++ b/docs/doxygen-user/main.dox @@ -48,6 +48,7 @@ The following topics are available here: - \subpage vm_extractor_page - \subpage plaso_page - \subpage drone_page + - \subpage gpx_page - Reviewing the Results - \subpage uilayout_page diff --git a/docs/doxygen-user/module_install.dox b/docs/doxygen-user/module_install.dox index 31a02b3098..1882ca70e2 100644 --- a/docs/doxygen-user/module_install.dox +++ b/docs/doxygen-user/module_install.dox @@ -9,9 +9,15 @@ There are two types of modules: \section module_install_nbm Installing NetBeans Modules If you have an NBM file, then it may contain one or more Autopsy modules. To install it, use the plugin manager at "Tools", "Plugins". +\image html module_install_netbeans.png + Choose the "Downloaded" tab and then choose "Add Plugins". Browse to the NBM file. It may require you to restart Autopsy. \section module_install_python Installing Python Modules -If you have a ZIP file with a Python module in it, then unzip the file and you should get a folder. Open the Python module library folder using "Tools", "Python Plugins". Copy the module folder into there and Autopsy should identify and use it next time it loads modules. +If you have a ZIP file with a Python module in it, then unzip the file and you should get a folder. Open the Python module library folder through Autopsy using "Tools", "Python Plugins". + +\image html module_install_python.png + +On Windows this will be under your user directory in "AppData\Roaming\autopsy\python_modules". Copy the module folder into there and Autopsy should identify and use it next time it loads modules. */
OperationCommand(s)Parameter(s)Example
Create New Case
--createCase
--caseName
---caseBaseDir
--createCase --caseName="test5" --caseBaseDir="C:\work\cases"
--createCase --caseName="test5" --caseBaseDir="C:\work\cases"
+--createCase --caseName="test_multi" --caseBaseDir="\\WIN-2913\work\cases" --caseType="multi"
Open Existing Case 
--caseDir
--caseDir="C:\work\Cases\test5_2019_09_20_11_01_29"