Merge branch 'develop' of github.com:sleuthkit/autopsy into 6104-persona-accounts-test

This commit is contained in:
U-BASIS\dsmyda 2020-03-26 12:06:12 -04:00
commit a48f5ba966
93 changed files with 2723 additions and 1546 deletions

View File

@ -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

View File

@ -615,10 +615,6 @@
<runtime-relative-path>ext/commons-validator-1.6.jar</runtime-relative-path>
<binary-origin>release/modules/ext/commons-validator-1.6.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/jna-5.1.0.jar</runtime-relative-path>
<binary-origin>release\modules\ext\jna-5.1.0.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/jbig2-imageio-3.0.2.jar</runtime-relative-path>
<binary-origin>release\modules\ext\jbig2-imageio-3.0.2.jar</binary-origin>

View File

@ -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

View File

@ -39,7 +39,7 @@ public class CentralRepoDbUpgrader13To14 implements CentralRepoDbUpgrader {
try (Statement statement = connection.createStatement();) {
CentralRepoPlatforms selectedPlatform = CentralRepoPlatforms.getSelectedPlatform();
CentralRepoPlatforms selectedPlatform = CentralRepoDbManager.getSavedDbChoice().getDbPlatform();
// Create account_types and accounts tables which are referred by X_instances tables
statement.execute(RdbmsCentralRepoFactory.getCreateAccountTypesTableStatement(selectedPlatform));
@ -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);

View File

@ -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;
}
}

View File

@ -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,14 +108,11 @@ public class CentralRepoPostgresSettingsUtil {
return settings;
}
logException(() -> settings.setHost(muConn.getHost()));
logException(() -> settings.setDbName(PostgresConnectionSettings.DEFAULT_DBNAME));
logException(() -> settings.setUserName(muConn.getUserName()));
setValOrLog((v) -> settings.setHost(v), muConn.getHost());
setValOrLog((v) -> settings.setUserName(v), muConn.getUserName());
setValOrLog((v) -> settings.setPassword(v), muConn.getPassword());
logException(() -> settings.setPort(Integer.parseInt(muConn.getPort())));
logException(() -> settings.setBulkThreshold(RdbmsCentralRepo.DEFAULT_BULK_THRESHHOLD));
logException(() -> settings.setPassword(muConn.getPassword()));
setValOrLog((v) -> settings.setPort(Integer.parseInt(v)), muConn.getPort());
return settings;
}
@ -120,24 +129,27 @@ public class CentralRepoPostgresSettingsUtil {
Map<String, String> 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;
}

View File

@ -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<CorrelationAttributeInstance.Type> getCorrelationTypes() throws CentralRepoException;
/**
* Get account type by type name.
*

View File

@ -0,0 +1,115 @@
/*
* Central Repository
*
* Copyright 2018-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.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);
}
}
}

View File

@ -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++;
}

View File

@ -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));
}
}

View File

@ -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;
@ -84,23 +85,12 @@ public class CorrelationAttributeUtil {
BlackboardArtifact sourceArtifact = getCorrAttrSourceArtifact(artifact);
if (sourceArtifact != null) {
int artifactTypeID = sourceArtifact.getArtifactTypeID();
if (artifactTypeID == ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) {
BlackboardAttribute setNameAttr = sourceArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME));
if (setNameAttr != null && CorrelationAttributeUtil.getEmailAddressAttrDisplayName().equals(setNameAttr.getValueString())) {
makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD, CorrelationAttributeInstance.EMAIL_TYPE_ID);
}
} else if (artifactTypeID == ARTIFACT_TYPE.TSK_WEB_BOOKMARK.getTypeID()
if (artifactTypeID == ARTIFACT_TYPE.TSK_WEB_BOOKMARK.getTypeID()
|| artifactTypeID == ARTIFACT_TYPE.TSK_WEB_COOKIE.getTypeID()
|| artifactTypeID == ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID()
|| artifactTypeID == ARTIFACT_TYPE.TSK_WEB_HISTORY.getTypeID()) {
makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN, CorrelationAttributeInstance.DOMAIN_TYPE_ID);
} else if (artifactTypeID == ARTIFACT_TYPE.TSK_CONTACT.getTypeID()
|| artifactTypeID == ARTIFACT_TYPE.TSK_CALLLOG.getTypeID()
|| artifactTypeID == ARTIFACT_TYPE.TSK_MESSAGE.getTypeID()) {
makeCorrAttrFromArtifactPhoneAttr(sourceArtifact);
} else if (artifactTypeID == ARTIFACT_TYPE.TSK_DEVICE_ATTACHED.getTypeID()) {
makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_ID, CorrelationAttributeInstance.USBID_TYPE_ID);
makeCorrAttrFromArtifactAttr(correlationAttrs, sourceArtifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MAC_ADDRESS, CorrelationAttributeInstance.MAC_TYPE_ID);
@ -169,58 +159,6 @@ public class CorrelationAttributeUtil {
return sourceArtifact;
}
/**
* Makes a correlation attribute instance from a phone number attribute of an
* artifact.
*
* @param artifact An artifact with a phone number attribute.
*
* @return The correlation instance artifact or null, if the phone number is
* not a valid correlation attribute.
*
* @throws TskCoreException If there is an error querying the case
* database.
* @throws CentralRepoException If there is an error querying the central
* repository.
*/
private static CorrelationAttributeInstance makeCorrAttrFromArtifactPhoneAttr(BlackboardArtifact artifact) throws TskCoreException, CentralRepoException {
CorrelationAttributeInstance corrAttr = null;
/*
* Extract the phone number from the artifact attribute.
*/
String value = null;
if (null != artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER))) {
value = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER)).getValueString();
} else if (null != artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM))) {
value = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM)).getValueString();
} else if (null != artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO))) {
value = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO)).getValueString();
}
/*
* Normalize the phone number.
*/
if (value != null) {
String newValue = value.replaceAll("\\D", "");
if (value.startsWith("+")) {
newValue = "+" + newValue;
}
value = newValue;
/*
* Validate the phone number. Three to five digit phone numbers may
* be valid, but they are too short to use as correlation
* attributes.
*/
if (value.length() > 5) {
corrAttr = makeCorrAttr(artifact, CentralRepository.getInstance().getCorrelationTypeById(CorrelationAttributeInstance.PHONE_TYPE_ID), value);
}
}
return corrAttr;
}
/**
* Makes a correlation attribute instance for an account artifact.
*
@ -248,24 +186,28 @@ public class CorrelationAttributeUtil {
BlackboardAttribute accountTypeAttribute = acctArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE));
String accountTypeStr = accountTypeAttribute.getValueString();
// Get the corresponding CentralRepoAccountType from the database.
CentralRepoAccountType crAccountType = CentralRepository.getInstance().getAccountTypeByName(accountTypeStr);
// do not create any correlation attribute instance for a Device account
if (Account.Type.DEVICE.getTypeName().equalsIgnoreCase(accountTypeStr) == false) {
int corrTypeId = crAccountType.getCorrelationTypeId();
CorrelationAttributeInstance.Type corrType = CentralRepository.getInstance().getCorrelationTypeById(corrTypeId);
// Get the corresponding CentralRepoAccountType from the database.
CentralRepoAccountType crAccountType = CentralRepository.getInstance().getAccountTypeByName(accountTypeStr);
// Get the account identifier
BlackboardAttribute accountIdAttribute = acctArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ID));
String accountIdStr = accountIdAttribute.getValueString();
int corrTypeId = crAccountType.getCorrelationTypeId();
CorrelationAttributeInstance.Type corrType = CentralRepository.getInstance().getCorrelationTypeById(corrTypeId);
// add/get the account and get its accountId.
CentralRepoAccount crAccount = CentralRepository.getInstance().getOrCreateAccount(crAccountType, accountIdStr);
// Get the account identifier
BlackboardAttribute accountIdAttribute = acctArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ID));
String accountIdStr = accountIdAttribute.getValueString();
CorrelationAttributeInstance corrAttr = makeCorrAttr(acctArtifact, corrType, accountIdStr);
if (corrAttr != null) {
// set the account_id in correlation attribute
corrAttr.setAccountId(crAccount.getAccountId());
corrAttrInstances.add(corrAttr);
// 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);
}
}
}

View File

@ -1,68 +0,0 @@
/*
* Central Repository
*
* Copyright 2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.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);
}
}
}
}

View File

@ -80,6 +80,7 @@ abstract class RdbmsCentralRepo implements CentralRepository {
.expireAfterWrite(ACCOUNTS_CACHE_TIMEOUT, TimeUnit.MINUTES).
build();
private boolean isCRTypeCacheInitialized;
private static final Cache<Integer, CorrelationAttributeInstance.Type> typeCache = CacheBuilder.newBuilder().build();
private static final Cache<String, CorrelationCase> 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<CorrelationAttributeInstance.Type> getDefinedCorrelationTypes() throws CentralRepoException {
Connection conn = connect();
List<CorrelationAttributeInstance.Type> 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<CorrelationAttributeInstance.Type> 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<CorrelationAttributeInstance.Type> 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

View File

@ -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,"
@ -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);
}
}
}

View File

@ -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.

View File

@ -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

View File

@ -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<CentralRepoDbChoice>, Serializable {
private static final long serialVersionUID = 1L;
public Component getListCellRendererComponent(JList list, Object value,
@Override
public Component getListCellRendererComponent(
JList<? extends CentralRepoDbChoice> 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;
}
}
@ -145,9 +147,7 @@ public class EamDbSettingsDialog extends JDialog {
CentralRepoDbChoice.DB_CHOICES[0] :
initialMenuItem;
// set the renderer so item is unselectable if inappropriate
cbDatabaseType.setRenderer(DB_CHOICE_RENDERER);
changeDbSelection(toSelect);
}

View File

@ -56,8 +56,6 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i
private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.STARTED, IngestManager.IngestJobEvent.CANCELLED, IngestManager.IngestJobEvent.COMPLETED);
private final IngestJobEventPropertyChangeListener ingestJobEventListener;
/**
* Creates new form EamOptionsPanel
*/
@ -75,7 +73,6 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i
});
}
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.
* 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.
* @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?",
@ -131,30 +125,28 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i
if (!muPreviouslySelected && muCurrentlySelected) {
SwingUtilities.invokeLater(() -> {
if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(parent,
"<html><body>" +
"<div style='width: 400px;'>" +
"<p>" + NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.onMultiUserChange.enable.description") + "</p>" +
"<p style='margin-top: 10px'>" + NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.onMultiUserChange.enable.description2") + "</p>" +
"</div>" +
"</body></html>",
NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.onMultiUserChange.enable.title"),
JOptionPane.YES_NO_OPTION)) {
if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(parent,
"<html><body>"
+ "<div style='width: 400px;'>"
+ "<p>" + NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.onMultiUserChange.enable.description") + "</p>"
+ "<p style='margin-top: 10px'>" + NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.onMultiUserChange.enable.description2") + "</p>"
+ "</div>"
+ "</body></html>",
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);
}
// 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",
@ -186,12 +180,12 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i
int result = JOptionPane.showOptionDialog(
parent,
"<html><body>" +
"<div style='width: 400px;'>" +
"<p>" + NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.onMultiUserChange.disabledMu.description") + "</p>" +
"<p style='margin-top: 10px'>" + NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.onMultiUserChange.disabledMu.description2") + "</p>" +
"</div>" +
"</body></html>",
"<html><body>"
+ "<div style='width: 400px;'>"
+ "<p>" + NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.onMultiUserChange.disabledMu.description") + "</p>"
+ "<p style='margin-top: 10px'>" + NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.onMultiUserChange.disabledMu.description2") + "</p>"
+ "</div>"
+ "</body></html>",
NbBundle.getMessage(GlobalSettingsPanel.class, "GlobalSettingsPanel.onMultiUserChange.disabledMu.title"),
JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.PLAIN_MESSAGE,
@ -202,49 +196,19 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i
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.
* WARNING: Do NOT modify this code. The content of this method is always
@ -591,15 +555,13 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i
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();
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);
}
/**

View File

@ -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();

View File

@ -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();

View File

@ -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);

View File

@ -118,7 +118,7 @@ public final class InterCasePanel extends javax.swing.JPanel {
void setupCorrelationTypeFilter() {
this.correlationTypeFilters = new HashMap<>();
try {
List<CorrelationAttributeInstance.Type> types = CentralRepository.getInstance().getCorrelationTypes();
List<CorrelationAttributeInstance.Type> types = CentralRepository.getInstance().getDefinedCorrelationTypes();
for (CorrelationAttributeInstance.Type type : types) {
correlationTypeFilters.put(type.getDisplayName(), type);
this.correlationTypeComboBox.addItem(type.getDisplayName());

View File

@ -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();
try {
if (attributeTypeName.contains("PHONE")) {
attributeValue = CommunicationsUtils.normalizePhoneNum(attributeValue);
} else if (attributeTypeName.contains("EMAIL")) {
attributeValue = CommunicationsUtils.normalizeEmailAddress(attributeValue);
}
if (attributeTypeName.contains("PHONE")) {
attributeValue = RelationshipsNodeUtilities.normalizePhoneNum(attributeValue);
} else if (attributeTypeName.contains("EMAIL")) {
attributeValue = RelationshipsNodeUtilities.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 ( typeSpecificID.equals(attributeValue) ) {
isReference = true;
break;
}
}
if (isReference) {
referenceCnt++;

View File

@ -19,6 +19,7 @@
package org.sleuthkit.autopsy.communications.relationships;
import java.util.logging.Level;
import org.apache.commons.lang.StringUtils;
import org.openide.nodes.Sheet;
import org.sleuthkit.autopsy.communications.Utils;
import static org.sleuthkit.autopsy.communications.relationships.RelationshipsNodeUtilities.getAttributeDisplayString;
@ -67,14 +68,6 @@ final class CallLogNode extends BlackboardArtifactNode {
return sheet;
}
String phoneNumber = getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_FROM);
if(phoneNumber == null || phoneNumber.isEmpty()) {
phoneNumber = getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_TO);
}
if(phoneNumber == null || phoneNumber.isEmpty()) {
phoneNumber = getAttributeDisplayString(artifact, TSK_PHONE_NUMBER);
}
long duration = -1;
try{
duration = getCallDuration(artifact);
@ -84,7 +77,7 @@ final class CallLogNode extends BlackboardArtifactNode {
sheetSet.put(createNode(TSK_DATETIME_START, artifact));
sheetSet.put(createNode(TSK_DIRECTION, artifact));
sheetSet.put(new NodeProperty<>(TSK_PHONE_NUMBER.getLabel(), TSK_PHONE_NUMBER.getDisplayName(), "", phoneNumber));
sheetSet.put(new NodeProperty<>(TSK_PHONE_NUMBER.getLabel(), TSK_PHONE_NUMBER.getDisplayName(), "", getPhoneNumber(artifact)));
if(duration != -1) {
sheetSet.put(new NodeProperty<>("duration", "Duration", "", Long.toString(duration)));
}
@ -107,6 +100,59 @@ final class CallLogNode extends BlackboardArtifactNode {
return endAttribute.getValueLong() - startAttribute.getValueLong();
}
/**
* Returns the phone number to display in the To/From column. The number is
* picked from one of the 3 possible phone number attributes, based on the
* direction of the call.
*
* @param artifact Call log artifact.
*
* @return Phone number to display.
*/
private String getPhoneNumber(BlackboardArtifact artifact) {
String direction = getAttributeDisplayString(artifact, TSK_DIRECTION);
String phoneNumberToReturn;
String fromPhoneNumber = getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_FROM);
String toPhoneNumber = getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_TO);
String phoneNumber = getAttributeDisplayString(artifact, TSK_PHONE_NUMBER);
switch (direction.toLowerCase()) {
case "incoming": // NON-NLS
phoneNumberToReturn = getFirstNonBlank(fromPhoneNumber, phoneNumber, toPhoneNumber);
break;
case "outgoing": // NON-NLS
phoneNumberToReturn = getFirstNonBlank(toPhoneNumber, phoneNumber, fromPhoneNumber);
break;
default:
phoneNumberToReturn = getFirstNonBlank(toPhoneNumber, fromPhoneNumber, phoneNumber );
break;
}
return phoneNumberToReturn;
}
/**
* Checks the given string arguments in order and returns the first non blank string.
* Returns a blank string if all the input strings are blank.
*
* @param string1 First string to check
* @param string2 Second string to check
* @param string3 Third string to check
*
* @retunr first non blank string if there is one, blank string otherwise.
*
*/
private String getFirstNonBlank(String string1, String string2, String string3 ) {
if (!StringUtils.isBlank(string1)) {
return string1;
} else if (!StringUtils.isBlank(string2)) {
return string2;
} else if (!StringUtils.isBlank(string3)) {
return string3;
}
return "";
}
/**
* Circumvent DataResultFilterNode's slightly odd delegation to
* BlackboardArtifactNode.getSourceName().

View File

@ -111,7 +111,7 @@ final class CorrelationCaseChildNodeFactory extends ChildFactory<CorrelationCase
private CorrelationAttributeInstance.Type getCorrelationType(Account.Type accountType) throws CentralRepoException {
if (correlationTypeMap == null) {
correlationTypeMap = new HashMap<>();
List<CorrelationAttributeInstance.Type> correcationTypeList = CentralRepository.getInstance().getCorrelationTypes();
List<CorrelationAttributeInstance.Type> correcationTypeList = CentralRepository.getInstance().getDefinedCorrelationTypes();
correcationTypeList.forEach((type) -> {
correlationTypeMap.put(type.getId(), type);
});

View File

@ -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;
}
}

View File

@ -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}

View File

@ -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);
}
}

View File

@ -63,7 +63,7 @@
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" max="-2" attributes="0">
<Component id="progressLabel" max="32767" attributes="0"/>
<Component id="playBackPanel" pref="0" max="32767" attributes="0"/>
<Component id="playBackPanel" max="32767" attributes="0"/>
</Group>
<EmptySpace min="-2" pref="10" max="-2" attributes="0"/>
</Group>
@ -129,12 +129,6 @@
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/contentviewers/Bundle.properties" key="MediaPlayerPanel.playButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[53, 29]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[53, 29]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[49, 29]"/>
</Property>

View File

@ -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);
@ -241,7 +237,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
progressSlider.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
if (progressSlider.getValueIsAdjusting()) {
if (progressSlider.getValueIsAdjusting() && gstPlayBin != null) {
long duration = gstPlayBin.queryDuration(TimeUnit.NANOSECONDS);
double relativePosition = progressSlider.getValue() * 1.0 / PROGRESS_SLIDER_SIZE;
long newStartTime = (long) (relativePosition * duration);
@ -267,17 +263,20 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
@Override
public void mousePressed(MouseEvent e) {
previousState = gstPlayBin.getState();
gstPlayBin.pause();
if (gstPlayBin != null) {
previousState = gstPlayBin.getState();
gstPlayBin.pause();
}
}
@Override
public void mouseReleased(MouseEvent e) {
if(previousState.equals(State.PLAYING)) {
if (previousState.equals(State.PLAYING) && gstPlayBin != null) {
gstPlayBin.play();
}
previousState = State.NULL;
}
@Override
public void mouseClicked(MouseEvent e) {
}
@ -293,7 +292,7 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
});
//Manage the audio level when the user is adjusting the volume slider
audioSlider.addChangeListener((ChangeEvent event) -> {
if (audioSlider.getValueIsAdjusting()) {
if (audioSlider.getValueIsAdjusting() && gstPlayBin != null) {
double audioPercent = (audioSlider.getValue() * 2.0) / 100.0;
gstPlayBin.setVolume(audioPercent);
}
@ -327,11 +326,13 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
endOfStreamListener = new Bus.EOS() {
@Override
public void endOfStream(GstObject go) {
gstPlayBin.seek(ClockTime.ZERO);
/**
* Keep the video from automatically playing
*/
Gst.getExecutor().submit(() -> gstPlayBin.pause());
if (gstPlayBin != null) {
gstPlayBin.seek(ClockTime.ZERO);
/**
* Keep the video from automatically playing
*/
Gst.getExecutor().submit(() -> gstPlayBin.pause());
}
}
};
}
@ -385,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();
@ -420,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
@ -537,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 {
@ -546,41 +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());
//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()) {
// This will disable the panel for future use.
IS_GST_ENABLED = false;
return;
}
JFXPanel fxPanel = new JFXPanel();
videoPanel.removeAll();
videoPanel.setLayout(new BoxLayout(videoPanel, BoxLayout.Y_AXIS));
videoPanel.add(fxPanel);
fxAppSink = new JavaFxAppSink("JavaFxAppSink", fxPanel);
gstPlayBin.setVideoSink(fxAppSink);
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;
}
gstPlayBin.setVolume((audioSlider.getValue() * 2.0) / 100.0);
gstPlayBin.pause();
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);
timer.start();
SwingUtilities.invokeLater(() -> {
enableComponents(true);
});
});
} catch (CancellationException ex) {
logger.log(Level.INFO, "Media buffering was canceled."); //NON-NLS
} catch (InterruptedException ex) {
@ -598,24 +619,30 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
@Override
public void actionPerformed(ActionEvent e) {
if (!progressSlider.getValueIsAdjusting()) {
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));
}
if (!progressSlider.getValueIsAdjusting() && gstPlayBin != null) {
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();
}
}
}
@ -655,8 +682,8 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
}
/**
* Modifies the View to be an oval rather than the underlying
* rectangle Controller.
* Modifies the View to be an oval rather than the underlying rectangle
* Controller.
*/
@Override
public void paintThumb(Graphics graphic) {
@ -705,12 +732,13 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
@Override
protected TrackListener createTrackListener(JSlider slider) {
/**
* This track listener will force the thumb to be snapped to the mouse
* location. This makes grabbing and dragging the JSlider much easier.
* Using the default track listener, the user would have to click
* exactly on the slider thumb to drag it. Now the thumb positions
* itself under the mouse so that it can always be dragged.
*/
* This track listener will force the thumb to be snapped to the
* mouse location. This makes grabbing and dragging the JSlider much
* easier. Using the default track listener, the user would have to
* click exactly on the slider thumb to drag it. Now the thumb
* positions itself under the mouse so that it can always be
* dragged.
*/
return new TrackListener() {
@Override
public void mousePressed(MouseEvent e) {
@ -982,88 +1010,104 @@ public class MediaPlayerPanel extends JPanel implements MediaFileViewer.MediaVie
}// </editor-fold>//GEN-END:initComponents
private void rewindButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_rewindButtonActionPerformed
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);
Gst.getExecutor().submit(() -> {
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
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;
}
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;
}
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
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) {//GEN-FIRST:event_playButtonActionPerformed
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();
}
}//GEN-LAST:event_playButtonActionPerformed
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) {//GEN-FIRST:event_playBackSpeedComboBoxActionPerformed
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 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;

View File

@ -0,0 +1,74 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.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() {
}
}

View File

@ -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();
}

View File

@ -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
@ -101,8 +102,8 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
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<T extends AbstractFile> 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 "";
}
/**

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013-2018 Basis Technology Corp.
* Copyright 2013-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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"),
@ -119,7 +117,6 @@ public class BlackboardArtifactTagNode extends DisplayableItemNode {
}
} catch (TskCoreException ex) {
LOGGER.log(Level.SEVERE, MessageFormat.format("Error getting arttribute(s) from blackboard artifact{0}.", artifact.getArtifactID()), ex); //NON-NLS
MessageNotifyUtil.Notify.error(Bundle.BlackboardArtifactNode_getAction_errorTitle(), Bundle.BlackboardArtifactNode_getAction_resultErrorMessage());
}
// if the artifact links to another file, add an action to go to that file
@ -130,7 +127,6 @@ public class BlackboardArtifactTagNode extends DisplayableItemNode {
}
} catch (TskCoreException ex) {
LOGGER.log(Level.SEVERE, MessageFormat.format("Error getting linked file from blackboard artifact{0}.", artifact.getArtifactID()), ex); //NON-NLS
MessageNotifyUtil.Notify.error(Bundle.BlackboardArtifactNode_getAction_errorTitle(), Bundle.BlackboardArtifactNode_getAction_linkedFileMessage());
}
//if this artifact has associated content, add the action to view the content in the timeline
AbstractFile file = getLookup().lookup(AbstractFile.class);
@ -148,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();

View File

@ -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

View File

@ -74,13 +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
BlackboardArtifactNode.getAction.errorTitle=Error getting actions
BlackboardArtifactNode.getAction.linkedFileMessage=There was a problem getting actions for the selected result. The 'View File in Timeline' action will not be available.
BlackboardArtifactNode.getAction.resultErrorMessage=There was a problem getting actions for the selected result. The 'View Result in Timeline' action will not be available.
BlackboardArtifactTagNode.createSheet.userName.text=User Name
BlackboardArtifactTagNode.viewSourceArtifact.text=View Source Result
Category.five=CAT-5: Non-pertinent
@ -91,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
@ -178,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
@ -348,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

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013-2016 Basis Technology Corp.
* Copyright 2013-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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<Action> 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();
}
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2017 Basis Technology Corp.
* Copyright 2012-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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);
}
}

View File

@ -0,0 +1,128 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.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> T accept(DisplayableItemNodeVisitor<T> 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));
}
}
}
}

View File

@ -64,7 +64,7 @@ public abstract class AbstractNodePropertySheetTask<T extends AbstractNode> 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<T extends AbstractNode> 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();

View File

@ -0,0 +1,61 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.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<AbstractNode> {
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);
}
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2019 Basis Technology Corp.
* Copyright 2012-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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.

View File

@ -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

View File

@ -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.
*

View File

@ -24,19 +24,28 @@
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<EmptySpace min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<Group type="102" attributes="0">
<Component id="fileSizeLabel" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="isDeletedLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace min="-2" max="-2" attributes="0"/>
<Component id="scoreLabel" min="-2" max="-2" attributes="0"/>
</Group>
<Component id="previewScrollPane" pref="649" max="32767" attributes="0"/>
<Component id="nameLabel" alignment="0" max="32767" attributes="0"/>
<Group type="102" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="nameLabel" pref="586" max="32767" attributes="0"/>
<Component id="previewTextPane" alignment="0" pref="586" max="32767" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" max="-2" attributes="0">
<Component id="numberOfImagesLabel" max="32767" attributes="0"/>
<Component id="sampleImageLabel" max="32767" attributes="0"/>
</Group>
</Group>
</Group>
<EmptySpace max="-2" attributes="0"/>
<EmptySpace min="-2" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
@ -44,9 +53,15 @@
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="1" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Component id="nameLabel" min="-2" pref="16" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" max="-2" attributes="0">
<Component id="numberOfImagesLabel" pref="17" max="32767" attributes="0"/>
<Component id="nameLabel" max="32767" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Component id="previewScrollPane" min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="sampleImageLabel" min="-2" max="-2" attributes="0"/>
<Component id="previewTextPane" min="-2" pref="98" max="-2" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="scoreLabel" alignment="0" min="-2" max="-2" attributes="0"/>
@ -104,36 +119,39 @@
</Component>
<Component class="javax.swing.JLabel" name="nameLabel">
</Component>
<Container class="javax.swing.JScrollPane" name="previewScrollPane">
<Component class="javax.swing.JLabel" name="sampleImageLabel">
<Properties>
<Property name="verticalScrollBarPolicy" type="int" value="21"/>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.EtchedBorderInfo">
<EtchetBorder/>
</Border>
</Property>
<Property name="iconTextGap" type="int" value="0"/>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[100, 100]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[100, 100]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[100, 100]"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="numberOfImagesLabel">
</Component>
<Component class="javax.swing.JTextPane" name="previewTextPane">
<Properties>
<Property name="editable" type="boolean" value="false"/>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.EtchedBorderInfo">
<EtchetBorder/>
</Border>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="new AutoWrappingJTextPane()"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Component class="javax.swing.JTextArea" name="previewTextArea">
<Properties>
<Property name="editable" type="boolean" value="false"/>
<Property name="columns" type="int" value="20"/>
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
<Font name="Tahoma" size="11" style="0"/>
</Property>
<Property name="lineWrap" type="boolean" value="true"/>
<Property name="rows" type="int" value="5"/>
<Property name="wrapStyleWord" type="boolean" value="true"/>
<Property name="enabled" type="boolean" value="false"/>
<Property name="focusable" type="boolean" value="false"/>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[164, 94]"/>
</Property>
</Properties>
</Component>
</SubComponents>
</Container>
</Component>
</SubComponents>
</Form>

View File

@ -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<? extends DocumentWrapper> 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());

View File

@ -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;
}
}

View File

@ -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<Image> 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<Image> createDefaultThumbnailList() {
private static List<Image> createDefaultThumbnailList(BufferedImage failedVideoThumbImage) {
List<Image> 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;
}

View File

@ -273,7 +273,6 @@ final class FileSearchData {
= new ImmutableSet.Builder<String>()
.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<String> IMAGE_UNSUPPORTED_DOC_TYPES
= new ImmutableSet.Builder<String>()
.add("application/pdf", //NON-NLS
"application/xhtml+xml").build(); //NON-NLS
static Collection<String> 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

View File

@ -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();

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -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.");
}

View File

@ -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;
}

View File

@ -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

View File

@ -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);
}
}

View File

@ -0,0 +1,74 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.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() {
}
}

View File

@ -14,8 +14,7 @@
<!-- for viewers -->
<dependency conf="autopsy_core->*" org="org.freedesktop.gstreamer" name="gst1-java-core" rev="1.0.0"/>
<dependency conf="autopsy_core->*" org="net.java.dev.jna" name="jna" rev="3.4.0"/>
<dependency conf="autopsy_core->*" org="net.java.dev.jna" name="platform" rev="3.4.0"/>
<dependency conf="autopsy_core->*" org="net.java.dev.jna" name="jna-platform" rev="5.5.0"/>
<!-- for file search -->
<dependency conf="autopsy_core->*" org="com.github.lgooddatepicker" name="LGoodDatePicker" rev="10.3.1"/>
@ -73,8 +72,5 @@
<dependency conf="autopsy_core->default" org="com.googlecode.plist" name="dd-plist" rev="1.20"/>
<exclude org="*" ext="*" type="javadoc"/>
<!-- conflict resolutions for multiple JAR versions -->
<conflict org="net.java.dev.jna" module="jna" rev="3.4.0"/>
<conflict org="net.java.dev.jna" module="platform" rev="3.4.0"/>
</dependencies>
</ivy-module>

View File

@ -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

View File

@ -806,10 +806,6 @@
<runtime-relative-path>ext/sigar-1.6.4.jar</runtime-relative-path>
<binary-origin>release/modules/ext/sigar-1.6.4.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/jna-3.4.0.jar</runtime-relative-path>
<binary-origin>release/modules/ext/jna-3.4.0.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/gson-2.8.5.jar</runtime-relative-path>
<binary-origin>release/modules/ext/gson-2.8.5.jar</binary-origin>
@ -902,6 +898,10 @@
<runtime-relative-path>ext/commons-csv-1.4.jar</runtime-relative-path>
<binary-origin>release/modules/ext/commons-csv-1.4.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/jna-5.5.0.jar</runtime-relative-path>
<binary-origin>release/modules/ext/jna-5.5.0.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/imageio-sgi-3.2.jar</runtime-relative-path>
<binary-origin>release/modules/ext/imageio-sgi-3.2.jar</binary-origin>
@ -946,10 +946,6 @@
<runtime-relative-path>ext/imageio-bmp-3.2.jar</runtime-relative-path>
<binary-origin>release/modules/ext/imageio-bmp-3.2.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/platform-3.4.0.jar</runtime-relative-path>
<binary-origin>release/modules/ext/platform-3.4.0.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/commons-lang-2.6.jar</runtime-relative-path>
<binary-origin>release/modules/ext/commons-lang-2.6.jar</binary-origin>
@ -1018,6 +1014,10 @@
<runtime-relative-path>ext/dom4j-1.6.1.jar</runtime-relative-path>
<binary-origin>release/modules/ext/dom4j-1.6.1.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/jna-platform-5.5.0.jar</runtime-relative-path>
<binary-origin>release/modules/ext/jna-platform-5.5.0.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/imageio-metadata-3.2.jar</runtime-relative-path>
<binary-origin>release/modules/ext/imageio-metadata-3.2.jar</binary-origin>

View File

@ -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))

View File

@ -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())

View File

@ -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

View File

@ -1,7 +1,7 @@
"""
Autopsy Forensic Browser
Copyright 2016 Basis Technology Corp.
Copyright 2016-2020 Basis Technology Corp.
Contact: carrier <at> sleuthkit <dot> 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

View File

@ -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))
# 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
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())
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)

View File

@ -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):
"""

View File

@ -117,13 +117,25 @@ class ViberAnalyzer(general.AndroidComponentAnalyzer):
try:
contacts_parser = ViberContactsParser(contacts_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()
)
if (not(not contacts_parser.get_phone() or contacts_parser.get_phone().isspace())):
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()
)
# Check if contact_name is blank and if it is not create a TSK_CONTACT otherwise ignore as not Contact Info
elif (not(not contacts_parser.get_contact_name() or contacts_parser.get_contact_name().isspace())):
current_case = Case.getCurrentCase().getSleuthkitCase()
attributes = ArrayList()
artifact = contacts_db.getDBFile().newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT)
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME.getTypeID(), self._PARSER_NAME, contacts_parser.get_contact_name()))
artifact.addAttributes(attributes)
# Post the artifact to blackboard
current_case.getBlackboard().postArtifact(artifact, self._PARSER_NAME)
contacts_parser.close()
except SQLException as ex:
self._logger.log(Level.WARNING, "Error querying the viber database for contacts.", ex)
@ -269,7 +281,7 @@ class ViberContactsParser(TskContactsParser):
super(ViberContactsParser, self).__init__(contact_db.runQuery(
"""
SELECT C.display_name AS name,
D.data2 AS number
coalesce(D.data2, D.data1, D.data3) AS number
FROM phonebookcontact AS C
JOIN phonebookdata AS D
ON C._id = D.contact_id

View File

@ -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,7 +434,13 @@ 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(

View File

@ -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=<html>The text index for this case is read-only. <br />You will be able to see existing keyword search results and perform exact match and substring match keyword searches,<br />but you will not be able to add new text to the index or perform regex searches. You may instead open the case<br /> with your previous version of this application.</html>
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=

View File

@ -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=<html>The text index for this case is read-only. <br />You will be able to see existing keyword search results and perform exact match and substring match keyword searches,<br />but you will not be able to add new text to the index or perform regex searches. You may instead open the case<br /> with your previous version of this application.</html>
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=

View File

@ -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=<html>\u3053\u306e\u30b1\u30fc\u30b9\u306e\u30c6\u30ad\u30b9\u30c8\u7d22\u5f15\u306f\u8aad\u307f\u53d6\u308a\u5c02\u7528\u3067\u3059\u3002<br />\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<br />\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<br />\u30b1\u30fc\u30b9\u3092\u958b\u3051\u307e\u3059\u3002</html>
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:

View File

@ -26,6 +26,9 @@
</Container>
</NonVisualComponents>
<Properties>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[100, 0]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[100, 58]"/>
</Property>
@ -45,8 +48,8 @@
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="controlScrollPane" alignment="0" pref="54" max="32767" attributes="0"/>
<Component id="extractedScrollPane" alignment="0" pref="54" max="32767" attributes="0"/>
<Component id="controlScrollPane" alignment="0" pref="980" max="32767" attributes="0"/>
<Component id="extractedScrollPane" alignment="0" pref="980" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
@ -104,7 +107,7 @@
<Container class="javax.swing.JPanel" name="controlPanel">
<Properties>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[0, 0]"/>
<Dimension value="[0, 20]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[600, 81]"/>
@ -146,7 +149,9 @@
<EmptySpace min="-2" pref="0" max="-2" attributes="0"/>
<Component id="pageNextButton" min="-2" max="-2" attributes="0"/>
<EmptySpace type="separate" max="-2" attributes="0"/>
<Component id="jSeparator1" min="-2" max="-2" attributes="0"/>
<Component id="jSeparator3" min="-2" max="-2" attributes="0"/>
<EmptySpace type="separate" max="-2" attributes="0"/>
<Component id="zoomPanel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="32767" attributes="0"/>
<Component id="jLabel1" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
@ -165,7 +170,6 @@
<Component id="pageButtonsLabel" linkSize="1" alignment="2" min="-2" max="-2" attributes="0"/>
<Component id="pagePreviousButton" linkSize="1" alignment="2" min="-2" max="-2" attributes="0"/>
<Component id="pageNextButton" linkSize="1" alignment="2" min="-2" max="-2" attributes="0"/>
<Component id="jSeparator1" linkSize="1" alignment="2" min="-2" max="-2" attributes="0"/>
<Component id="sourceComboBox" linkSize="1" alignment="2" min="-2" max="-2" attributes="0"/>
<Component id="pagesLabel" linkSize="1" alignment="2" min="-2" max="-2" attributes="0"/>
<Component id="hitLabel" linkSize="1" alignment="2" min="-2" max="-2" attributes="0"/>
@ -177,6 +181,8 @@
<Component id="hitOfLabel" linkSize="1" alignment="2" min="-2" max="-2" attributes="0"/>
<Component id="hitTotalLabel" linkSize="1" alignment="2" min="-2" max="-2" attributes="0"/>
<Component id="hitButtonsLabel" linkSize="1" alignment="2" min="-2" max="-2" attributes="0"/>
<Component id="jSeparator3" linkSize="1" alignment="2" min="-2" max="-2" attributes="0"/>
<Component id="zoomPanel" linkSize="1" alignment="1" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
</Group>
@ -300,11 +306,6 @@
</Property>
</Properties>
</Component>
<Component class="javax.swing.JSeparator" name="jSeparator1">
<Properties>
<Property name="orientation" type="int" value="1"/>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="hitLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
@ -426,6 +427,32 @@
<Property name="orientation" type="int" value="1"/>
</Properties>
</Component>
<Component class="javax.swing.JSeparator" name="jSeparator3">
<Properties>
<Property name="orientation" type="int" value="1"/>
</Properties>
</Component>
<Container class="javax.swing.JPanel" name="zoomPanel">
<Properties>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[150, 20]"/>
</Property>
<Property name="name" type="java.lang.String" value="" noResource="true"/>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[200, 20]"/>
</Property>
</Properties>
<AccessibilityProperties>
<Property name="AccessibleContext.accessibleName" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/Bundle.properties" key="ExtractedContentPanel.AccessibleContext.accessibleName" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</AccessibilityProperties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="new TextZoomPanel(this)"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignFlowLayout"/>
</Container>
</SubComponents>
</Container>
</SubComponents>

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2019 Basis Technology Corp.
* Copyright 2011-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> 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,12 +132,8 @@ 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) {
@ -134,8 +143,49 @@ 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.
* WARNING: Do NOT modify this code. The content of this method is always
@ -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<org.sleuthkit.autopsy.keywordsearch.IndexedText>());
@ -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<org.sleuthkit.autopsy.keywordsearch.IndexedText> 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<IndexedText> 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);
}

View File

@ -0,0 +1,39 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.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);
}

View File

@ -0,0 +1,163 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<Properties>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[150, 20]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[200, 20]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<Component id="zoomTextField" min="-2" max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
<Component id="zoomOutButton" min="-2" max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
<Component id="zoomInButton" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="zoomResetButton" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="jSeparator2" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="jSeparator2" min="-2" max="-2" attributes="0"/>
<Group type="103" alignment="0" groupAlignment="0" attributes="0">
<Component id="zoomTextField" alignment="2" max="32767" attributes="0"/>
<Component id="zoomOutButton" alignment="2" max="32767" attributes="0"/>
<Component id="zoomInButton" alignment="2" max="32767" attributes="0"/>
<Component id="zoomResetButton" alignment="2" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Component class="javax.swing.JTextField" name="zoomTextField">
<Properties>
<Property name="editable" type="boolean" value="false"/>
<Property name="horizontalAlignment" type="int" value="4"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/Bundle.properties" key="TextZoomPanel.zoomTextField.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[50, 2147483647]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[50, 20]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[50, 20]"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JButton" name="zoomOutButton">
<Properties>
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
<Image iconType="3" name="/org/sleuthkit/autopsy/keywordsearch/zoom-out.png"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/Bundle.properties" key="TextZoomPanel.zoomOutButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="borderPainted" type="boolean" value="false"/>
<Property name="focusable" type="boolean" value="false"/>
<Property name="horizontalTextPosition" type="int" value="0"/>
<Property name="margin" type="java.awt.Insets" editor="org.netbeans.beaninfo.editors.InsetsEditor">
<Insets value="[0, 0, 0, 0]"/>
</Property>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[20, 20]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[20, 20]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[20, 20]"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="zoomOutButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JButton" name="zoomInButton">
<Properties>
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
<Image iconType="3" name="/org/sleuthkit/autopsy/keywordsearch/zoom-in.png"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/Bundle.properties" key="TextZoomPanel.zoomInButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="borderPainted" type="boolean" value="false"/>
<Property name="focusable" type="boolean" value="false"/>
<Property name="horizontalTextPosition" type="int" value="0"/>
<Property name="margin" type="java.awt.Insets" editor="org.netbeans.beaninfo.editors.InsetsEditor">
<Insets value="[0, 0, 0, 0]"/>
</Property>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[20, 20]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[20, 20]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[20, 20]"/>
</Property>
<Property name="rolloverEnabled" type="boolean" value="true"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="zoomInButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JToolBar$Separator" name="jSeparator2">
<Properties>
<Property name="orientation" type="int" value="1"/>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[6, 20]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
</Component>
<Component class="javax.swing.JButton" name="zoomResetButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/keywordsearch/Bundle.properties" key="TextZoomPanel.zoomResetButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="borderPainted" type="boolean" value="false"/>
<Property name="focusable" type="boolean" value="false"/>
<Property name="horizontalTextPosition" type="int" value="0"/>
<Property name="margin" type="java.awt.Insets" editor="org.netbeans.beaninfo.editors.InsetsEditor">
<Insets value="[0, 0, 0, 0]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[50, 20]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[70, 20]"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="zoomResetButtonActionPerformed"/>
</Events>
</Component>
</SubComponents>
</Form>

View File

@ -0,0 +1,221 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.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")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//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))
);
}// </editor-fold>//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
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 503 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 B

View File

@ -560,7 +560,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,

View File

@ -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
<table>
<tr><th>Operation</th><th>Command(s)</th><th>Parameter(s)</th><th>Example</th></tr>
<tr><td><b>Create New Case</b><td><pre>--createCase</pre></td><td><pre>--caseName
--caseBaseDir</pre></td><td><pre>--createCase --caseName="test5" --caseBaseDir="C:\work\cases"</pre></td></tr>
--caseBaseDir
--caseType (optional)</pre></td><td><pre>--createCase --caseName="test5" --caseBaseDir="C:\work\cases"
--createCase --caseName="test_multi" --caseBaseDir="\\WIN-2913\work\cases" --caseType="multi"</pre></td></tr>
<tr><td><b>Open Existing Case</b></td><td>&nbsp;</td><td><pre>--caseDir</pre></td><td><pre>--caseDir="C:\work\Cases\test5_2019_09_20_11_01_29"</pre></td></tr>
@ -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.

View File

@ -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:
<ul>
@ -20,6 +20,8 @@ Optional ingest modules:
<li>\ref hash_db_page - Needed to use the \ref file_disc_hash_filter and to de-duplicate files
<li>\ref interesting_files_identifier_page - Needed to use the \ref file_disc_int_filter
<li>\ref object_detection_page - Needed to use the \ref file_disc_obj_filter
<li>\ref keyword_search_page - Improves document summaries
<li>\ref embedded_file_extractor_page - Allows display of an image contained in a document
</ul>
\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

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 155 KiB

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 448 KiB

After

Width:  |  Height:  |  Size: 477 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 265 KiB

After

Width:  |  Height:  |  Size: 287 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 418 KiB

After

Width:  |  Height:  |  Size: 420 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -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.
*/