Merge branch 'develop' of github.com:sleuthkit/autopsy into 6104-persona-accounts-test
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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++;
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
|
@ -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++;
|
||||
|
@ -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().
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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, "{key}")"/>
|
||||
</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>
|
||||
|
@ -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;
|
||||
|
74
Core/src/org/sleuthkit/autopsy/contentviewers/utils/GstLoader.java
Executable 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() {
|
||||
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
@ -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 "";
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
128
Core/src/org/sleuthkit/autopsy/datamodel/TagNode.java
Executable 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
|
61
Core/src/org/sleuthkit/autopsy/datamodel/utils/FileNameTransTask.java
Executable 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);
|
||||
}
|
||||
|
||||
}
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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>
|
||||
|
@ -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());
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 2.4 KiB |
@ -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.");
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
}
|
||||
|
||||
}
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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))
|
||||
|
@ -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())
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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=
|
||||
|
@ -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=
|
||||
|
@ -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:
|
||||
|
@ -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, "{key}")"/>
|
||||
</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>
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
}
|
@ -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, "{key}")"/>
|
||||
</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, "{key}")"/>
|
||||
</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, "{key}")"/>
|
||||
</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, "{key}")"/>
|
||||
</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>
|
@ -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
|
||||
}
|
After Width: | Height: | Size: 503 B |
After Width: | Height: | Size: 518 B |
@ -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,
|
||||
|
@ -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> </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.
|
||||
|
@ -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
|
||||
|
||||
|
BIN
docs/doxygen-user/images/FileDiscovery/fd_documents.png
Normal file
After Width: | Height: | Size: 79 KiB |
Before Width: | Height: | Size: 155 KiB After Width: | Height: | Size: 189 KiB |
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 158 KiB |
Before Width: | Height: | Size: 448 KiB After Width: | Height: | Size: 477 KiB |
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 107 KiB |
Before Width: | Height: | Size: 265 KiB After Width: | Height: | Size: 287 KiB |
Before Width: | Height: | Size: 418 KiB After Width: | Height: | Size: 420 KiB |
BIN
docs/doxygen-user/images/module_install_netbeans.png
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
docs/doxygen-user/images/module_install_python.png
Normal file
After Width: | Height: | Size: 39 KiB |
@ -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.
|
||||
|
||||
*/
|
||||
|