mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-14 17:06:16 +00:00
Merge branch 'develop' of https://github.com/sleuthkit/autopsy into sqliteviewer
This commit is contained in:
commit
e984dc49b0
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2017 Basis Technology Corp.
|
||||
* Copyright 2017-2018 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -38,7 +38,6 @@ import org.openide.util.actions.Presenter;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.services.TagsManager;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifactTag;
|
||||
import org.sleuthkit.datamodel.TagName;
|
||||
@ -54,7 +53,7 @@ import org.sleuthkit.datamodel.TskData;
|
||||
})
|
||||
public class DeleteFileBlackboardArtifactTagAction extends AbstractAction implements Presenter.Popup {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(DeleteFileBlackboardArtifactTagAction.class.getName());
|
||||
private static final Logger logger = Logger.getLogger(DeleteFileBlackboardArtifactTagAction.class.getName());
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String MENU_TEXT = NbBundle.getMessage(DeleteFileBlackboardArtifactTagAction.class,
|
||||
@ -98,17 +97,11 @@ public class DeleteFileBlackboardArtifactTagAction extends AbstractAction implem
|
||||
protected Void doInBackground() throws Exception {
|
||||
TagsManager tagsManager = Case.getCurrentCase().getServices().getTagsManager();
|
||||
|
||||
// Pull the from the global context to avoid unnecessary calls
|
||||
// to the database.
|
||||
final Collection<AbstractFile> selectedFilesList
|
||||
= new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class));
|
||||
AbstractFile file = selectedFilesList.iterator().next();
|
||||
|
||||
try {
|
||||
LOGGER.log(Level.INFO, "Removing tag {0} from {1}", new Object[]{tagName.getDisplayName(), file.getName()}); //NON-NLS
|
||||
logger.log(Level.INFO, "Removing tag {0} from {1}", new Object[]{tagName.getDisplayName(), artifactTag.getContent().getName()}); //NON-NLS
|
||||
tagsManager.deleteBlackboardArtifactTag(artifactTag);
|
||||
} catch (TskCoreException tskCoreException) {
|
||||
LOGGER.log(Level.SEVERE, "Error untagging artifact", tskCoreException); //NON-NLS
|
||||
logger.log(Level.SEVERE, "Error untagging artifact", tskCoreException); //NON-NLS
|
||||
Platform.runLater(()
|
||||
-> new Alert(Alert.AlertType.ERROR, Bundle.DeleteFileBlackboardArtifactTagAction_deleteTag_alert(artifactId)).show()
|
||||
);
|
||||
@ -122,7 +115,7 @@ public class DeleteFileBlackboardArtifactTagAction extends AbstractAction implem
|
||||
try {
|
||||
get();
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Unexpected exception while untagging artifact", ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, "Unexpected exception while untagging artifact", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
}.execute();
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2017 Basis Technology Corp.
|
||||
* Copyright 2017-2018 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -52,7 +52,7 @@ import org.sleuthkit.datamodel.TskData;
|
||||
})
|
||||
public class DeleteFileContentTagAction extends AbstractAction implements Presenter.Popup {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(DeleteFileContentTagAction.class.getName());
|
||||
private static final Logger logger = Logger.getLogger(DeleteFileContentTagAction.class.getName());
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String MENU_TEXT = NbBundle.getMessage(DeleteFileContentTagAction.class,
|
||||
@ -97,17 +97,11 @@ public class DeleteFileContentTagAction extends AbstractAction implements Presen
|
||||
protected Void doInBackground() throws Exception {
|
||||
TagsManager tagsManager = Case.getCurrentCase().getServices().getTagsManager();
|
||||
|
||||
// Pull the from the global context to avoid unnecessary calls
|
||||
// to the database.
|
||||
final Collection<AbstractFile> selectedFilesList =
|
||||
new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class));
|
||||
AbstractFile file = selectedFilesList.iterator().next();
|
||||
|
||||
try {
|
||||
LOGGER.log(Level.INFO, "Removing tag {0} from {1}", new Object[]{tagName.getDisplayName(), file.getName()}); //NON-NLS
|
||||
logger.log(Level.INFO, "Removing tag {0} from {1}", new Object[]{tagName.getDisplayName(), contentTag.getContent().getName()}); //NON-NLS
|
||||
tagsManager.deleteContentTag(contentTag);
|
||||
} catch (TskCoreException tskCoreException) {
|
||||
LOGGER.log(Level.SEVERE, "Error untagging file", tskCoreException); //NON-NLS
|
||||
logger.log(Level.SEVERE, "Error untagging file", tskCoreException); //NON-NLS
|
||||
Platform.runLater(() ->
|
||||
new Alert(Alert.AlertType.ERROR, Bundle.DeleteFileContentTagAction_deleteTag_alert(fileId)).show()
|
||||
);
|
||||
@ -121,7 +115,7 @@ public class DeleteFileContentTagAction extends AbstractAction implements Presen
|
||||
try {
|
||||
get();
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Unexpected exception while untagging file", ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, "Unexpected exception while untagging file", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
}.execute();
|
||||
|
@ -29,6 +29,8 @@ import org.netbeans.swing.outline.Outline;
|
||||
import org.openide.nodes.Node;
|
||||
import java.awt.EventQueue;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
@ -207,28 +209,34 @@ class CaseBrowser extends javax.swing.JPanel implements ExplorerManager.Provider
|
||||
List<String> nodeList = CoordinationService.getInstance().getNodeList(CoordinationService.CategoryNode.CASES);
|
||||
|
||||
for (String node : nodeList) {
|
||||
Path casePath = Paths.get(node);
|
||||
File caseFolder = casePath.toFile();
|
||||
if (caseFolder.exists()) {
|
||||
/*
|
||||
* Search for '*.aut' files.
|
||||
*/
|
||||
File[] fileArray = caseFolder.listFiles();
|
||||
if (fileArray == null) {
|
||||
continue;
|
||||
}
|
||||
String autFilePath = null;
|
||||
for (File file : fileArray) {
|
||||
String name = file.getName().toLowerCase();
|
||||
if (autFilePath == null && name.endsWith(".aut")) {
|
||||
try {
|
||||
caseList.add(new CaseMetadata(Paths.get(file.getAbsolutePath())));
|
||||
} catch (CaseMetadata.CaseMetadataException ex) {
|
||||
LOGGER.log(Level.SEVERE, String.format("Error reading case metadata file '%s'.", autFilePath), ex);
|
||||
Path casePath;
|
||||
try {
|
||||
casePath = Paths.get(node).toRealPath(LinkOption.NOFOLLOW_LINKS);
|
||||
|
||||
File caseFolder = casePath.toFile();
|
||||
if (caseFolder.exists()) {
|
||||
/*
|
||||
* Search for '*.aut' files.
|
||||
*/
|
||||
File[] fileArray = caseFolder.listFiles();
|
||||
if (fileArray == null) {
|
||||
continue;
|
||||
}
|
||||
String autFilePath = null;
|
||||
for (File file : fileArray) {
|
||||
String name = file.getName().toLowerCase();
|
||||
if (autFilePath == null && name.endsWith(".aut")) {
|
||||
try {
|
||||
caseList.add(new CaseMetadata(Paths.get(file.getAbsolutePath())));
|
||||
} catch (CaseMetadata.CaseMetadataException ex) {
|
||||
LOGGER.log(Level.SEVERE, String.format("Error reading case metadata file '%s'.", autFilePath), ex);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (IOException ignore) {
|
||||
//if a path could not be resolved to a real path do add it to the caseList
|
||||
}
|
||||
}
|
||||
return caseList;
|
||||
|
@ -489,12 +489,16 @@ public class DataContentViewerOtherCases extends javax.swing.JPanel implements D
|
||||
corAttrInstances.addAll(getCorrelatedInstances(corAttr, dataSourceName, deviceId));
|
||||
|
||||
corAttrInstances.forEach((corAttrInstance) -> {
|
||||
CorrelationAttribute newCeArtifact = new CorrelationAttribute(
|
||||
corAttr.getCorrelationType(),
|
||||
corAttr.getCorrelationValue()
|
||||
);
|
||||
newCeArtifact.addInstance(corAttrInstance);
|
||||
tableModel.addEamArtifact(newCeArtifact);
|
||||
try {
|
||||
CorrelationAttribute newCeArtifact = new CorrelationAttribute(
|
||||
corAttr.getCorrelationType(),
|
||||
corAttr.getCorrelationValue()
|
||||
);
|
||||
newCeArtifact.addInstance(corAttrInstance);
|
||||
tableModel.addEamArtifact(newCeArtifact);
|
||||
} catch (EamDbException ex){
|
||||
LOGGER.log(Level.SEVERE, "Error creating correlation attribute", ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -271,6 +271,10 @@ public abstract class AbstractSqlEamDb implements EamDb {
|
||||
*/
|
||||
@Override
|
||||
public void updateCase(CorrelationCase eamCase) throws EamDbException {
|
||||
if(eamCase == null) {
|
||||
throw new EamDbException("CorrelationCase argument is null");
|
||||
}
|
||||
|
||||
Connection conn = connect();
|
||||
|
||||
PreparedStatement preparedStatement = null;
|
||||
@ -444,6 +448,10 @@ public abstract class AbstractSqlEamDb implements EamDb {
|
||||
*/
|
||||
@Override
|
||||
public CorrelationDataSource getDataSource(CorrelationCase correlationCase, String dataSourceDeviceId) throws EamDbException {
|
||||
if(correlationCase == null) {
|
||||
throw new EamDbException("CorrelationCase argument is null");
|
||||
}
|
||||
|
||||
Connection conn = connect();
|
||||
|
||||
CorrelationDataSource eamDataSourceResult = null;
|
||||
@ -513,6 +521,16 @@ public abstract class AbstractSqlEamDb implements EamDb {
|
||||
*/
|
||||
@Override
|
||||
public void addArtifact(CorrelationAttribute eamArtifact) throws EamDbException {
|
||||
if(eamArtifact == null) {
|
||||
throw new EamDbException("CorrelationAttribute is null");
|
||||
}
|
||||
if(eamArtifact.getCorrelationType() == null) {
|
||||
throw new EamDbException("Correlation type is null");
|
||||
}
|
||||
if(eamArtifact.getCorrelationValue() == null) {
|
||||
throw new EamDbException("Correlation value is null");
|
||||
}
|
||||
|
||||
Connection conn = connect();
|
||||
|
||||
List<CorrelationAttributeInstance> eamInstances = eamArtifact.getInstances();
|
||||
@ -526,11 +544,21 @@ public abstract class AbstractSqlEamDb implements EamDb {
|
||||
sql.append("(case_id, data_source_id, value, file_path, known_status, comment) ");
|
||||
sql.append("VALUES ((SELECT id FROM cases WHERE case_uid=? LIMIT 1), ");
|
||||
sql.append("(SELECT id FROM data_sources WHERE device_id=? AND case_id=? LIMIT 1), ?, ?, ?, ?)");
|
||||
|
||||
|
||||
try {
|
||||
preparedStatement = conn.prepareStatement(sql.toString());
|
||||
for (CorrelationAttributeInstance eamInstance : eamInstances) {
|
||||
if (!eamArtifact.getCorrelationValue().isEmpty()) {
|
||||
if(eamInstance.getCorrelationCase() == null) {
|
||||
throw new EamDbException("CorrelationAttributeInstance has null case");
|
||||
}
|
||||
if(eamInstance.getCorrelationDataSource() == null) {
|
||||
throw new EamDbException("CorrelationAttributeInstance has null data source");
|
||||
}
|
||||
if(eamInstance.getKnownStatus() == null) {
|
||||
throw new EamDbException("CorrelationAttributeInstance has null known status");
|
||||
}
|
||||
|
||||
preparedStatement.setString(1, eamInstance.getCorrelationCase().getCaseUUID());
|
||||
preparedStatement.setString(2, eamInstance.getCorrelationDataSource().getDeviceID());
|
||||
preparedStatement.setInt(3, eamInstance.getCorrelationDataSource().getCaseID());
|
||||
@ -567,6 +595,9 @@ public abstract class AbstractSqlEamDb implements EamDb {
|
||||
*/
|
||||
@Override
|
||||
public List<CorrelationAttributeInstance> getArtifactInstancesByTypeValue(CorrelationAttribute.Type aType, String value) throws EamDbException {
|
||||
if(aType == null) {
|
||||
throw new EamDbException("Correlation type is null");
|
||||
}
|
||||
Connection conn = connect();
|
||||
|
||||
List<CorrelationAttributeInstance> artifactInstances = new ArrayList<>();
|
||||
@ -619,6 +650,12 @@ public abstract class AbstractSqlEamDb implements EamDb {
|
||||
*/
|
||||
@Override
|
||||
public List<CorrelationAttributeInstance> getArtifactInstancesByPath(CorrelationAttribute.Type aType, String filePath) throws EamDbException {
|
||||
if(aType == null) {
|
||||
throw new EamDbException("Correlation type is null");
|
||||
}
|
||||
if(filePath == null) {
|
||||
throw new EamDbException("Correlation value is null");
|
||||
}
|
||||
Connection conn = connect();
|
||||
|
||||
List<CorrelationAttributeInstance> artifactInstances = new ArrayList<>();
|
||||
@ -641,7 +678,7 @@ public abstract class AbstractSqlEamDb implements EamDb {
|
||||
|
||||
try {
|
||||
preparedStatement = conn.prepareStatement(sql.toString());
|
||||
preparedStatement.setString(1, filePath);
|
||||
preparedStatement.setString(1, filePath.toLowerCase());
|
||||
resultSet = preparedStatement.executeQuery();
|
||||
while (resultSet.next()) {
|
||||
artifactInstance = getEamArtifactInstanceFromResultSet(resultSet);
|
||||
@ -670,6 +707,13 @@ public abstract class AbstractSqlEamDb implements EamDb {
|
||||
*/
|
||||
@Override
|
||||
public Long getCountArtifactInstancesByTypeValue(CorrelationAttribute.Type aType, String value) throws EamDbException {
|
||||
if(aType == null) {
|
||||
throw new EamDbException("Correlation type is null");
|
||||
}
|
||||
if(value == null) {
|
||||
throw new EamDbException("Correlation value is null");
|
||||
}
|
||||
|
||||
Connection conn = connect();
|
||||
|
||||
Long instanceCount = 0L;
|
||||
@ -684,7 +728,7 @@ public abstract class AbstractSqlEamDb implements EamDb {
|
||||
|
||||
try {
|
||||
preparedStatement = conn.prepareStatement(sql.toString());
|
||||
preparedStatement.setString(1, value);
|
||||
preparedStatement.setString(1, value.toLowerCase());
|
||||
resultSet = preparedStatement.executeQuery();
|
||||
resultSet.next();
|
||||
instanceCount = resultSet.getLong(1);
|
||||
@ -701,6 +745,9 @@ public abstract class AbstractSqlEamDb implements EamDb {
|
||||
|
||||
@Override
|
||||
public int getFrequencyPercentage(CorrelationAttribute corAttr) throws EamDbException {
|
||||
if (corAttr == null) {
|
||||
throw new EamDbException("Correlation attribute is null");
|
||||
}
|
||||
Double uniqueTypeValueTuples = getCountUniqueCaseDataSourceTuplesHavingTypeValue(corAttr.getCorrelationType(), corAttr.getCorrelationValue()).doubleValue();
|
||||
Double uniqueCaseDataSourceTuples = getCountUniqueDataSources().doubleValue();
|
||||
Double commonalityPercentage = uniqueTypeValueTuples / uniqueCaseDataSourceTuples * 100;
|
||||
@ -719,6 +766,10 @@ public abstract class AbstractSqlEamDb implements EamDb {
|
||||
*/
|
||||
@Override
|
||||
public Long getCountUniqueCaseDataSourceTuplesHavingTypeValue(CorrelationAttribute.Type aType, String value) throws EamDbException {
|
||||
if(aType == null) {
|
||||
throw new EamDbException("Correlation type is null");
|
||||
}
|
||||
|
||||
Connection conn = connect();
|
||||
|
||||
Long instanceCount = 0L;
|
||||
@ -840,6 +891,10 @@ public abstract class AbstractSqlEamDb implements EamDb {
|
||||
@Override
|
||||
public void prepareBulkArtifact(CorrelationAttribute eamArtifact) throws EamDbException {
|
||||
|
||||
if(eamArtifact.getCorrelationType() == null) {
|
||||
throw new EamDbException("Correlation type is null");
|
||||
}
|
||||
|
||||
synchronized (bulkArtifacts) {
|
||||
bulkArtifacts.get(eamArtifact.getCorrelationType().getDbTableName()).add(eamArtifact);
|
||||
bulkArtifactsCount++;
|
||||
@ -893,6 +948,17 @@ public abstract class AbstractSqlEamDb implements EamDb {
|
||||
|
||||
for (CorrelationAttributeInstance eamInstance : eamInstances) {
|
||||
if (!eamArtifact.getCorrelationValue().isEmpty()) {
|
||||
|
||||
if(eamInstance.getCorrelationCase() == null) {
|
||||
throw new EamDbException("Correlation attribute instance has null case");
|
||||
}
|
||||
if(eamInstance.getCorrelationDataSource() == null) {
|
||||
throw new EamDbException("Correlation attribute instance has null data source");
|
||||
}
|
||||
if(eamInstance.getKnownStatus()== null) {
|
||||
throw new EamDbException("Correlation attribute instance has null known known status");
|
||||
}
|
||||
|
||||
bulkPs.setString(1, eamInstance.getCorrelationCase().getCaseUUID());
|
||||
bulkPs.setString(2, eamInstance.getCorrelationDataSource().getDeviceID());
|
||||
bulkPs.setInt(3, eamInstance.getCorrelationDataSource().getCaseID());
|
||||
@ -929,12 +995,16 @@ public abstract class AbstractSqlEamDb implements EamDb {
|
||||
*/
|
||||
@Override
|
||||
public void bulkInsertCases(List<CorrelationCase> cases) throws EamDbException {
|
||||
Connection conn = connect();
|
||||
|
||||
if(cases == null) {
|
||||
throw new EamDbException("cases argument is null");
|
||||
}
|
||||
|
||||
if (cases.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Connection conn = connect();
|
||||
|
||||
int counter = 0;
|
||||
PreparedStatement bulkPs = null;
|
||||
try {
|
||||
@ -1012,15 +1082,28 @@ public abstract class AbstractSqlEamDb implements EamDb {
|
||||
*/
|
||||
@Override
|
||||
public void setArtifactInstanceKnownStatus(CorrelationAttribute eamArtifact, TskData.FileKnown knownStatus) throws EamDbException {
|
||||
Connection conn = connect();
|
||||
|
||||
if(eamArtifact == null) {
|
||||
throw new EamDbException("Correlation attribute is null");
|
||||
}
|
||||
if(knownStatus == null) {
|
||||
throw new EamDbException("Known status is null");
|
||||
}
|
||||
if (1 != eamArtifact.getInstances().size()) {
|
||||
throw new EamDbException("Error: Artifact must have exactly one (1) Artifact Instance to set as notable."); // NON-NLS
|
||||
}
|
||||
|
||||
|
||||
List<CorrelationAttributeInstance> eamInstances = eamArtifact.getInstances();
|
||||
CorrelationAttributeInstance eamInstance = eamInstances.get(0);
|
||||
|
||||
if(eamInstance.getCorrelationCase() == null) {
|
||||
throw new EamDbException("Correlation case is null");
|
||||
}
|
||||
if(eamInstance.getCorrelationDataSource() == null) {
|
||||
throw new EamDbException("Correlation data source is null");
|
||||
}
|
||||
|
||||
Connection conn = connect();
|
||||
|
||||
PreparedStatement preparedUpdate = null;
|
||||
PreparedStatement preparedQuery = null;
|
||||
ResultSet resultSet = null;
|
||||
@ -1103,6 +1186,10 @@ public abstract class AbstractSqlEamDb implements EamDb {
|
||||
*/
|
||||
@Override
|
||||
public List<CorrelationAttributeInstance> getArtifactInstancesKnownBad(CorrelationAttribute.Type aType, String value) throws EamDbException {
|
||||
if(aType == null) {
|
||||
throw new EamDbException("Correlation type is null");
|
||||
}
|
||||
|
||||
Connection conn = connect();
|
||||
|
||||
List<CorrelationAttributeInstance> artifactInstances = new ArrayList<>();
|
||||
@ -1153,6 +1240,10 @@ public abstract class AbstractSqlEamDb implements EamDb {
|
||||
*/
|
||||
@Override
|
||||
public Long getCountArtifactInstancesKnownBad(CorrelationAttribute.Type aType, String value) throws EamDbException {
|
||||
if(aType == null) {
|
||||
throw new EamDbException("Correlation type is null");
|
||||
}
|
||||
|
||||
Connection conn = connect();
|
||||
|
||||
Long badInstances = 0L;
|
||||
@ -1197,6 +1288,10 @@ public abstract class AbstractSqlEamDb implements EamDb {
|
||||
*/
|
||||
@Override
|
||||
public List<String> getListCasesHavingArtifactInstancesKnownBad(CorrelationAttribute.Type aType, String value) throws EamDbException {
|
||||
if(aType == null) {
|
||||
throw new EamDbException("Correlation type is null");
|
||||
}
|
||||
|
||||
Connection conn = connect();
|
||||
|
||||
Collection<String> caseNames = new LinkedHashSet<>();
|
||||
@ -1313,7 +1408,7 @@ public abstract class AbstractSqlEamDb implements EamDb {
|
||||
@Override
|
||||
public boolean referenceSetIsValid(int referenceSetID, String setName, String version) throws EamDbException {
|
||||
EamGlobalSet refSet = this.getReferenceSetByID(referenceSetID);
|
||||
if (refSet == null) {
|
||||
if(refSet == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1382,6 +1477,9 @@ public abstract class AbstractSqlEamDb implements EamDb {
|
||||
*/
|
||||
@Override
|
||||
public boolean isArtifactKnownBadByReference(CorrelationAttribute.Type aType, String value) throws EamDbException {
|
||||
if(aType == null) {
|
||||
throw new EamDbException("null correlation type");
|
||||
}
|
||||
|
||||
// TEMP: Only support file correlation type
|
||||
if (aType.getId() != CorrelationAttribute.FILES_TYPE_ID) {
|
||||
@ -1424,6 +1522,10 @@ public abstract class AbstractSqlEamDb implements EamDb {
|
||||
*/
|
||||
@Override
|
||||
public long newOrganization(EamOrganization eamOrg) throws EamDbException {
|
||||
if(eamOrg == null) {
|
||||
throw new EamDbException("EamOrganization is null");
|
||||
}
|
||||
|
||||
Connection conn = connect();
|
||||
ResultSet generatedKeys = null;
|
||||
PreparedStatement preparedStatement = null;
|
||||
@ -1529,6 +1631,9 @@ public abstract class AbstractSqlEamDb implements EamDb {
|
||||
public EamOrganization getReferenceSetOrganization(int referenceSetID) throws EamDbException {
|
||||
|
||||
EamGlobalSet globalSet = getReferenceSetByID(referenceSetID);
|
||||
if(globalSet == null) {
|
||||
throw new EamDbException("Reference set with ID " + referenceSetID + " not found");
|
||||
}
|
||||
return (getOrganizationByID(globalSet.getOrgID()));
|
||||
}
|
||||
|
||||
@ -1542,6 +1647,10 @@ public abstract class AbstractSqlEamDb implements EamDb {
|
||||
*/
|
||||
@Override
|
||||
public void updateOrganization(EamOrganization updatedOrganization) throws EamDbException {
|
||||
if(updatedOrganization == null) {
|
||||
throw new EamDbException("null updatedOrganization");
|
||||
}
|
||||
|
||||
Connection conn = connect();
|
||||
PreparedStatement preparedStatement = null;
|
||||
String sql = "UPDATE organizations SET org_name = ?, poc_name = ?, poc_email = ?, poc_phone = ? WHERE id = ?";
|
||||
@ -1566,6 +1675,10 @@ public abstract class AbstractSqlEamDb implements EamDb {
|
||||
"AbstractSqlEamDb.deleteOrganization.errorDeleting.message=Error executing query when attempting to delete organization by id."})
|
||||
@Override
|
||||
public void deleteOrganization(EamOrganization organizationToDelete) throws EamDbException {
|
||||
if(organizationToDelete == null) {
|
||||
throw new EamDbException("Organization to delete is null");
|
||||
}
|
||||
|
||||
Connection conn = connect();
|
||||
PreparedStatement checkIfUsedStatement = null;
|
||||
ResultSet resultSet = null;
|
||||
@ -1605,6 +1718,18 @@ public abstract class AbstractSqlEamDb implements EamDb {
|
||||
*/
|
||||
@Override
|
||||
public int newReferenceSet(EamGlobalSet eamGlobalSet) throws EamDbException {
|
||||
if(eamGlobalSet == null){
|
||||
throw new EamDbException("EamGlobalSet argument is null");
|
||||
}
|
||||
|
||||
if(eamGlobalSet.getFileKnownStatus() == null){
|
||||
throw new EamDbException("File known status on the EamGlobalSet is null");
|
||||
}
|
||||
|
||||
if(eamGlobalSet.getType() == null){
|
||||
throw new EamDbException("Type on the EamGlobalSet is null");
|
||||
}
|
||||
|
||||
Connection conn = connect();
|
||||
|
||||
PreparedStatement preparedStatement1 = null;
|
||||
@ -1666,8 +1791,11 @@ public abstract class AbstractSqlEamDb implements EamDb {
|
||||
preparedStatement1 = conn.prepareStatement(sql1);
|
||||
preparedStatement1.setInt(1, referenceSetID);
|
||||
resultSet = preparedStatement1.executeQuery();
|
||||
resultSet.next();
|
||||
return getEamGlobalSetFromResultSet(resultSet);
|
||||
if(resultSet.next()) {
|
||||
return getEamGlobalSetFromResultSet(resultSet);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
} catch (SQLException ex) {
|
||||
throw new EamDbException("Error getting reference set by id.", ex); // NON-NLS
|
||||
@ -1689,6 +1817,11 @@ public abstract class AbstractSqlEamDb implements EamDb {
|
||||
*/
|
||||
@Override
|
||||
public List<EamGlobalSet> getAllReferenceSets(CorrelationAttribute.Type correlationType) throws EamDbException {
|
||||
|
||||
if(correlationType == null){
|
||||
throw new EamDbException("Correlation type is null");
|
||||
}
|
||||
|
||||
List<EamGlobalSet> results = new ArrayList<>();
|
||||
Connection conn = connect();
|
||||
|
||||
@ -1723,6 +1856,13 @@ public abstract class AbstractSqlEamDb implements EamDb {
|
||||
*/
|
||||
@Override
|
||||
public void addReferenceInstance(EamGlobalFileInstance eamGlobalFileInstance, CorrelationAttribute.Type correlationType) throws EamDbException {
|
||||
if(eamGlobalFileInstance.getKnownStatus() == null){
|
||||
throw new EamDbException("known status of EamGlobalFileInstance is null");
|
||||
}
|
||||
if(correlationType == null){
|
||||
throw new EamDbException("Correlation type is null");
|
||||
}
|
||||
|
||||
Connection conn = connect();
|
||||
|
||||
PreparedStatement preparedStatement = null;
|
||||
@ -1786,6 +1926,13 @@ public abstract class AbstractSqlEamDb implements EamDb {
|
||||
*/
|
||||
@Override
|
||||
public void bulkInsertReferenceTypeEntries(Set<EamGlobalFileInstance> globalInstances, CorrelationAttribute.Type contentType) throws EamDbException {
|
||||
if(contentType == null) {
|
||||
throw new EamDbException("Null correlation type");
|
||||
}
|
||||
if(globalInstances == null) {
|
||||
throw new EamDbException("Null set of EamGlobalFileInstance");
|
||||
}
|
||||
|
||||
Connection conn = connect();
|
||||
|
||||
PreparedStatement bulkPs = null;
|
||||
@ -1799,6 +1946,10 @@ public abstract class AbstractSqlEamDb implements EamDb {
|
||||
bulkPs = conn.prepareStatement(String.format(sql, EamDbUtil.correlationTypeToReferenceTableName(contentType)));
|
||||
|
||||
for (EamGlobalFileInstance globalInstance : globalInstances) {
|
||||
if(globalInstance.getKnownStatus() == null){
|
||||
throw new EamDbException("EamGlobalFileInstance with value " + globalInstance.getMD5Hash() + " has null known status");
|
||||
}
|
||||
|
||||
bulkPs.setInt(1, globalInstance.getGlobalSetID());
|
||||
bulkPs.setString(2, globalInstance.getMD5Hash());
|
||||
bulkPs.setByte(3, globalInstance.getKnownStatus().getFileKnownValue());
|
||||
@ -1808,7 +1959,7 @@ public abstract class AbstractSqlEamDb implements EamDb {
|
||||
|
||||
bulkPs.executeBatch();
|
||||
conn.commit();
|
||||
} catch (SQLException ex) {
|
||||
} catch (SQLException | EamDbException ex) {
|
||||
try {
|
||||
conn.rollback();
|
||||
} catch (SQLException ex2) {
|
||||
@ -1833,6 +1984,10 @@ public abstract class AbstractSqlEamDb implements EamDb {
|
||||
*/
|
||||
@Override
|
||||
public List<EamGlobalFileInstance> getReferenceInstancesByTypeValue(CorrelationAttribute.Type aType, String aValue) throws EamDbException {
|
||||
if(aType == null) {
|
||||
throw new EamDbException("correlation type is null");
|
||||
}
|
||||
|
||||
Connection conn = connect();
|
||||
|
||||
List<EamGlobalFileInstance> globalFileInstances = new ArrayList<>();
|
||||
@ -1869,6 +2024,10 @@ public abstract class AbstractSqlEamDb implements EamDb {
|
||||
*/
|
||||
@Override
|
||||
public int newCorrelationType(CorrelationAttribute.Type newType) throws EamDbException {
|
||||
if (newType == null) {
|
||||
throw new EamDbException("null correlation type");
|
||||
}
|
||||
|
||||
Connection conn = connect();
|
||||
|
||||
PreparedStatement preparedStatement = null;
|
||||
@ -1883,7 +2042,7 @@ public abstract class AbstractSqlEamDb implements EamDb {
|
||||
} else {
|
||||
insertSql = "INSERT INTO correlation_types(id, display_name, db_table_name, supported, enabled) VALUES (?, ?, ?, ?, ?)";
|
||||
}
|
||||
querySql = "SELECT id FROM correlation_types WHERE display_name=? AND db_table_name=?";
|
||||
querySql = "SELECT * FROM correlation_types WHERE display_name=? AND db_table_name=?";
|
||||
|
||||
try {
|
||||
preparedStatement = conn.prepareStatement(insertSql);
|
||||
@ -2073,9 +2232,12 @@ public abstract class AbstractSqlEamDb implements EamDb {
|
||||
preparedStatement = conn.prepareStatement(sql);
|
||||
preparedStatement.setInt(1, typeId);
|
||||
resultSet = preparedStatement.executeQuery();
|
||||
resultSet.next();
|
||||
aType = getCorrelationTypeFromResultSet(resultSet);
|
||||
return aType;
|
||||
if(resultSet.next()) {
|
||||
aType = getCorrelationTypeFromResultSet(resultSet);
|
||||
return aType;
|
||||
} else {
|
||||
throw new EamDbException("Failed to find entry for correlation type ID = " + typeId);
|
||||
}
|
||||
|
||||
} catch (SQLException ex) {
|
||||
throw new EamDbException("Error getting correlation type by id.", ex); // NON-NLS
|
||||
@ -2131,8 +2293,8 @@ public abstract class AbstractSqlEamDb implements EamDb {
|
||||
}
|
||||
|
||||
CorrelationDataSource eamDataSource = new CorrelationDataSource(
|
||||
resultSet.getInt("id"),
|
||||
resultSet.getInt("case_id"),
|
||||
resultSet.getInt("id"),
|
||||
resultSet.getString("device_id"),
|
||||
resultSet.getString("name")
|
||||
);
|
||||
@ -2166,7 +2328,7 @@ public abstract class AbstractSqlEamDb implements EamDb {
|
||||
*
|
||||
* @throws SQLException when an expected column name is not in the resultSet
|
||||
*/
|
||||
private CorrelationAttributeInstance getEamArtifactInstanceFromResultSet(ResultSet resultSet) throws SQLException {
|
||||
private CorrelationAttributeInstance getEamArtifactInstanceFromResultSet(ResultSet resultSet) throws SQLException, EamDbException {
|
||||
if (null == resultSet) {
|
||||
return null;
|
||||
}
|
||||
@ -2216,7 +2378,7 @@ public abstract class AbstractSqlEamDb implements EamDb {
|
||||
return eamGlobalSet;
|
||||
}
|
||||
|
||||
private EamGlobalFileInstance getEamGlobalFileInstanceFromResultSet(ResultSet resultSet) throws SQLException {
|
||||
private EamGlobalFileInstance getEamGlobalFileInstanceFromResultSet(ResultSet resultSet) throws SQLException, EamDbException {
|
||||
if (null == resultSet) {
|
||||
return null;
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ import org.openide.util.NbBundle.Messages;
|
||||
/**
|
||||
* Represents a type and value pair that can be used for correlation.
|
||||
* CorrelationAttributeInstances store information about the actual
|
||||
* occurences of the attribute.
|
||||
* occurrences of the attribute.
|
||||
*/
|
||||
public class CorrelationAttribute implements Serializable {
|
||||
|
||||
@ -66,7 +66,10 @@ public class CorrelationAttribute implements Serializable {
|
||||
return DEFAULT_CORRELATION_TYPES;
|
||||
}
|
||||
|
||||
public CorrelationAttribute(Type correlationType, String correlationValue) {
|
||||
public CorrelationAttribute(Type correlationType, String correlationValue) throws EamDbException {
|
||||
if(correlationValue == null) {
|
||||
throw new EamDbException ("Correlation value is null");
|
||||
}
|
||||
this.ID = "";
|
||||
this.correlationType = correlationType;
|
||||
// Lower-case all values to normalize and improve correlation hits, going forward make sure this makes sense for all correlation types
|
||||
@ -181,9 +184,12 @@ public class CorrelationAttribute implements Serializable {
|
||||
* Must start with a lowercase letter and only contain
|
||||
* lowercase letters, numbers, and '_' characters.
|
||||
* @param supported Is this Type currently supported
|
||||
* @param enabled Is this Type currentl enabled.
|
||||
* @param enabled Is this Type currently enabled.
|
||||
*/
|
||||
public Type(int id, String displayName, String dbTableName, Boolean supported, Boolean enabled) throws EamDbException {
|
||||
if(dbTableName == null) {
|
||||
throw new EamDbException("dbTableName is null");
|
||||
}
|
||||
this.id = id;
|
||||
this.displayName = displayName;
|
||||
this.dbTableName = dbTableName;
|
||||
@ -195,7 +201,7 @@ public class CorrelationAttribute implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructior for custom types where we do not know the Type ID until
|
||||
* Constructor for custom types where we do not know the Type ID until
|
||||
* the row has been entered into the correlation_types table
|
||||
* in the central repository.
|
||||
*
|
||||
@ -204,7 +210,7 @@ public class CorrelationAttribute implements Serializable {
|
||||
* Must start with a lowercase letter and only contain
|
||||
* lowercase letters, numbers, and '_' characters.
|
||||
* @param supported Is this Type currently supported
|
||||
* @param enabled Is this Type currentl enabled.
|
||||
* @param enabled Is this Type currently enabled.
|
||||
*/
|
||||
public Type(String displayName, String dbTableName, Boolean supported, Boolean enabled) throws EamDbException {
|
||||
this(-1, displayName, dbTableName, supported, enabled);
|
||||
|
@ -46,7 +46,7 @@ public class CorrelationAttributeInstance implements Serializable {
|
||||
public CorrelationAttributeInstance(
|
||||
CorrelationCase eamCase,
|
||||
CorrelationDataSource eamDataSource
|
||||
) {
|
||||
) throws EamDbException {
|
||||
this(-1, eamCase, eamDataSource, "", null, TskData.FileKnown.UNKNOWN);
|
||||
}
|
||||
|
||||
@ -54,7 +54,7 @@ public class CorrelationAttributeInstance implements Serializable {
|
||||
CorrelationCase eamCase,
|
||||
CorrelationDataSource eamDataSource,
|
||||
String filePath
|
||||
) {
|
||||
) throws EamDbException {
|
||||
this(-1, eamCase, eamDataSource, filePath, null, TskData.FileKnown.UNKNOWN);
|
||||
}
|
||||
|
||||
@ -63,7 +63,7 @@ public class CorrelationAttributeInstance implements Serializable {
|
||||
CorrelationDataSource eamDataSource,
|
||||
String filePath,
|
||||
String comment
|
||||
) {
|
||||
) throws EamDbException {
|
||||
this(-1, eamCase, eamDataSource, filePath, comment, TskData.FileKnown.UNKNOWN);
|
||||
}
|
||||
|
||||
@ -73,7 +73,7 @@ public class CorrelationAttributeInstance implements Serializable {
|
||||
String filePath,
|
||||
String comment,
|
||||
TskData.FileKnown knownStatus
|
||||
) {
|
||||
) throws EamDbException {
|
||||
this(-1, eamCase, eamDataSource, filePath, comment, knownStatus);
|
||||
}
|
||||
|
||||
@ -84,7 +84,11 @@ public class CorrelationAttributeInstance implements Serializable {
|
||||
String filePath,
|
||||
String comment,
|
||||
TskData.FileKnown knownStatus
|
||||
) {
|
||||
) throws EamDbException {
|
||||
if(filePath == null) {
|
||||
throw new EamDbException("file path is null");
|
||||
}
|
||||
|
||||
this.ID = ID;
|
||||
this.correlationCase = eamCase;
|
||||
this.correlationDataSource = eamDataSource;
|
||||
|
@ -50,6 +50,10 @@ public class CorrelationCase implements Serializable {
|
||||
* @param caseUUID Globally unique identifier
|
||||
* @param displayName
|
||||
*/
|
||||
public CorrelationCase(String caseUUID, String displayName) {
|
||||
this(-1, caseUUID, displayName);
|
||||
}
|
||||
|
||||
CorrelationCase(int ID, String caseUUID, String displayName) {
|
||||
this(ID, caseUUID, null, displayName, DATE_FORMAT.format(new Date()), null, null, null, null, null);
|
||||
}
|
||||
@ -156,7 +160,7 @@ public class CorrelationCase implements Serializable {
|
||||
/**
|
||||
* @return the database ID for the case or -1 if it is unknown (or not in the DB)
|
||||
*/
|
||||
int getID() {
|
||||
public int getID() {
|
||||
// @@@ Should probably have some lazy logic here to lead the ID from the DB if it is -1
|
||||
return databaseId;
|
||||
}
|
||||
|
@ -38,6 +38,16 @@ public class CorrelationDataSource implements Serializable {
|
||||
private final String deviceID; //< Unique to its associated case (not necessarily globally unique)
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param caseId
|
||||
* @param deviceId
|
||||
* @param name
|
||||
*/
|
||||
public CorrelationDataSource(int caseId, String deviceId, String name) {
|
||||
this(caseId, -1, deviceId, name);
|
||||
}
|
||||
|
||||
CorrelationDataSource(int caseId,
|
||||
int dataSourceId,
|
||||
String deviceId,
|
||||
|
@ -135,7 +135,8 @@ public class EamArtifactUtil {
|
||||
* @return the new EamArtifact, or null if one was not created because
|
||||
* bbArtifact did not contain the needed data
|
||||
*/
|
||||
private static CorrelationAttribute getCorrelationAttributeFromBlackboardArtifact(CorrelationAttribute.Type correlationType, BlackboardArtifact bbArtifact) {
|
||||
private static CorrelationAttribute getCorrelationAttributeFromBlackboardArtifact(CorrelationAttribute.Type correlationType,
|
||||
BlackboardArtifact bbArtifact) throws EamDbException {
|
||||
String value = null;
|
||||
int artifactTypeID = bbArtifact.getArtifactTypeID();
|
||||
|
||||
|
@ -36,7 +36,7 @@ public class EamGlobalFileInstance {
|
||||
int globalSetID,
|
||||
String MD5Hash,
|
||||
TskData.FileKnown knownStatus,
|
||||
String comment) {
|
||||
String comment) throws EamDbException {
|
||||
this(-1, globalSetID, MD5Hash, knownStatus, comment);
|
||||
}
|
||||
|
||||
@ -45,7 +45,13 @@ public class EamGlobalFileInstance {
|
||||
int globalSetID,
|
||||
String MD5Hash,
|
||||
TskData.FileKnown knownStatus,
|
||||
String comment) {
|
||||
String comment) throws EamDbException {
|
||||
if(MD5Hash == null){
|
||||
throw new EamDbException("null MD5 hash");
|
||||
}
|
||||
if(knownStatus == null){
|
||||
throw new EamDbException("null known status");
|
||||
}
|
||||
this.instanceID = instanceID;
|
||||
this.globalSetID = globalSetID;
|
||||
// Normalize hashes by lower casing
|
||||
@ -111,7 +117,10 @@ public class EamGlobalFileInstance {
|
||||
/**
|
||||
* @param MD5Hash the MD5Hash to set
|
||||
*/
|
||||
public void setMD5Hash(String MD5Hash) {
|
||||
public void setMD5Hash(String MD5Hash) throws EamDbException {
|
||||
if(MD5Hash == null){
|
||||
throw new EamDbException("null MD5 hash");
|
||||
}
|
||||
// Normalize hashes by lower casing
|
||||
this.MD5Hash = MD5Hash.toLowerCase();
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import java.io.File;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
@ -165,6 +166,7 @@ public class SqliteEamDb extends AbstractSqlEamDb {
|
||||
connectionPool.setMaxIdle(-1);
|
||||
connectionPool.setMaxWaitMillis(1000);
|
||||
connectionPool.setValidationQuery(dbSettings.getValidationQuery());
|
||||
connectionPool.setConnectionInitSqls(Arrays.asList("PRAGMA foreign_keys = ON"));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -17,13 +17,13 @@ EamSqliteSettingsDialog.bnDatabasePathFileOpen.text=Browse...
|
||||
EamSqliteSettingsDialog.tfDatabasePath.toolTipText=Filename and path to store SQLite db file
|
||||
EamSqliteSettingsDialog.tfDatabasePath.text=
|
||||
EamSqliteSettingsDialog.lbDatabasePath.text=Database Path :
|
||||
ImportHashDatabaseDialog.tfDatabaseName.tooltip=Name for this database
|
||||
ImportHashDatabaseDialog.tfDatabaseVersion.tooltip.text=Database Version Number
|
||||
ImportHashDatabaseDialog.tfDatabaseName.tooltip=Name for this database
|
||||
ImportHashDatabaseDialog.tfDatabaseVersion.tooltip.text=Database Version Number
|
||||
ImportHashDatabaseDialog.tfDatabaseName.tooltip=Name for this hash set
|
||||
ImportHashDatabaseDialog.tfDatabaseVersion.tooltip.text=Hash Set Version Number
|
||||
ImportHashDatabaseDialog.tfDatabaseName.tooltip=Name for this hash set
|
||||
ImportHashDatabaseDialog.tfDatabaseVersion.tooltip.text=Hash Set Version Number
|
||||
GlobalSettingsPanel.tbOops.text=
|
||||
GlobalSettingsPanel.lbDatabaseSettings.text=Database Settings
|
||||
GlobalSettingsPanel.bnImportDatabase.label=Import Hash Database
|
||||
GlobalSettingsPanel.bnImportDatabase.label=Import Hash Set
|
||||
AddNewOrganizationDialog.lbPocPhone.text=Phone:
|
||||
AddNewOrganizationDialog.lbPocEmail.text=Email:
|
||||
AddNewOrganizationDialog.lbPocName.text=Name:
|
||||
|
@ -453,7 +453,7 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
|
||||
return;
|
||||
}
|
||||
|
||||
Content content = (selectedNode).getLookup().lookup(Content.class);
|
||||
Content content = DataContentViewerUtility.getDefaultContent(selectedNode);
|
||||
if (content == null) {
|
||||
resetComponent();
|
||||
return;
|
||||
|
@ -452,8 +452,7 @@ public class DataContentViewerString extends javax.swing.JPanel implements DataC
|
||||
return;
|
||||
}
|
||||
|
||||
Lookup lookup = selectedNode.getLookup();
|
||||
Content content = lookup.lookup(Content.class);
|
||||
Content content = DataContentViewerUtility.getDefaultContent(selectedNode);
|
||||
if (content != null) {
|
||||
this.setDataView(content, 0);
|
||||
return;
|
||||
|
54
Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerUtility.java
Executable file
54
Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerUtility.java
Executable file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* 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.corecomponents;
|
||||
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.openide.nodes.Node;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
|
||||
/**
|
||||
* Utility classes for content viewers.
|
||||
* In theory, this would live in the contentviewer package,
|
||||
* but the initial method was needed only be viewers in
|
||||
* corecomponents and therefore can stay out of public API.
|
||||
*/
|
||||
class DataContentViewerUtility {
|
||||
/**
|
||||
* Returns the first non-Blackboard Artifact from a Node.
|
||||
* Needed for (at least) Hex and Strings that want to view
|
||||
* all types of content (not just AbstractFile), but don't want
|
||||
* to display an artifact unless that's the only thing there.
|
||||
* Scenario is hash hit or interesting item hit.
|
||||
*
|
||||
* @param node Node passed into content viewer
|
||||
* @return highest priority content or null if there is no content
|
||||
*/
|
||||
static Content getDefaultContent(Node node) {
|
||||
Content bbContentSeen = null;
|
||||
for (Content content : (node).getLookup().lookupAll(Content.class)) {
|
||||
if (content instanceof BlackboardArtifact) {
|
||||
bbContentSeen = content;
|
||||
}
|
||||
else {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
return bbContentSeen;
|
||||
}
|
||||
}
|
@ -19,22 +19,16 @@
|
||||
package org.sleuthkit.autopsy.modules.filetypeid;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.tika.Tika;
|
||||
import org.apache.tika.io.TikaInputStream;
|
||||
import org.apache.tika.mime.MimeTypes;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.services.Blackboard;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.ReadContentInputStream;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
@ -278,52 +272,30 @@ public class FileTypeDetector {
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not the a file matches a user-defined custom file
|
||||
* type. If the file matches and corresponds to an interesting files type
|
||||
* rule, this method has the side effect of creating an interesting files
|
||||
* hit artifact and indexing that artifact for keyword search.
|
||||
* Determines whether or not a file matches a user-defined custom file type.
|
||||
*
|
||||
* @param file The file to test.
|
||||
*
|
||||
* @return The file type name string or null, if no match is detected.
|
||||
*
|
||||
* @throws TskCoreException
|
||||
* @return The MIME type as a string if a match is found; otherwise null.
|
||||
*/
|
||||
private String detectUserDefinedType(AbstractFile file) {
|
||||
String retValue = null;
|
||||
|
||||
for (FileType fileType : userDefinedFileTypes) {
|
||||
if (fileType.matches(file)) {
|
||||
if (fileType.createInterestingFileHit()) {
|
||||
try {
|
||||
BlackboardArtifact artifact;
|
||||
artifact = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT);
|
||||
Collection<BlackboardAttribute> attributes = new ArrayList<>();
|
||||
BlackboardAttribute setNameAttribute = new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME, FileTypeIdModuleFactory.getModuleName(), fileType.getInterestingFilesSetName());
|
||||
attributes.add(setNameAttribute);
|
||||
BlackboardAttribute ruleNameAttribute = new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CATEGORY, FileTypeIdModuleFactory.getModuleName(), fileType.getMimeType());
|
||||
attributes.add(ruleNameAttribute);
|
||||
artifact.addAttributes(attributes);
|
||||
try {
|
||||
Case.getCurrentCase().getServices().getBlackboard().indexArtifact(artifact);
|
||||
} catch (Blackboard.BlackboardException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Unable to index TSK_INTERESTING_FILE_HIT blackboard artifact %d (file obj_id=%d)", artifact.getArtifactID(), file.getId()), ex); //NON-NLS
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Unable to create TSK_INTERESTING_FILE_HIT artifact for file (obj_id=%d)", file.getId()), ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
return fileType.getMimeType();
|
||||
retValue = fileType.getMimeType();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return retValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not the a file matches a custom file type defined
|
||||
* by Autopsy.
|
||||
* Determines whether or not a file matches a custom file type defined by Autopsy.
|
||||
*
|
||||
* @param file The file to test.
|
||||
*
|
||||
* @return The file type name string or null, if no match is detected.
|
||||
* @return The MIME type as a string if a match is found; otherwise null.
|
||||
*/
|
||||
private String detectAutopsyDefinedType(AbstractFile file) {
|
||||
for (FileType fileType : autopsyDefinedFileTypes) {
|
||||
@ -395,7 +367,7 @@ public class FileTypeDetector {
|
||||
*
|
||||
* @throws TskCoreException if detection is required and there is a problem
|
||||
* writing the result to the case database.
|
||||
* @deprecated Use detectMIMEType instead, and call AbstractFile.setMIMEType
|
||||
* @deprecated Use getMIMEType instead, and call AbstractFile.setMIMEType
|
||||
* and AbstractFile.save to save the result to the file object and the
|
||||
* database.
|
||||
*/
|
||||
@ -419,7 +391,7 @@ public class FileTypeDetector {
|
||||
* @throws TskCoreException if detection is required and there is a problem
|
||||
* writing the result to the case database.
|
||||
*
|
||||
* @deprecated Use detectMIMEType instead, and call AbstractFile.setMIMEType
|
||||
* @deprecated Use getMIMEType instead, and call AbstractFile.setMIMEType
|
||||
* and AbstractFile.save to save the result to the file object and the
|
||||
* database.
|
||||
*/
|
||||
@ -441,7 +413,7 @@ public class FileTypeDetector {
|
||||
* were uncertain, octet-stream is returned.
|
||||
*
|
||||
* @throws TskCoreException
|
||||
* @deprecated Use detectMIMEType instead.
|
||||
* @deprecated Use getMIMEType instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public String detect(AbstractFile file) throws TskCoreException {
|
||||
|
@ -1,15 +1,15 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-2015 Basis Technology Corp.
|
||||
*
|
||||
* Copyright 2013-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.
|
||||
@ -18,9 +18,14 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.modules.filetypeid;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.services.Blackboard;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.ingest.FileIngestModule;
|
||||
import org.sleuthkit.autopsy.ingest.IngestJobContext;
|
||||
@ -29,13 +34,16 @@ import org.sleuthkit.autopsy.ingest.IngestServices;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.autopsy.ingest.IngestModule.ProcessResult;
|
||||
import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* Detects the type of a file based on signature (magic) values. Posts results
|
||||
* to the blackboard.
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"CannotRunFileTypeDetection=Unable to run file type detection."
|
||||
"CannotRunFileTypeDetection=Unable to run file type detection."
|
||||
})
|
||||
public class FileTypeIdIngestModule implements FileIngestModule {
|
||||
|
||||
@ -91,7 +99,12 @@ public class FileTypeIdIngestModule implements FileIngestModule {
|
||||
*/
|
||||
try {
|
||||
long startTime = System.currentTimeMillis();
|
||||
fileTypeDetector.getMIMEType(file);
|
||||
String mimeType = fileTypeDetector.getMIMEType(file);
|
||||
file.setMIMEType(mimeType);
|
||||
FileType fileType = detectUserDefinedFileType(file);
|
||||
if (fileType != null && fileType.createInterestingFileHit()) {
|
||||
createInterestingFileHit(file, fileType);
|
||||
}
|
||||
addToTotals(jobId, (System.currentTimeMillis() - startTime));
|
||||
return ProcessResult.OK;
|
||||
} catch (Exception e) {
|
||||
@ -100,6 +113,57 @@ public class FileTypeIdIngestModule implements FileIngestModule {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not a file matches a user-defined custom file type.
|
||||
*
|
||||
* @param file The file to test.
|
||||
*
|
||||
* @return The file type if a match is found; otherwise null.
|
||||
*
|
||||
* @throws CustomFileTypesException If there is an issue getting an instance
|
||||
* of CustomFileTypesManager.
|
||||
*/
|
||||
private FileType detectUserDefinedFileType(AbstractFile file) throws CustomFileTypesManager.CustomFileTypesException {
|
||||
FileType retValue = null;
|
||||
|
||||
CustomFileTypesManager customFileTypesManager = CustomFileTypesManager.getInstance();
|
||||
List<FileType> fileTypesList = customFileTypesManager.getUserDefinedFileTypes();
|
||||
for (FileType fileType : fileTypesList) {
|
||||
if (fileType.matches(file)) {
|
||||
retValue = fileType;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return retValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an Interesting File hit using the specified file type rule.
|
||||
*
|
||||
* @param file The file from which to generate an artifact.
|
||||
* @param fileType The file type rule for categorizing the hit.
|
||||
*/
|
||||
private void createInterestingFileHit(AbstractFile file, FileType fileType) {
|
||||
try {
|
||||
BlackboardArtifact artifact;
|
||||
artifact = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT);
|
||||
Collection<BlackboardAttribute> attributes = new ArrayList<>();
|
||||
BlackboardAttribute setNameAttribute = new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME, FileTypeIdModuleFactory.getModuleName(), fileType.getInterestingFilesSetName());
|
||||
attributes.add(setNameAttribute);
|
||||
BlackboardAttribute ruleNameAttribute = new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CATEGORY, FileTypeIdModuleFactory.getModuleName(), fileType.getMimeType());
|
||||
attributes.add(ruleNameAttribute);
|
||||
artifact.addAttributes(attributes);
|
||||
try {
|
||||
Case.getCurrentCase().getServices().getBlackboard().indexArtifact(artifact);
|
||||
} catch (Blackboard.BlackboardException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Unable to index TSK_INTERESTING_FILE_HIT blackboard artifact %d (file obj_id=%d)", artifact.getArtifactID(), file.getId()), ex); //NON-NLS
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Unable to create TSK_INTERESTING_FILE_HIT artifact for file (obj_id=%d)", file.getId()), ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutDown() {
|
||||
/**
|
||||
|
@ -149,7 +149,7 @@ final class AddContentToHashDbAction extends AbstractAction implements Presenter
|
||||
if (null != md5Hash) {
|
||||
// don't let them add the hash for an empty file to the DB
|
||||
if (HashUtility.isNoDataMd5(md5Hash)) { //NON-NLS
|
||||
Logger.getLogger(AddContentToHashDbAction.class.getName()).log(Level.INFO, "Not adding " + file.getName() + " to database (empty content)"); //NON-NLS
|
||||
Logger.getLogger(AddContentToHashDbAction.class.getName()).log(Level.INFO, "Not adding " + file.getName() + " to hash set (empty content)"); //NON-NLS
|
||||
JOptionPane.showMessageDialog(null,
|
||||
NbBundle.getMessage(this.getClass(),
|
||||
"AddContentToHashDbAction.addFilesToHashSet.unableToAddFileEmptyMsg",
|
||||
@ -162,7 +162,7 @@ final class AddContentToHashDbAction extends AbstractAction implements Presenter
|
||||
try {
|
||||
hashSet.addHashes(file);
|
||||
} catch (TskCoreException ex) {
|
||||
Logger.getLogger(AddContentToHashDbAction.class.getName()).log(Level.SEVERE, "Error adding to hash database", ex); //NON-NLS
|
||||
Logger.getLogger(AddContentToHashDbAction.class.getName()).log(Level.SEVERE, "Error adding to hash set", ex); //NON-NLS
|
||||
JOptionPane.showMessageDialog(null,
|
||||
NbBundle.getMessage(this.getClass(),
|
||||
"AddContentToHashDbAction.addFilesToHashSet.unableToAddFileMsg",
|
||||
|
@ -1,11 +1,11 @@
|
||||
OpenIDE-Module-Display-Category=Ingest Module
|
||||
OpenIDE-Module-Long-Description=\
|
||||
Hash Database ingest module. \n\n\
|
||||
The ingest module analyzes files in the disk image and marks them as "known" (based on NSRL database lookup for "known" files) and "bad / interesting" (based on one or more databases supplied by the user).\n\n\
|
||||
The module also contains additional non-ingest tools that are integrated in the GUI, such as file lookup by hash and hash database configuration.
|
||||
Hash Set ingest module. \n\n\
|
||||
The ingest module analyzes files in the disk image and marks them as "known" (based on NSRL hashset lookup for "known" files) and "bad / interesting" (based on one or more hash sets supplied by the user).\n\n\
|
||||
The module also contains additional non-ingest tools that are integrated in the GUI, such as file lookup by hash and hash set configuration.
|
||||
OpenIDE-Module-Name=HashDatabases
|
||||
OptionsCategory_Name_HashDatabase=Hash Databases
|
||||
OptionsCategory_Keywords_HashDatabase=Hash Databases
|
||||
OptionsCategory_Name_HashDatabase=Hash Sets
|
||||
OptionsCategory_Keywords_HashDatabase=Hash Sets
|
||||
HashDbSearchPanel.hashTable.columnModel.title0=MD5 Hashes
|
||||
HashDbSearchPanel.addButton.text=Add Hash
|
||||
HashDbSearchPanel.hashField.text=
|
||||
@ -16,11 +16,11 @@ HashDbSearchPanel.titleLabel.text=Search for files with the following MD5 hash(e
|
||||
HashDbSearchPanel.errorField.text=Error: Not all files have been hashed.
|
||||
HashDbSearchPanel.saveBox.text=Remember Hashes
|
||||
HashDbSearchPanel.cancelButton.text=Cancel
|
||||
OpenIDE-Module-Short-Description=Hash Database Ingest Module and hash db tools
|
||||
OpenIDE-Module-Short-Description=Hash Set Ingest Module and hash set tools
|
||||
HashDbImportDatabaseDialog.jLabel1.text=Name:
|
||||
HashDbImportDatabaseDialog.databasePathTextField.text=
|
||||
HashDbImportDatabaseDialog.knownBadRadioButton.text=Notable
|
||||
HashDbImportDatabaseDialog.jLabel2.text=Type of database\:
|
||||
HashDbImportDatabaseDialog.jLabel2.text=Type of hash set\:
|
||||
HashDbImportDatabaseDialog.okButton.text=OK
|
||||
HashDbImportDatabaseDialog.cancelButton.text=Cancel
|
||||
HashDbCreateDatabaseDialog.jLabel2.text=Type:
|
||||
@ -28,13 +28,13 @@ HashDbCreateDatabaseDialog.knownBadRadioButton.text=Notable
|
||||
HashDbCreateDatabaseDialog.cancelButton.text=Cancel
|
||||
ModalNoButtons.CURRENTDB_LABEL.text=(CurrentDb)
|
||||
ModalNoButtons.CURRENTLYON_LABEL.text=Currently Indexing x of y
|
||||
ModalNoButtons.GO_GET_COFFEE_LABEL.text=Hash databases are currently being indexed, this may take some time.
|
||||
ModalNoButtons.GO_GET_COFFEE_LABEL.text=Hash sets are currently being indexed, this may take some time.
|
||||
ModalNoButtons.CANCEL_BUTTON.text=Cancel
|
||||
HashDbImportDatabaseDialog.knownRadioButton.text=Known (NSRL or other)
|
||||
HashDbCreateDatabaseDialog.knownRadioButton.text=Known
|
||||
HashDbCreateDatabaseDialog.saveAsButton.text=Save As...
|
||||
HashDbCreateDatabaseDialog.hashSetNameTextField.text=
|
||||
HashDbImportDatabaseDialog.jLabel3.text=Database Path:
|
||||
HashDbImportDatabaseDialog.jLabel3.text=Hash Set Path:
|
||||
HashDbCreateDatabaseDialog.sendIngestMessagesCheckbox.text=Send ingest inbox messages for each hit
|
||||
HashDbImportDatabaseDialog.sendIngestMessagesCheckbox.text=Send ingest inbox message for each hit
|
||||
HashDbImportDatabaseDialog.hashSetNameTextField.text=
|
||||
@ -42,17 +42,17 @@ HashDbImportDatabaseDialog.openButton.text=Open...
|
||||
HashDbCreateDatabaseDialog.jLabel3.text=Name:
|
||||
HashDbCreateDatabaseDialog.okButton.text=OK
|
||||
HashDbCreateDatabaseDialog.databasePathTextField.text=
|
||||
AddContentToHashDbAction.ContentMenu.noHashDbsConfigd=No hash databases configured
|
||||
AddContentToHashDbAction.ContentMenu.createDbItem=Create database...
|
||||
AddContentToHashDbAction.addFilesToHashSet.addToHashDbErr1.text=Add to Hash Database Error
|
||||
AddContentToHashDbAction.addFilesToHashSet.addToHashDbErr2.text=Add to Hash Database Error
|
||||
AddContentToHashDbAction.addFilesToHashSet.addToHashDbErr3.text=Add to Hash Database Error
|
||||
AddContentToHashDbAction.addFilesToHashSet.unableToAddFileMsg=Unable to add {0} to the hash database.
|
||||
AddContentToHashDbAction.addFilesToHashSet.unableToAddFileEmptyMsg=Unable to add {0} to the hash database. File has no content.
|
||||
AddContentToHashDbAction.addFilesToHashSet.unableToAddFileSzMsg=Unable to add the {0} to the hash database. Hashes have not been calculated. Please configure and run an appropriate ingest module.
|
||||
AddContentToHashDbAction.ContentMenu.noHashDbsConfigd=No hash sets configured
|
||||
AddContentToHashDbAction.ContentMenu.createDbItem=Create hash set...
|
||||
AddContentToHashDbAction.addFilesToHashSet.addToHashDbErr1.text=Add to Hash Set Error
|
||||
AddContentToHashDbAction.addFilesToHashSet.addToHashDbErr2.text=Add to Hash Set Error
|
||||
AddContentToHashDbAction.addFilesToHashSet.addToHashDbErr3.text=Add to Hash Set Error
|
||||
AddContentToHashDbAction.addFilesToHashSet.unableToAddFileMsg=Unable to add {0} to the hash set.
|
||||
AddContentToHashDbAction.addFilesToHashSet.unableToAddFileEmptyMsg=Unable to add {0} to the hash set. File has no content.
|
||||
AddContentToHashDbAction.addFilesToHashSet.unableToAddFileSzMsg=Unable to add the {0} to the hash set. Hashes have not been calculated. Please configure and run an appropriate ingest module.
|
||||
HashDatabaseOptionsPanelController.moduleErr=Module Error
|
||||
HashDatabaseOptionsPanelController.moduleErrMsg=A module caused an error listening to HashDatabaseOptionsPanelController updates. See log to determine which module. Some data could be incomplete.
|
||||
HashDbConfigPanel.noSelectionText=No database selected
|
||||
HashDbConfigPanel.noSelectionText=
|
||||
HashDbConfigPanel.errorGettingPathText=Error occurred getting path
|
||||
HashDbConfigPanel.errorGettingIndexStatusText=Error occurred getting status
|
||||
HashDbConfigPanel.indexButtonText.index=Index
|
||||
@ -62,35 +62,33 @@ HashDbConfigPanel.indexStatusText.indexOnly=Index only
|
||||
HashDbConfigPanel.indexStatusText.indexed=Indexed
|
||||
HashDbConfigPanel.indexButtonText.reIndex=Re-Index
|
||||
HashDbConfigPanel.indexStatusText.noIndex=No index
|
||||
HashDbConfigPanel.dbsNotIndexedMsg=The following databases are not indexed, would you like to index them now? \n {0}
|
||||
HashDbConfigPanel.dbNotIndexedMsg=The following database is not indexed, would you like to index it now? \n{0}
|
||||
HashDbConfigPanel.unindexedDbsMsg=Unindexed databases
|
||||
HashDbConfigPanel.allUnindexedDbsRmFromListMsg=All unindexed databases will be removed from the list
|
||||
HashDbConfigPanel.dbsNotIndexedMsg=The following hash sets are not indexed, would you like to index them now? \n {0}
|
||||
HashDbConfigPanel.dbNotIndexedMsg=The following hash set is not indexed, would you like to index it now? \n{0}
|
||||
HashDbConfigPanel.unindexedDbsMsg=Unindexed hash sets
|
||||
HashDbConfigPanel.allUnindexedDbsRmFromListMsg=All unindexed hash sets will be removed from the list
|
||||
HashDbConfigPanel.nameColLbl=Name
|
||||
HashDbConfigPanel.editingCellsNotSupportedMsg=Editing of cells is not supported
|
||||
HashDbConfigPanel.deleteDbActionConfirmMsg=This will remove the hash database for all cases. Do you want to proceed?
|
||||
HashDbConfigPanel.deleteDbActionMsg=Delete Hash Database from Configuration
|
||||
HashDbCreateDatabaseDialog.defaultFileName=hashset
|
||||
HashDbCreateDatabaseDialog.createHashDbMsg=Create Hash Database
|
||||
HashDbCreateDatabaseDialog.hashDbMustHaveFileExtensionMsg=The hash database file must have a .{0} extension.
|
||||
HashDbCreateDatabaseDialog.createHashDbMsg=Create Hash Set
|
||||
HashDbCreateDatabaseDialog.hashDbMustHaveFileExtensionMsg=The hash set file must have a .{0} extension.
|
||||
HashDbCreateDatabaseDialog.fileNameErr=File Name Error
|
||||
HashDbCreateDatabaseDialog.fileNameAlreadyExistsMsg=A file with this name already exists. Please choose a new file name.
|
||||
HashDbCreateDatabaseDialog.fileExistsErr=File Already Exists Error
|
||||
HashDbCreateDatabaseDialog.mustEnterHashSetNameMsg=A hash set name must be entered.
|
||||
HashDbCreateDatabaseDialog.createHashDbErr=Create Hash Database Error
|
||||
HashDbCreateDatabaseDialog.mustEnterHashDbPathMsg=A database path must be entered.
|
||||
HashDbCreateDatabaseDialog.errMsg.hashDbCreationErr=Hash database creation error
|
||||
HashDbCreateDatabaseDialog.cannotCreateFileAtLocMsg=Cannot create a hash database file at the selected location.
|
||||
HashDbCreateDatabaseDialog.failedToCreateHashDbMsg=Failed to create the hash database.
|
||||
HashDbImportDatabaseDialog.importHashDbMsg=Import Hash Database
|
||||
HashDbImportDatabaseDialog.fileNameExtFilter.text=Hash Database File
|
||||
HashDbImportDatabaseDialog.failedToGetDbPathMsg=Failed to get the path of the selected database.
|
||||
HashDbImportDatabaseDialog.importHashDbErr=Import Hash Database Error
|
||||
HashDbImportDatabaseDialog.mustSelectHashDbFilePathMsg=A hash database file path must be selected.
|
||||
HashDbImportDatabaseDialog.hashDbDoesNotExistMsg=The selected hash database does not exist.
|
||||
HashDbImportDatabaseDialog.errorMessage.failedToOpenHashDbMsg=Failed to open hash database at {0}.
|
||||
HashDbCreateDatabaseDialog.createHashDbErr=Create Hash Set Error
|
||||
HashDbCreateDatabaseDialog.mustEnterHashDbPathMsg=A hash set path must be entered.
|
||||
HashDbCreateDatabaseDialog.errMsg.hashDbCreationErr=Hash set creation error
|
||||
HashDbCreateDatabaseDialog.cannotCreateFileAtLocMsg=Cannot create a hash set file at the selected location.
|
||||
HashDbCreateDatabaseDialog.failedToCreateHashDbMsg=Failed to create the hash set.
|
||||
HashDbImportDatabaseDialog.importHashDbMsg=Import Hash Set
|
||||
HashDbImportDatabaseDialog.fileNameExtFilter.text=Hash Set File
|
||||
HashDbImportDatabaseDialog.failedToGetDbPathMsg=Failed to get the path of the selected hash set.
|
||||
HashDbImportDatabaseDialog.importHashDbErr=Import Hash Set Error
|
||||
HashDbImportDatabaseDialog.mustSelectHashDbFilePathMsg=A hash set file path must be selected.
|
||||
HashDbImportDatabaseDialog.hashDbDoesNotExistMsg=The selected hash set does not exist.
|
||||
HashDbImportDatabaseDialog.errorMessage.failedToOpenHashDbMsg=Failed to open hash set at {0}.
|
||||
HashDbIngestModule.moduleName=Hash Lookup
|
||||
HashDbIngestModule.moduleDescription=Identifies known and notable files using supplied hash databases, such as a standard NSRL database.
|
||||
HashDbIngestModule.moduleDescription=Identifies known and notable files using supplied hash sets, such as a standard NSRL hash set.
|
||||
HashDbIngestModule.fileReadErrorMsg=Read Error\: {0}
|
||||
HashDbIngestModule.calcHashValueErr=Error encountered while calculating the hash value for {0}.
|
||||
HashDbIngestModule.hashLookupErrorMsg=Hash Lookup Error\: {0}
|
||||
@ -99,25 +97,25 @@ HashDbIngestModule.lookingUpKnownBadHashValueErr=Error encountered while looking
|
||||
HashDbIngestModule.lookingUpKnownHashValueErr=Error encountered while looking up known hash value for {0}.
|
||||
HashDbIngestModule.postToBB.fileName=File Name
|
||||
HashDbIngestModule.postToBB.md5Hash=MD5 Hash
|
||||
HashDbIngestModule.postToBB.hashsetName=Hashset Name
|
||||
HashDbIngestModule.postToBB.hashsetName=Hash Set Name
|
||||
HashDbIngestModule.postToBB.knownBadMsg=Notable\: {0}
|
||||
HashDbIngestModule.complete.knownBadsFound=Notables found\:
|
||||
HashDbIngestModule.complete.totalCalcTime=Total Calculation Time
|
||||
HashDbIngestModule.complete.totalLookupTime=Total Lookup Time
|
||||
HashDbIngestModule.complete.databasesUsed=Databases Used\:
|
||||
HashDbIngestModule.complete.databasesUsed=Hash Sets Used\:
|
||||
HashDbIngestModule.complete.hashLookupResults=Hash Lookup Results
|
||||
HashDbManager.moduleErrorListeningToUpdatesMsg=A module caused an error listening to HashDbManager updates. See log to determine which module. Some data could be incomplete.
|
||||
HashDbManager.replacingDuplicateHashsetNameMsg=Duplicate hash set name {0} found.\nReplacing with {1}.
|
||||
HashDbManager.openHashDbErr=Open Hash Database Error
|
||||
HashDbManager.unableToOpenHashDbMsg=Unable to open {0} hash database.
|
||||
HashDbManager.openHashDbErr=Open Hash Set Error
|
||||
HashDbManager.unableToOpenHashDbMsg=Unable to open {0} hash set.
|
||||
HashDbManager.savedBackupOfOldConfigMsg={0}\nA backup copy of the old configuration has been saved as\n{1}
|
||||
HashDbManager.baseMessage.updatedFormatHashDbConfig=The format of the hash database configuration file has been updated.
|
||||
HashDbManager.baseMessage.updatedFormatHashDbConfig=The format of the hash set configuration file has been updated.
|
||||
HashDbManager.msgBoxTitle.confFileFmtChanged=Configuration File Format Changed
|
||||
HashDbManager.dlgMsg.dbNotFoundAtLoc=Database {0} could not be found at location\n{1}\nWould you like to search for the file?
|
||||
HashDbManager.dlgTitle.MissingDb=Missing Database
|
||||
HashDbManager.dlgMsg.dbNotFoundAtLoc=Hash set {0} could not be found at location\n{1}\nWould you like to search for the file?
|
||||
HashDbManager.dlgTitle.MissingDb=Missing Hash Set
|
||||
HashDbManager.progress.indexingHashSet=Indexing {0}
|
||||
HashDbManager.dlgMsg.errorIndexingHashSet=Error indexing {0} hash database.
|
||||
HashDbManager.hashDbIndexingErr=Hash Database Indexing Error
|
||||
HashDbManager.dlgMsg.errorIndexingHashSet=Error indexing {0} hash set.
|
||||
HashDbManager.hashDbIndexingErr=Hash Set Indexing Error
|
||||
HashDbPanelSearchAction.actionName=File Search by MD5 Hash
|
||||
HashDbSearchAction.dlgMsg.noFilesHaveMD5Calculated=No files currently have an MD5 hash calculated, run HashDB ingest first.
|
||||
HashDbSearchManager.MD5HashSearch=MD5 Hash Search
|
||||
@ -130,56 +128,56 @@ HashDbSearchPanel.errorText.invalidMD5HashMsg=Error\: That is not a valid MD5 ha
|
||||
HashDbSearchThread.progress.cancellingSearch={0} (Cancelling...)
|
||||
HashDbSearchThread.name.searching=Searching
|
||||
HashDbSearchThread.noMoreFilesWithMD5Msg=No other files with the same MD5 hash were found.
|
||||
ModalNoButtons.indexingDbsTitle=Indexing databases
|
||||
ModalNoButtons.indexingDbTitle=Indexing database
|
||||
ModalNoButtons.exitHashDbIndexingMsg=You are about to exit out of indexing your hash databases. \n\
|
||||
ModalNoButtons.indexingDbsTitle=Indexing hash sets
|
||||
ModalNoButtons.indexingDbTitle=Indexing hash set
|
||||
ModalNoButtons.exitHashDbIndexingMsg=You are about to exit out of indexing your hash sets. \n\
|
||||
The generated index will be left unusable. If you choose to continue,\n\
|
||||
please delete the corresponding -md5.idx file in the hash folder.\n\
|
||||
Exit indexing?
|
||||
ModalNoButtons.dlgTitle.unfinishedIndexing=Unfinished Indexing
|
||||
ModalNoButtons.indexThis.currentlyIndexing1Db=Currently indexing 1 database
|
||||
ModalNoButtons.indexThis.currentlyIndexing1Db=Currently indexing 1 hash set
|
||||
ModalNoButtons.indexThese.currentlyIndexing1OfNDbs=Currently indexing 1 of {0}
|
||||
ModalNoButtons.propChg.currentlyIndexingXofN=Currently indexing {0} of {1}
|
||||
HashDbManager.duplicateHashSetNameExceptionMsg=The hash set name ''{0}'' has already been used for another hash database.
|
||||
HashDbManager.hashDbDoesNotExistExceptionMsg=No hash database found at\n{0}
|
||||
HashDbManager.duplicateHashSetNameExceptionMsg=The hash set name ''{0}'' has already been used for another hash set.
|
||||
HashDbManager.hashDbDoesNotExistExceptionMsg=No hash set found at\n{0}
|
||||
HashDbManager.hashDbFileExistsExceptionMsg=A file already exists at\n{0}
|
||||
HashDbManager.hashDbAlreadyAddedExceptionMsg=The hash database at\n{0}\nhas already been created or imported.
|
||||
HashDbManager.illegalHashDbFileNameExtensionMsg=The hash database file name must have a .{0} extension.
|
||||
HashDbManager.hashDbAlreadyAddedExceptionMsg=The hash set at\n{0}\nhas already been created or imported.
|
||||
HashDbManager.illegalHashDbFileNameExtensionMsg=The hash set file name must have a .{0} extension.
|
||||
HashDbManager.moduleErr=Module Error
|
||||
HashDbManager.knownBad.text=Notable
|
||||
HashDbManager.known.text=Known
|
||||
HashDbManager.fileNameExtensionFilter.title=Hash Database File
|
||||
HashDbManager.fileNameExtensionFilter.title=Hash Set File
|
||||
HashDbSearchAction.dlgMsg.title=File Search by MD5 Hash
|
||||
HashDbSearchAction.getName.text=Hash Search
|
||||
HashDbSearchPanel.dlgMsg.title=File Search by MD5 Hash
|
||||
AddContentToHashDbAction.singleSelectionName=Add file to hash database
|
||||
AddContentToHashDbAction.multipleSelectionName=Add files to hash database
|
||||
AddContentToHashDbAction.singleSelectionName=Add file to hash set
|
||||
AddContentToHashDbAction.multipleSelectionName=Add files to hash set
|
||||
HashDbManager.ingestRunningExceptionMsg=Ingest is ongoing; this service will be unavailable until it finishes.
|
||||
HashDbManager.saveErrorExceptionMsg=Error saving hash configuration
|
||||
HashLookupSettingsPanel.jButton3.text=Import Database
|
||||
HashLookupSettingsPanel.jButton3.text=Import Hash Set
|
||||
HashLookupSettingsPanel.jLabel6.text=Type:
|
||||
HashLookupSettingsPanel.jLabel4.text=Location:
|
||||
HashLookupSettingsPanel.jLabel2.text=Name:
|
||||
HashLookupModuleSettingsPanel.alwaysCalcHashesCheckbox.text=Calculate MD5 even if no hash database is selected
|
||||
HashLookupModuleSettingsPanel.knownHashDbsLabel.text=Select known hash databases to use:
|
||||
HashLookupModuleSettingsPanel.knownBadHashDbsLabel.text=Select notable hash databases to use:
|
||||
HashLookupModuleSettingsPanel.alwaysCalcHashesCheckbox.text=Calculate MD5 even if no hash set is selected
|
||||
HashLookupModuleSettingsPanel.knownHashDbsLabel.text=Select known hash sets to use:
|
||||
HashLookupModuleSettingsPanel.knownBadHashDbsLabel.text=Select notable hash sets to use:
|
||||
AddContentToHashDbAction.addFilesToHashSet.files=files
|
||||
AddContentToHashDbAction.addFilesToHashSet.file=file
|
||||
HashDbManager.errCreatingIndex.title=Error creating index
|
||||
HashDbManager.errCreatingIndex.msg=Error creating index\: {0}
|
||||
HashLookupModuleFactory.getIngestJobSettingsPanel.exception.msg=Expected settings argument to be instanceof HashLookupModuleSettings
|
||||
HashLookupModuleFactory.createFileIngestModule.exception.msg=Expected settings argument to be instanceof HashLookupModuleSettings
|
||||
HashLookupModuleSettingsPanel.alwaysCalcHashesCheckbox.toolTipText=Calculate MD5 even if no hash database is selected
|
||||
HashLookupModuleSettingsPanel.alwaysCalcHashesCheckbox.toolTipText=Calculate MD5 even if no hash set is selected
|
||||
HashDbSearchPanel.hashTable.defaultModel.title.text=MD5 Hashes
|
||||
AddHashValuesToDatabaseDialog.JDialog.Title=Add Hashes to Database
|
||||
AddHashValuesToDatabaseDialog.JDialog.Title=Add Hashes to Hash Set
|
||||
AddHashValuesToDatabaseDialog.instructionLabel.text_1=Paste MD5 hash values (one per line) below:
|
||||
AddHashValuesToDatabaseDialog.cancelButton.text_2=Cancel
|
||||
AddHashValuesToDatabaseDialog.AddValuesToHashDatabaseButton.text_2=Add Hashes to Database
|
||||
AddHashValuesToDatabaseDialog.AddValuesToHashDatabaseButton.text_2=Add Hashes to Hash Set
|
||||
AddHashValuesToDatabaseDialog.pasteFromClipboardButton.text_2=Paste From Clipboard
|
||||
AddHashValuesToDatabaseProgressDialog.okButton.text=OK
|
||||
AddHashValuesToDatabaseProgressDialog.statusLabel.text=status
|
||||
AddHashValuesToDatabaseProgressDialog.title=Add Hashes to Database Progress
|
||||
AddHashValuesToDatabaseDialog.title=Add Hashes to Database
|
||||
AddHashValuesToDatabaseProgressDialog.title=Add Hashes to Hash Set Progress
|
||||
AddHashValuesToDatabaseDialog.title=Add Hashes to Hash Set
|
||||
AddHashValuesToDatabaseProgressDialog.showErrorsButton.text=Show Errors
|
||||
AddHashValuesToDatabaseProgressDialog.addHashValuesToDatabase.parsing=Parsing text for MD5 hashes...
|
||||
AddHashValuesToDatabaseProgressDialog.addHashValuesToDatabase.invalidHash=The input contains invalid hash.
|
||||
@ -187,47 +185,47 @@ AddHashValuesToDatabaseProgressDialog.addHashValuesToDatabase.invaliHash.msg=Inv
|
||||
AddHashValuesToDatabaseProgressDialog.addHashValuesToDatabase.noHashesToAdd=There are no hashes to add.
|
||||
AddHashValuesToDatabaseProgressDialog.addHashValuesToDatabase.success={0} Hashes added successfully.
|
||||
AddHashValuesToDatabaseProgressDialog.addHashValuesToDatabase.errorAddingValidHash=There is an error adding valid hashes.
|
||||
AddHashValuesToDatabaseProgressDialog.addHashValuesToDatabase.errorAddingValidHash.msg=Error adding valid hashes to the database:
|
||||
AddHashValuesToDatabaseProgressDialog.addHashValuesToDatabase.errorAddingValidHash.msg=Error adding valid hashes to the hash set:
|
||||
HashLookupSettingsPanel.ingestWarningLabel.text=Ingest is ongoing, some settings will be unavailable until it finishes.
|
||||
HashLookupSettingsPanel.addHashesToDatabaseButton.text=Add Hashes to Database
|
||||
HashLookupSettingsPanel.indexPathLabel.text=No database selected
|
||||
HashLookupSettingsPanel.addHashesToDatabaseButton.text=Add Hashes to Hash Set
|
||||
HashLookupSettingsPanel.indexPathLabel.text=
|
||||
HashLookupSettingsPanel.indexPathLabelLabel.text=Index Path:
|
||||
HashLookupSettingsPanel.createDatabaseButton.toolTipText=
|
||||
HashLookupSettingsPanel.createDatabaseButton.text=New database
|
||||
HashLookupSettingsPanel.createDatabaseButton.text=New Hash Set
|
||||
HashLookupSettingsPanel.optionsLabel.text=Options
|
||||
HashLookupSettingsPanel.informationLabel.text=Information
|
||||
HashLookupSettingsPanel.sendIngestMessagesCheckBox.text=Send ingest inbox message for each hit
|
||||
HashLookupSettingsPanel.indexButton.text=Index
|
||||
HashLookupSettingsPanel.indexLabel.text=Index Status:
|
||||
HashLookupSettingsPanel.hashDbIndexStatusLabel.text=No database selected
|
||||
HashLookupSettingsPanel.hashDbTypeLabel.text=No database selected
|
||||
HashLookupSettingsPanel.hashDbIndexStatusLabel.text=
|
||||
HashLookupSettingsPanel.hashDbTypeLabel.text=
|
||||
HashLookupSettingsPanel.typeLabel.text=Type:
|
||||
HashLookupSettingsPanel.locationLabel.text=Database Path:
|
||||
HashLookupSettingsPanel.hashDbLocationLabel.text=No database selected
|
||||
HashLookupSettingsPanel.hashDbNameLabel.text=No database selected
|
||||
HashLookupSettingsPanel.locationLabel.text=Hash Set Path:
|
||||
HashLookupSettingsPanel.hashDbLocationLabel.text=
|
||||
HashLookupSettingsPanel.hashDbNameLabel.text=
|
||||
HashLookupSettingsPanel.nameLabel.text=Name:
|
||||
HashLookupSettingsPanel.hashDatabasesLabel.text=Hash Databases:
|
||||
HashLookupSettingsPanel.hashDatabasesLabel.text=Hash Sets:
|
||||
HashLookupSettingsPanel.importDatabaseButton.toolTipText=
|
||||
HashLookupSettingsPanel.importDatabaseButton.text=Import database
|
||||
HashLookupSettingsPanel.deleteDatabaseButton.text=Delete database
|
||||
ImportHashDatabaseDialog.lbFilePath.text=Database Path:
|
||||
ImportHashDatabaseDialog.tfDatabaseName.tooltip=Name for this database
|
||||
ImportHashDatabaseDialog.tfDatabaseVersion.tooltip.text=Database Version Number
|
||||
ImportHashDatabaseDialog.tfDatabaseName.tooltip=Name for this database
|
||||
ImportHashDatabaseDialog.tfDatabaseVersion.tooltip.text=Database Version Number
|
||||
HashLookupSettingsPanel.importDatabaseButton.text=Import Hash Set
|
||||
HashLookupSettingsPanel.deleteDatabaseButton.text=Delete Hash Set
|
||||
ImportHashDatabaseDialog.lbFilePath.text=Hash Set Path:
|
||||
ImportHashDatabaseDialog.tfDatabaseName.tooltip=Name for this hash set
|
||||
ImportHashDatabaseDialog.tfDatabaseVersion.tooltip.text=Hash Set Version Number
|
||||
ImportHashDatabaseDialog.tfDatabaseName.tooltip=Name for this hash set
|
||||
ImportHashDatabaseDialog.tfDatabaseVersion.tooltip.text=Hash Set Version Number
|
||||
ImportCentralRepoDbProgressDialog.lbProgress.text=Starting import...
|
||||
ImportCentralRepoDbProgressDialog.bnOk.text=OK
|
||||
ImportCentralRepoDbProgressDialog.bnCancel.text=Cancel
|
||||
HashLookupSettingsPanel.versionLabel.text_1=Version:
|
||||
HashLookupSettingsPanel.hashDbVersionLabel.text_1=No database selected
|
||||
HashLookupSettingsPanel.hashDbVersionLabel.text_1=
|
||||
HashLookupSettingsPanel.orgLabel.text_1=Organization:
|
||||
HashLookupSettingsPanel.hashDbOrgLabel.text_1=No database selected
|
||||
HashLookupSettingsPanel.hashDbOrgLabel.text_1=
|
||||
HashLookupSettingsPanel.readOnlyLabel.text_1=Read only:
|
||||
HashLookupSettingsPanel.hashDbReadOnlyLabel.text_1=No database selected
|
||||
HashLookupSettingsPanel.hashDbReadOnlyLabel.text_1=
|
||||
ImportCentralRepoDbProgressDialog.jLabel1.text=Importing hash set into the central repository
|
||||
HashDbImportDatabaseDialog.lbVersion.text=Version:
|
||||
HashDbImportDatabaseDialog.lbOrg.text=Source Organization:
|
||||
HashDbImportDatabaseDialog.readOnlyCheckbox.text=Make database read-only
|
||||
HashDbImportDatabaseDialog.readOnlyCheckbox.text=Make hash set read-only
|
||||
HashDbImportDatabaseDialog.orgButton.text=Manage Organizations
|
||||
HashDbImportDatabaseDialog.versionTextField.text=1.0
|
||||
HashDbImportDatabaseDialog.fileTypeRadioButton.text=Local
|
||||
@ -238,4 +236,4 @@ HashDbCreateDatabaseDialog.fileTypeRadioButton.text=Local
|
||||
HashDbCreateDatabaseDialog.centralRepoRadioButton.text=Remote (Central Repository)
|
||||
HashDbCreateDatabaseDialog.lbOrg.text=Source Organization:
|
||||
HashDbCreateDatabaseDialog.orgButton.text=Manage Organizations
|
||||
HashDbCreateDatabaseDialog.databasePathLabel.text=Database Path:
|
||||
HashDbCreateDatabaseDialog.databasePathLabel.text=Hash Set Path:
|
||||
|
@ -58,8 +58,6 @@ HashDbConfigPanel.unindexedDbsMsg=\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u5316\u30
|
||||
HashDbConfigPanel.allUnindexedDbsRmFromListMsg=\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u5316\u3055\u308c\u3066\u3044\u306a\u3044\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306f\u30ea\u30b9\u30c8\u304b\u3089\u524a\u9664\u3055\u308c\u307e\u3059
|
||||
HashDbConfigPanel.nameColLbl=\u540d\u524d
|
||||
HashDbConfigPanel.editingCellsNotSupportedMsg=\u30bb\u30eb\u306f\u7de8\u96c6\u4e0d\u53ef\u3067\u3059
|
||||
HashDbConfigPanel.deleteDbActionConfirmMsg=\u5168\u3066\u306e\u30b1\u30fc\u30b9\u306b\u304a\u3051\u308b\u30cf\u30c3\u30b7\u30e5\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u3092\u524a\u9664\u3057\u307e\u3059\u3002\u5b9f\u884c\u3057\u307e\u3059\u304b\uff1f
|
||||
HashDbConfigPanel.deleteDbActionMsg=\u8a2d\u5b9a\u304b\u3089\u30cf\u30c3\u30b7\u30e5\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u3092\u524a\u9664
|
||||
HashDbCreateDatabaseDialog.createHashDbMsg=\u30cf\u30c3\u30b7\u30e5\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u3092\u4f5c\u6210
|
||||
HashDbCreateDatabaseDialog.hashDbMustHaveFileExtensionMsg=\u30cf\u30c3\u30b7\u30e5\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u30d5\u30a1\u30a4\u30eb\u306f .{0} \u306e\u62e1\u5f35\u5b50\u304c\u5fc5\u8981\u3067\u3059\u3002
|
||||
HashDbCreateDatabaseDialog.fileNameErr=\u30d5\u30a1\u30a4\u30eb\u540d\u30a8\u30e9\u30fc
|
||||
|
@ -35,7 +35,7 @@ import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
keywordsCategory = "HashDatabase",
|
||||
id = "HashDatabase")
|
||||
// moved messages to Bundle.properties
|
||||
//@org.openide.util.NbBundle.Messages({"OptionsCategory_Name_HashDatabase=Hash Database", "OptionsCategory_Keywords_HashDatabase=Hash Database"})
|
||||
//@org.openide.util.NbBundle.Messages({"OptionsCategory_Name_HashDatabase=Hash Set", "OptionsCategory_Keywords_HashDatabase=Hash Set"})
|
||||
public final class HashDatabaseOptionsPanelController extends OptionsPanelController {
|
||||
|
||||
private HashLookupSettingsPanel panel;
|
||||
|
@ -420,7 +420,7 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog {
|
||||
}
|
||||
ModuleSettings.setConfigSetting(ModuleSettings.MAIN_SETTINGS, LAST_FILE_PATH_KEY, databaseFile.getParent());
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(HashDbImportDatabaseDialog.class.getName()).log(Level.SEVERE, "Failed to get path of selected database", ex); //NON-NLS
|
||||
Logger.getLogger(HashDbImportDatabaseDialog.class.getName()).log(Level.SEVERE, "Failed to get path of selected hash set", ex); //NON-NLS
|
||||
JOptionPane.showMessageDialog(this,
|
||||
NbBundle.getMessage(this.getClass(),
|
||||
"HashDbImportDatabaseDialog.failedToGetDbPathMsg"));
|
||||
|
@ -51,9 +51,9 @@ import org.sleuthkit.datamodel.TskData;
|
||||
import org.sleuthkit.datamodel.TskException;
|
||||
|
||||
@NbBundle.Messages({
|
||||
"HashDbIngestModule.noKnownBadHashDbSetMsg=No notable hash database set.",
|
||||
"HashDbIngestModule.noKnownBadHashDbSetMsg=No notable hash set.",
|
||||
"HashDbIngestModule.knownBadFileSearchWillNotExecuteWarn=Notable file search will not be executed.",
|
||||
"HashDbIngestModule.noKnownHashDbSetMsg=No known hash database set.",
|
||||
"HashDbIngestModule.noKnownHashDbSetMsg=No known hash set.",
|
||||
"HashDbIngestModule.knownFileSearchWillNotExecuteWarn=Known file search will not be executed."
|
||||
})
|
||||
public class HashDbIngestModule implements FileIngestModule {
|
||||
@ -95,7 +95,7 @@ public class HashDbIngestModule implements FileIngestModule {
|
||||
public void startUp(org.sleuthkit.autopsy.ingest.IngestJobContext context) throws IngestModuleException {
|
||||
jobId = context.getJobId();
|
||||
if (!hashDbManager.verifyAllDatabasesLoadedCorrectly()) {
|
||||
throw new IngestModuleException("Could not load all hash databases");
|
||||
throw new IngestModuleException("Could not load all hash sets");
|
||||
}
|
||||
updateEnabledHashSets(hashDbManager.getKnownBadFileHashSets(), knownBadHashSets);
|
||||
updateEnabledHashSets(hashDbManager.getKnownFileHashSets(), knownHashSets);
|
||||
@ -136,7 +136,7 @@ public class HashDbIngestModule implements FileIngestModule {
|
||||
enabledHashSets.add(db);
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.WARNING, "Error getting index status for " + db.getDisplayName()+ " hash database", ex); //NON-NLS
|
||||
logger.log(Level.WARNING, "Error getting index status for " + db.getDisplayName()+ " hash set", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -276,14 +276,14 @@ public class HashDbManager implements PropertyChangeListener {
|
||||
boolean readOnly) throws TskCoreException{
|
||||
|
||||
if(! EamDb.isEnabled()){
|
||||
throw new TskCoreException("Could not load central repository database " + hashSetName + " - central repository is not enabled");
|
||||
throw new TskCoreException("Could not load central repository hash set " + hashSetName + " - central repository is not enabled");
|
||||
}
|
||||
|
||||
CentralRepoHashSet db = new CentralRepoHashSet(hashSetName, version, referenceSetID, searchDuringIngest,
|
||||
sendIngestMessages, knownFilesType, readOnly);
|
||||
|
||||
if(! db.isValid()){
|
||||
throw new TskCoreException("Error finding database " + hashSetName + " in central repository");
|
||||
throw new TskCoreException("Error finding hash set " + hashSetName + " in central repository");
|
||||
}
|
||||
|
||||
// Add the hash database to the collection
|
||||
@ -320,7 +320,7 @@ public class HashDbManager implements PropertyChangeListener {
|
||||
hashSetPaths.add(indexPath);
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error getting index path of " + hashDb.getHashSetName() + " hash database after indexing", ex); //NON-NLS
|
||||
Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error getting index path of " + hashDb.getHashSetName() + " hash set after indexing", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -363,7 +363,7 @@ public class HashDbManager implements PropertyChangeListener {
|
||||
hashSetPaths.remove(hashDatabase.getIndexPath());
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error getting index path of " + hashDatabase.getHashSetName() + " hash database when removing the database", ex); //NON-NLS
|
||||
Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error getting index path of " + hashDatabase.getHashSetName() + " hash set when removing the hash set", ex); //NON-NLS
|
||||
}
|
||||
|
||||
try {
|
||||
@ -371,13 +371,13 @@ public class HashDbManager implements PropertyChangeListener {
|
||||
hashSetPaths.remove(hashDatabase.getDatabasePath());
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error getting database path of " + hashDatabase.getHashSetName() + " hash database when removing the database", ex); //NON-NLS
|
||||
Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error getting hash set path of " + hashDatabase.getHashSetName() + " hash set when removing the hash set", ex); //NON-NLS
|
||||
}
|
||||
|
||||
try {
|
||||
hashDatabase.close();
|
||||
} catch (TskCoreException ex) {
|
||||
Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error closing " + hashDb.getHashSetName() + " hash database when removing the database", ex); //NON-NLS
|
||||
Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error closing " + hashDb.getHashSetName() + " hash set when removing the hash set", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
@ -480,7 +480,7 @@ public class HashDbManager implements PropertyChangeListener {
|
||||
updateableDbs.add(db);
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error checking updateable status of " + db.getHashSetName() + " hash database", ex); //NON-NLS
|
||||
Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error checking updateable status of " + db.getHashSetName() + " hash set", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
return updateableDbs;
|
||||
@ -532,7 +532,7 @@ public class HashDbManager implements PropertyChangeListener {
|
||||
try {
|
||||
((SleuthkitHashSet)database).close();
|
||||
} catch (TskCoreException ex) {
|
||||
Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error closing " + database.getHashSetName() + " hash database", ex); //NON-NLS
|
||||
Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error closing " + database.getHashSetName() + " hash set", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -554,7 +554,7 @@ public class HashDbManager implements PropertyChangeListener {
|
||||
*
|
||||
* @param settings The settings to configure.
|
||||
*/
|
||||
@Messages({"# {0} - database name", "HashDbManager.noDbPath.message=Couldn't get valid database path for: {0}",
|
||||
@Messages({"# {0} - hash set name", "HashDbManager.noDbPath.message=Couldn't get valid hash set path for: {0}",
|
||||
"HashDbManager.centralRepoLoadError.message=Error loading central repository hash sets"})
|
||||
private void configureSettings(HashLookupSettings settings) {
|
||||
allDatabasesLoadedCorrectly = true;
|
||||
@ -578,7 +578,7 @@ public class HashDbManager implements PropertyChangeListener {
|
||||
}
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error opening hash database", ex); //NON-NLS
|
||||
Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error opening hash set", ex); //NON-NLS
|
||||
JOptionPane.showMessageDialog(null,
|
||||
NbBundle.getMessage(this.getClass(),
|
||||
"HashDbManager.unableToOpenHashDbMsg", hashDbInfo.getHashSetName()),
|
||||
@ -592,7 +592,7 @@ public class HashDbManager implements PropertyChangeListener {
|
||||
try{
|
||||
updateHashSetsFromCentralRepository();
|
||||
} catch (TskCoreException ex){
|
||||
Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error opening hash database", ex); //NON-NLS
|
||||
Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error opening hash set", ex); //NON-NLS
|
||||
|
||||
JOptionPane.showMessageDialog(null,
|
||||
Bundle.HashDbManager_centralRepoLoadError_message(),
|
||||
@ -615,7 +615,7 @@ public class HashDbManager implements PropertyChangeListener {
|
||||
allDatabasesLoadedCorrectly = true;
|
||||
} catch (HashLookupSettings.HashLookupSettingsException ex) {
|
||||
allDatabasesLoadedCorrectly = false;
|
||||
logger.log(Level.SEVERE, "Could not overwrite hash database settings.", ex);
|
||||
logger.log(Level.SEVERE, "Could not overwrite hash set settings.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1232,9 +1232,10 @@ public class HashDbManager implements PropertyChangeListener {
|
||||
} else {
|
||||
type = TskData.FileKnown.KNOWN;
|
||||
}
|
||||
EamGlobalFileInstance fileInstance = new EamGlobalFileInstance(referenceSetID, file.getMd5Hash(),
|
||||
type, comment);
|
||||
|
||||
try{
|
||||
EamGlobalFileInstance fileInstance = new EamGlobalFileInstance(referenceSetID, file.getMd5Hash(),
|
||||
type, comment);
|
||||
EamDb.getInstance().addReferenceInstance(fileInstance,EamDb.getInstance().getCorrelationTypeById(CorrelationAttribute.FILES_TYPE_ID));
|
||||
} catch (EamDbException ex){
|
||||
throw new TskCoreException("Error adding hashes to " + getDisplayName(), ex);
|
||||
@ -1259,8 +1260,12 @@ public class HashDbManager implements PropertyChangeListener {
|
||||
type = TskData.FileKnown.BAD;
|
||||
} else {
|
||||
type = TskData.FileKnown.KNOWN;
|
||||
}
|
||||
globalFileInstances.add(new EamGlobalFileInstance(referenceSetID, hashEntry.getMd5Hash(), type, hashEntry.getComment()));
|
||||
}
|
||||
try {
|
||||
globalFileInstances.add(new EamGlobalFileInstance(referenceSetID, hashEntry.getMd5Hash(), type, hashEntry.getComment()));
|
||||
} catch (EamDbException ex){
|
||||
throw new TskCoreException("Error adding hashes to " + getDisplayName(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
try{
|
||||
@ -1344,7 +1349,7 @@ public class HashDbManager implements PropertyChangeListener {
|
||||
try{
|
||||
return EamDb.getInstance().referenceSetIsValid(this.referenceSetID, this.hashSetName, this.version);
|
||||
} catch (EamDbException ex){
|
||||
Logger.getLogger(CentralRepoHashSet.class.getName()).log(Level.SEVERE, "Error validating hash database " + hashSetName, ex); //NON-NLS
|
||||
Logger.getLogger(CentralRepoHashSet.class.getName()).log(Level.SEVERE, "Error validating hash set " + hashSetName, ex); //NON-NLS
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ final class HashLookupModuleSettings implements IngestModuleIngestJobSettings {
|
||||
try{
|
||||
databaseInfoList = HashLookupSettings.convertHashSetList(hashDbList);
|
||||
} catch (HashLookupSettings.HashLookupSettingsException ex){
|
||||
Logger.getLogger(HashLookupModuleSettings.class.getName()).log(Level.SEVERE, "Error creating hash database settings.", ex); //NON-NLS
|
||||
Logger.getLogger(HashLookupModuleSettings.class.getName()).log(Level.SEVERE, "Error creating hash set settings.", ex); //NON-NLS
|
||||
databaseInfoList = new ArrayList<>();
|
||||
}
|
||||
}
|
||||
@ -87,7 +87,7 @@ final class HashLookupModuleSettings implements IngestModuleIngestJobSettings {
|
||||
dbInfo.setSearchDuringIngest(true);
|
||||
databaseInfoList.add(dbInfo);
|
||||
} catch (TskCoreException ex){
|
||||
Logger.getLogger(HashLookupModuleSettings.class.getName()).log(Level.SEVERE, "Error creating hash database settings for " + db.getHashSetName(), ex); //NON-NLS
|
||||
Logger.getLogger(HashLookupModuleSettings.class.getName()).log(Level.SEVERE, "Error creating hash set settings for " + db.getHashSetName(), ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
for(HashDb db:disabledHashSets){
|
||||
@ -96,7 +96,7 @@ final class HashLookupModuleSettings implements IngestModuleIngestJobSettings {
|
||||
dbInfo.setSearchDuringIngest(false);
|
||||
databaseInfoList.add(dbInfo);
|
||||
} catch (TskCoreException ex){
|
||||
Logger.getLogger(HashLookupModuleSettings.class.getName()).log(Level.SEVERE, "Error creating hash database settings for " + db.getHashSetName(), ex); //NON-NLS
|
||||
Logger.getLogger(HashLookupModuleSettings.class.getName()).log(Level.SEVERE, "Error creating hash set settings for " + db.getHashSetName(), ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,7 +152,7 @@ final class HashLookupModuleSettings implements IngestModuleIngestJobSettings {
|
||||
try{
|
||||
databaseInfoList = HashLookupSettings.convertHashSetList(HashDbManager.getInstance().getAllHashSets());
|
||||
} catch (HashLookupSettings.HashLookupSettingsException ex){
|
||||
Logger.getLogger(HashLookupModuleSettings.class.getName()).log(Level.SEVERE, "Error updating hash database settings.", ex); //NON-NLS
|
||||
Logger.getLogger(HashLookupModuleSettings.class.getName()).log(Level.SEVERE, "Error updating hash set settings.", ex); //NON-NLS
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -80,7 +80,7 @@ final class HashLookupSettings implements Serializable {
|
||||
try{
|
||||
dbInfoList.add(new HashDbInfo(db));
|
||||
} catch (TskCoreException ex){
|
||||
logger.log(Level.SEVERE, "Could not load database settings for {0}", db.getHashSetName());
|
||||
logger.log(Level.SEVERE, "Could not load hash set settings for {0}", db.getHashSetName());
|
||||
}
|
||||
}
|
||||
return dbInfoList;
|
||||
@ -128,7 +128,7 @@ final class HashLookupSettings implements Serializable {
|
||||
return filesSetsSettings;
|
||||
}
|
||||
} catch (IOException | ClassNotFoundException ex) {
|
||||
throw new HashLookupSettingsException("Could not read hash database settings.", ex);
|
||||
throw new HashLookupSettingsException("Could not read hash set settings.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@ -285,7 +285,7 @@ final class HashLookupSettings implements Serializable {
|
||||
out.writeObject(settings);
|
||||
return true;
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Could not write hash database settings.");
|
||||
logger.log(Level.SEVERE, "Could not write hash set settings.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011 - 2015 Basis Technology Corp.
|
||||
* Copyright 2011-2018 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -175,7 +175,7 @@ public final class HashLookupSettingsPanel extends IngestModuleGlobalSettingsPan
|
||||
try {
|
||||
addHashesToDatabaseButton.setEnabled(!ingestIsRunning && db.isUpdateable());
|
||||
} catch (TskCoreException ex) {
|
||||
Logger.getLogger(HashLookupSettingsPanel.class.getName()).log(Level.SEVERE, "Error identifying if the database is updateable.", ex); //NON-NLS
|
||||
Logger.getLogger(HashLookupSettingsPanel.class.getName()).log(Level.SEVERE, "Error identifying if the hash set is updateable.", ex); //NON-NLS
|
||||
addHashesToDatabaseButton.setEnabled(false);
|
||||
}
|
||||
|
||||
@ -192,14 +192,14 @@ public final class HashLookupSettingsPanel extends IngestModuleGlobalSettingsPan
|
||||
try {
|
||||
hashDbLocationLabel.setText(shortenPath(db.getDatabasePath()));
|
||||
} catch (TskCoreException ex) {
|
||||
Logger.getLogger(HashLookupSettingsPanel.class.getName()).log(Level.SEVERE, "Error getting database path of " + db.getHashSetName() + " hash database", ex); //NON-NLS
|
||||
Logger.getLogger(HashLookupSettingsPanel.class.getName()).log(Level.SEVERE, "Error getting hash set path of " + db.getHashSetName() + " hash set", ex); //NON-NLS
|
||||
hashDbLocationLabel.setText(ERROR_GETTING_PATH_TEXT);
|
||||
}
|
||||
|
||||
try {
|
||||
indexPathLabel.setText(shortenPath(hashDb.getIndexPath()));
|
||||
} catch (TskCoreException ex) {
|
||||
Logger.getLogger(HashLookupSettingsPanel.class.getName()).log(Level.SEVERE, "Error getting index path of " + db.getHashSetName() + " hash database", ex); //NON-NLS
|
||||
Logger.getLogger(HashLookupSettingsPanel.class.getName()).log(Level.SEVERE, "Error getting index path of " + db.getHashSetName() + " hash set", ex); //NON-NLS
|
||||
indexPathLabel.setText(ERROR_GETTING_PATH_TEXT);
|
||||
}
|
||||
|
||||
@ -237,7 +237,7 @@ public final class HashLookupSettingsPanel extends IngestModuleGlobalSettingsPan
|
||||
indexButton.setEnabled(true);
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
Logger.getLogger(HashLookupSettingsPanel.class.getName()).log(Level.SEVERE, "Error getting index state of hash database", ex); //NON-NLS
|
||||
Logger.getLogger(HashLookupSettingsPanel.class.getName()).log(Level.SEVERE, "Error getting index state of hash set", ex); //NON-NLS
|
||||
hashDbIndexStatusLabel.setText(ERROR_GETTING_INDEX_STATUS_TEXT);
|
||||
hashDbIndexStatusLabel.setForeground(Color.red);
|
||||
indexButton.setText(NbBundle.getMessage(this.getClass(), "HashDbConfigPanel.indexButtonText.index"));
|
||||
@ -299,9 +299,9 @@ public final class HashLookupSettingsPanel extends IngestModuleGlobalSettingsPan
|
||||
}
|
||||
|
||||
@Override
|
||||
@Messages({"HashLookupSettingsPanel.saveFail.message=Couldn't save hash db settings.",
|
||||
@Messages({"HashLookupSettingsPanel.saveFail.message=Couldn't save hash set settings.",
|
||||
"HashLookupSettingsPanel.saveFail.title=Save Fail"})
|
||||
public void saveSettings() {
|
||||
public void saveSettings() {
|
||||
// Clear out the list of new central repo hash sets. They don't need to be
|
||||
// indexed so will all be saved on both code paths.
|
||||
newReferenceSetIDs.clear();
|
||||
@ -316,7 +316,7 @@ public final class HashLookupSettingsPanel extends IngestModuleGlobalSettingsPan
|
||||
unindexed.add(hashDatabase);
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
Logger.getLogger(HashLookupSettingsPanel.class.getName()).log(Level.SEVERE, "Error getting index info for hash database", ex); //NON-NLS
|
||||
Logger.getLogger(HashLookupSettingsPanel.class.getName()).log(Level.SEVERE, "Error getting index info for hash set", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -399,7 +399,7 @@ public final class HashLookupSettingsPanel extends IngestModuleGlobalSettingsPan
|
||||
* unindexed, along with solutions. This method is related to
|
||||
* ModalNoButtons, to be removed at a later date.
|
||||
*
|
||||
* @param plural Whether or not there are multiple unindexed databases
|
||||
* @param plural Whether or not there are multiple unindexed databases
|
||||
* @param unindexed The list of unindexed databases. Can be of size 1.
|
||||
*/
|
||||
private void showInvalidIndex(boolean plural, List<SleuthkitHashSet> unindexed) {
|
||||
@ -515,7 +515,7 @@ public final class HashLookupSettingsPanel extends IngestModuleGlobalSettingsPan
|
||||
try {
|
||||
return hashSets.get(rowIndex).isValid();
|
||||
} catch (TskCoreException ex) {
|
||||
Logger.getLogger(HashSetTableModel.class.getName()).log(Level.SEVERE, "Error getting index info for hash database", ex); //NON-NLS
|
||||
Logger.getLogger(HashSetTableModel.class.getName()).log(Level.SEVERE, "Error getting index info for hash set", ex); //NON-NLS
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -998,12 +998,14 @@ public final class HashLookupSettingsPanel extends IngestModuleGlobalSettingsPan
|
||||
}
|
||||
}//GEN-LAST:event_importDatabaseButtonActionPerformed
|
||||
|
||||
@Messages({})
|
||||
@Messages({
|
||||
"HashLookupSettingsPanel.promptTitle.deleteHashDb=Delete Hash Database from Configuration",
|
||||
"HashLookupSettingsPanel.promptMessage.deleteHashDb=This will make the hash database unavailable for lookup. Do you want to proceed?\n\nNote: The hash database can still be re-imported later."
|
||||
})
|
||||
private void deleteDatabaseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteDatabaseButtonActionPerformed
|
||||
if (JOptionPane.showConfirmDialog(null,
|
||||
NbBundle.getMessage(this.getClass(),
|
||||
"HashDbConfigPanel.deleteDbActionConfirmMsg"),
|
||||
NbBundle.getMessage(this.getClass(), "HashDbConfigPanel.deleteDbActionMsg"),
|
||||
Bundle.HashLookupSettingsPanel_promptMessage_deleteHashDb(),
|
||||
Bundle.HashLookupSettingsPanel_promptTitle_deleteHashDb(),
|
||||
JOptionPane.YES_NO_OPTION,
|
||||
JOptionPane.WARNING_MESSAGE) == JOptionPane.YES_OPTION) {
|
||||
HashDb hashDb = ((HashSetTable) hashSetTable).getSelection();
|
||||
|
@ -64,7 +64,7 @@ public class KdbHashSetParser implements HashSetParser {
|
||||
totalHashes = resultSet.getLong("count");
|
||||
} else {
|
||||
close();
|
||||
throw new TskCoreException("Error getting hash count from database " + filename);
|
||||
throw new TskCoreException("Error getting hash count from hash set " + filename);
|
||||
}
|
||||
|
||||
// Get the hashes
|
||||
@ -72,7 +72,7 @@ public class KdbHashSetParser implements HashSetParser {
|
||||
|
||||
// At this point, getNextHash can read each hash from the result set
|
||||
} catch (ClassNotFoundException | SQLException ex) {
|
||||
throw new TskCoreException("Error opening/reading database " + filename, ex);
|
||||
throw new TskCoreException("Error opening/reading hash set " + filename, ex);
|
||||
}
|
||||
|
||||
}
|
||||
@ -101,10 +101,10 @@ public class KdbHashSetParser implements HashSetParser {
|
||||
totalHashesRead++;
|
||||
return sb.toString();
|
||||
} else {
|
||||
throw new TskCoreException("Could not read expected number of hashes from database " + filename);
|
||||
throw new TskCoreException("Could not read expected number of hashes from hash set " + filename);
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
throw new TskCoreException("Error reading hash from result set for database " + filename, ex);
|
||||
throw new TskCoreException("Error reading hash from result set for hash set " + filename, ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,8 +38,6 @@ FilesSetRulePanel.interesting.jLabel5.text=Enter information about files that yo
|
||||
FilesSetRulePanel.ingest.jLabel5.text=Enter information about files that you want to run ingest on.
|
||||
FilesSetRulePanel.nameCheck.text=Name Pattern:
|
||||
FilesSetRulePanel.pathCheck.text=Path Pattern:
|
||||
FilesSetRulePanel.mimeCheck.text=MIME Type:
|
||||
FilesSetRulePanel.fileSizeCheck.text=File Size:
|
||||
FilesSetRulePanel.filesRadioButton.text=Files
|
||||
FilesSetRulePanel.dirsRadioButton.text=Directories
|
||||
FilesSetDefsPanel.interesting.setsListLabel.text=Rule Sets:
|
||||
@ -79,3 +77,10 @@ FilesSetDefsPanel.ingoreUnallocCheckbox.text=Ignore Unallocated Space
|
||||
FilesSetDefsPanel.ingoreUnallocCheckbox.toolTipText=Ignores unallocated space, such as deleted files. May run faster but produce less complete results.
|
||||
FilesSetDefsPanel.ingestWarningLabel.text=Ingest is ongoing, some settings will be unavailable until it finishes.
|
||||
FilesSetDefsPanel.allRadioButton.text=All
|
||||
FilesSetRulePanel.dateCheck.text=Modified Within:
|
||||
FilesSetRulePanel.fileSizeCheck.text=File Size:
|
||||
FilesSetRulePanel.mimeCheck.text=MIME Type:
|
||||
FilesSetDefsPanel.modifiedDateLabel.text=Modified Within:
|
||||
FilesSetDefsPanel.daysIncludedTextField.text=
|
||||
FilesSetDefsPanel.daysIncludedLabel.text=day(s)
|
||||
FilesSetRulePanel.daysIncludedLabel.text=day(s)
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2017 Basis Technology Corp.
|
||||
* Copyright 2011-2018 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -25,6 +25,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
|
||||
@ -169,6 +170,7 @@ public final class FilesSet implements Serializable {
|
||||
private final ParentPathCondition pathCondition;
|
||||
private final MimeTypeCondition mimeTypeCondition;
|
||||
private final FileSizeCondition fileSizeCondition;
|
||||
private final DateCondition dateCondition;
|
||||
private final List<FileAttributeCondition> conditions = new ArrayList<>();
|
||||
|
||||
/**
|
||||
@ -180,8 +182,10 @@ public final class FilesSet implements Serializable {
|
||||
* @param pathCondition A file path condition, may be null.
|
||||
* @param mimeTypeCondition A file mime type condition, may be null.
|
||||
* @param fileSizeCondition A file size condition, may be null.
|
||||
* @param dateCondition A file date created or modified condition,
|
||||
* may be null
|
||||
*/
|
||||
Rule(String ruleName, FileNameCondition fileNameCondition, MetaTypeCondition metaTypeCondition, ParentPathCondition pathCondition, MimeTypeCondition mimeTypeCondition, FileSizeCondition fileSizeCondition) {
|
||||
Rule(String ruleName, FileNameCondition fileNameCondition, MetaTypeCondition metaTypeCondition, ParentPathCondition pathCondition, MimeTypeCondition mimeTypeCondition, FileSizeCondition fileSizeCondition, DateCondition dateCondition) {
|
||||
// since ruleName is optional, ruleUUID can be used to uniquely identify a rule.
|
||||
this.uuid = UUID.randomUUID().toString();
|
||||
if (metaTypeCondition == null) {
|
||||
@ -216,6 +220,10 @@ public final class FilesSet implements Serializable {
|
||||
if (this.pathCondition != null) {
|
||||
this.conditions.add(this.pathCondition);
|
||||
}
|
||||
this.dateCondition = dateCondition;
|
||||
if (this.dateCondition != null) {
|
||||
this.conditions.add(this.dateCondition);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -254,6 +262,10 @@ public final class FilesSet implements Serializable {
|
||||
return this.pathCondition;
|
||||
}
|
||||
|
||||
DateCondition getDateCondition() {
|
||||
return this.dateCondition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not a file satisfies the rule.
|
||||
*
|
||||
@ -270,6 +282,10 @@ public final class FilesSet implements Serializable {
|
||||
return true;
|
||||
}
|
||||
|
||||
@NbBundle.Messages({
|
||||
"# {0} - daysIncluded",
|
||||
"FilesSet.rule.dateRule.toString=(modified within {0} day(s))"
|
||||
})
|
||||
@Override
|
||||
public String toString() {
|
||||
// This override is designed to provide a display name for use with
|
||||
@ -283,6 +299,8 @@ public final class FilesSet implements Serializable {
|
||||
} else if (this.fileSizeCondition != null) {
|
||||
return this.ruleName + " (" + fileSizeCondition.getComparator().getSymbol() + " " + fileSizeCondition.getSizeValue()
|
||||
+ " " + fileSizeCondition.getUnit().getName() + ")";
|
||||
} else if (this.dateCondition != null) {
|
||||
return this.ruleName + Bundle.FilesSet_rule_dateRule_toString(dateCondition.getDaysIncluded());
|
||||
} else {
|
||||
return this.ruleName + " ()";
|
||||
}
|
||||
@ -537,7 +555,7 @@ public final class FilesSet implements Serializable {
|
||||
case FILES:
|
||||
return file.isFile();
|
||||
case DIRECTORIES:
|
||||
return file.getMetaType() == TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_DIR
|
||||
return file.getMetaType() == TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_DIR
|
||||
|| file.getMetaType() == TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_VIRT_DIR;
|
||||
case FILES_AND_DIRECTORIES:
|
||||
return file.getMetaType() == TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_REG
|
||||
@ -737,6 +755,46 @@ public final class FilesSet implements Serializable {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A class for checking whether a file's creation or modification
|
||||
* occured in a specific range of time
|
||||
*/
|
||||
static final class DateCondition implements FileAttributeCondition {
|
||||
|
||||
private final static long SECS_PER_DAY = 60 * 60 * 24;
|
||||
|
||||
private int daysIncluded;
|
||||
|
||||
/**
|
||||
* Construct a new DateCondition
|
||||
*
|
||||
* @param days - files created or modified more recently than this
|
||||
* number of days will pass
|
||||
*/
|
||||
DateCondition(int days) {
|
||||
daysIncluded = days;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of days which this condition allows to pass
|
||||
*
|
||||
* @return integer value of the number days which will pass
|
||||
*/
|
||||
int getDaysIncluded() {
|
||||
return daysIncluded;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean passes(AbstractFile file) {
|
||||
long dateThreshold = System.currentTimeMillis() / 1000 - daysIncluded * SECS_PER_DAY;
|
||||
if (file.getCrtime() > dateThreshold || file.getMtime() > dateThreshold) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A file name extension condition for an interesting files set
|
||||
* membership rule. The immutability of a file name extension condition
|
||||
|
@ -90,109 +90,91 @@
|
||||
</Group>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
<Component id="separator" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace min="-2" pref="101" max="-2" attributes="0"/>
|
||||
<Component id="filesRadioButton" min="-2" pref="47" max="-2" attributes="0"/>
|
||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||
<Component id="dirsRadioButton" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="allRadioButton" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace min="-2" pref="105" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="fileNameRadioButton" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" pref="4" max="-2" attributes="0"/>
|
||||
<Component id="fileNameExtensionRadioButton" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||
<Component id="fileNameRegexCheckbox" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="rulePathConditionRegexCheckBox" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="rulesListLabel" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="jLabel1" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="newRuleButton" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace type="separate" max="-2" attributes="0"/>
|
||||
<Component id="editRuleButton" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace type="separate" max="-2" attributes="0"/>
|
||||
<Component id="deleteRuleButton" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="ignoreKnownFilesCheckbox" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="ingoreUnallocCheckbox" min="-2" pref="158" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="jLabel5" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="jLabel6" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="ingestWarningLabel" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace min="24" pref="28" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" attributes="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="rulesListScrollPane" alignment="1" max="32767" attributes="0"/>
|
||||
<Component id="setDescScrollPanel" alignment="1" max="32767" attributes="0"/>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace min="-2" pref="22" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="jLabel7" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="jLabel8" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace type="separate" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="103" alignment="0" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="jLabel3" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="jLabel2" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace min="-2" pref="6" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<Component id="jLabel4" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace min="-2" pref="16" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" max="-2" attributes="0">
|
||||
<Component id="jLabel7" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="jLabel8" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="jLabel2" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="jLabel4" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="modifiedDateLabel" alignment="0" max="32767" attributes="0"/>
|
||||
<Component id="jLabel3" alignment="0" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="equalitySignComboBox" min="-2" pref="44" max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
<Component id="fileSizeSpinner" max="32767" attributes="0"/>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
<Component id="fileSizeUnitComboBox" min="-2" pref="79" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="rulePathConditionTextField" alignment="0" max="32767" attributes="0"/>
|
||||
<Component id="fileNameTextField" alignment="1" max="32767" attributes="0"/>
|
||||
<Component id="mimeTypeComboBox" alignment="0" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="rulesListScrollPane" alignment="1" max="32767" attributes="0"/>
|
||||
<Component id="setDescScrollPanel" alignment="1" max="32767" attributes="0"/>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="equalitySignComboBox" min="-2" pref="60" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="fileSizeSpinner" max="32767" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="fileSizeUnitComboBox" min="-2" pref="79" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" attributes="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="fileNameRadioButton" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="rulePathConditionRegexCheckBox" min="-2" max="-2" attributes="0"/>
|
||||
<Group type="102" attributes="0">
|
||||
<Component id="daysIncludedTextField" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="daysIncludedLabel" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" attributes="0">
|
||||
<Component id="filesRadioButton" min="-2" pref="47" max="-2" attributes="0"/>
|
||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||
<Component id="dirsRadioButton" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="allRadioButton" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace min="-2" pref="8" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" attributes="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="rulesListLabel" min="-2" max="-2" attributes="0"/>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="ignoreKnownFilesCheckbox" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="ingoreUnallocCheckbox" min="-2" pref="158" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="jLabel5" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="jLabel6" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="ingestWarningLabel" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace min="-2" pref="174" max="-2" attributes="0"/>
|
||||
<Component id="fileNameExtensionRadioButton" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||
<Component id="fileNameRegexCheckbox" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="jLabel1" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="newRuleButton" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace type="separate" max="-2" attributes="0"/>
|
||||
<Component id="editRuleButton" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace type="separate" max="-2" attributes="0"/>
|
||||
<Component id="deleteRuleButton" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace min="24" pref="28" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</Group>
|
||||
</Group>
|
||||
@ -213,9 +195,9 @@
|
||||
</Group>
|
||||
<Component id="ingestWarningLabel" alignment="1" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="setDescScrollPanel" min="-2" pref="45" max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" pref="6" max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
<Component id="setDescScrollPanel" pref="69" max="32767" attributes="0"/>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="ignoreKnownFilesCheckbox" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="ingoreUnallocCheckbox" alignment="3" min="-2" pref="23" max="-2" attributes="0"/>
|
||||
@ -223,7 +205,7 @@
|
||||
<EmptySpace type="unrelated" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="rulesListLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
<Component id="rulesListScrollPane" pref="82" max="32767" attributes="0"/>
|
||||
<Component id="rulesListScrollPane" pref="61" max="32767" attributes="0"/>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="newRuleButton" linkSize="2" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
@ -232,14 +214,14 @@
|
||||
</Group>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
<Component id="jLabel1" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" pref="8" max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="jLabel2" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="filesRadioButton" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="dirsRadioButton" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="allRadioButton" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace min="-2" pref="8" max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="jLabel3" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="fileNameTextField" alignment="3" min="-2" pref="20" max="-2" attributes="0"/>
|
||||
@ -250,26 +232,32 @@
|
||||
<Component id="fileNameExtensionRadioButton" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="fileNameRegexCheckbox" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace min="-2" pref="8" max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="jLabel4" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="rulePathConditionTextField" alignment="3" min="-2" pref="20" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace min="-2" pref="6" max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
<Component id="rulePathConditionRegexCheckBox" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace type="unrelated" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="jLabel7" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="mimeTypeComboBox" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace type="unrelated" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="jLabel8" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="equalitySignComboBox" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="fileSizeSpinner" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="fileSizeUnitComboBox" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace min="-2" pref="5" max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="modifiedDateLabel" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="daysIncludedTextField" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="daysIncludedLabel" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" attributes="0">
|
||||
<Component id="jScrollPane2" min="-2" max="-2" attributes="0"/>
|
||||
@ -437,7 +425,7 @@
|
||||
</FontInfo>
|
||||
</Property>
|
||||
<Property name="lineWrap" type="boolean" value="true"/>
|
||||
<Property name="rows" type="int" value="10"/>
|
||||
<Property name="rows" type="int" value="6"/>
|
||||
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[10, 22]"/>
|
||||
</Property>
|
||||
@ -996,6 +984,36 @@
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="exportSetButtonActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="modifiedDateLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/modules/interestingitems/Bundle.properties" key="FilesSetDefsPanel.modifiedDateLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JTextField" name="daysIncludedTextField">
|
||||
<Properties>
|
||||
<Property name="editable" type="boolean" value="false"/>
|
||||
<Property name="horizontalAlignment" type="int" value="11"/>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/modules/interestingitems/Bundle.properties" key="FilesSetDefsPanel.daysIncludedTextField.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[60, 20]"/>
|
||||
</Property>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[60, 20]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="daysIncludedLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/modules/interestingitems/Bundle.properties" key="FilesSetDefsPanel.daysIncludedLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
<Property name="enabled" type="boolean" value="false"/>
|
||||
</Properties>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2017 Basis Technology Corp.
|
||||
* Copyright 2011-2018 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -251,6 +251,7 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp
|
||||
this.fileNameRegexCheckbox.setSelected(false);
|
||||
this.filesRadioButton.setSelected(true);
|
||||
this.rulePathConditionTextField.setText("");
|
||||
this.daysIncludedTextField.setText("");
|
||||
this.rulePathConditionRegexCheckBox.setSelected(false);
|
||||
this.mimeTypeComboBox.setSelectedIndex(0);
|
||||
this.equalitySignComboBox.setSelectedIndex(2);
|
||||
@ -326,7 +327,7 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp
|
||||
FilesSet.Rule.ParentPathCondition pathCondition = rule.getPathCondition();
|
||||
FilesSet.Rule.MimeTypeCondition mimeTypeCondition = rule.getMimeTypeCondition();
|
||||
FilesSet.Rule.FileSizeCondition fileSizeCondition = rule.getFileSizeCondition();
|
||||
|
||||
FilesSet.Rule.DateCondition dateCondition = rule.getDateCondition();
|
||||
// Populate the components that display the properties of the
|
||||
// selected rule.
|
||||
if (nameCondition != null) {
|
||||
@ -372,7 +373,12 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp
|
||||
FilesSetDefsPanel.this.equalitySignComboBox.setSelectedIndex(2);
|
||||
FilesSetDefsPanel.this.fileSizeSpinner.setValue(0);
|
||||
}
|
||||
|
||||
if (dateCondition != null){
|
||||
FilesSetDefsPanel.this.daysIncludedTextField.setText(Integer.toString(dateCondition.getDaysIncluded()));
|
||||
}
|
||||
else {
|
||||
FilesSetDefsPanel.this.daysIncludedTextField.setText("");
|
||||
}
|
||||
// Enable the new, edit and delete rule buttons.
|
||||
FilesSetDefsPanel.this.newRuleButton.setEnabled(true && canBeEnabled);
|
||||
FilesSetDefsPanel.this.editRuleButton.setEnabled(true && canBeEnabled);
|
||||
@ -477,7 +483,7 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp
|
||||
if (selectedRule != null) {
|
||||
rules.remove(selectedRule.getUuid());
|
||||
}
|
||||
FilesSet.Rule newRule = new FilesSet.Rule(panel.getRuleName(), panel.getFileNameCondition(), panel.getMetaTypeCondition(), panel.getPathCondition(), panel.getMimeTypeCondition(), panel.getFileSizeCondition());
|
||||
FilesSet.Rule newRule = new FilesSet.Rule(panel.getRuleName(), panel.getFileNameCondition(), panel.getMetaTypeCondition(), panel.getPathCondition(), panel.getMimeTypeCondition(), panel.getFileSizeCondition(), panel.getDateCondition());
|
||||
rules.put(newRule.getUuid(), newRule);
|
||||
|
||||
// Add the new/edited files set definition, replacing any previous
|
||||
@ -590,6 +596,9 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp
|
||||
copySetButton = new javax.swing.JButton();
|
||||
importSetButton = new javax.swing.JButton();
|
||||
exportSetButton = new javax.swing.JButton();
|
||||
modifiedDateLabel = new javax.swing.JLabel();
|
||||
daysIncludedTextField = new javax.swing.JTextField();
|
||||
daysIncludedLabel = new javax.swing.JLabel();
|
||||
|
||||
setFont(getFont().deriveFont(getFont().getStyle() & ~java.awt.Font.BOLD, 11));
|
||||
|
||||
@ -643,7 +652,7 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp
|
||||
setDescriptionTextArea.setColumns(20);
|
||||
setDescriptionTextArea.setFont(setDescriptionTextArea.getFont().deriveFont(setDescriptionTextArea.getFont().getStyle() & ~java.awt.Font.BOLD, 13));
|
||||
setDescriptionTextArea.setLineWrap(true);
|
||||
setDescriptionTextArea.setRows(10);
|
||||
setDescriptionTextArea.setRows(6);
|
||||
setDescriptionTextArea.setMinimumSize(new java.awt.Dimension(10, 22));
|
||||
setDescScrollPanel.setViewportView(setDescriptionTextArea);
|
||||
|
||||
@ -841,6 +850,17 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp
|
||||
}
|
||||
});
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(modifiedDateLabel, org.openide.util.NbBundle.getMessage(FilesSetDefsPanel.class, "FilesSetDefsPanel.modifiedDateLabel.text")); // NOI18N
|
||||
|
||||
daysIncludedTextField.setEditable(false);
|
||||
daysIncludedTextField.setHorizontalAlignment(javax.swing.JTextField.TRAILING);
|
||||
daysIncludedTextField.setText(org.openide.util.NbBundle.getMessage(FilesSetDefsPanel.class, "FilesSetDefsPanel.daysIncludedTextField.text")); // NOI18N
|
||||
daysIncludedTextField.setMinimumSize(new java.awt.Dimension(60, 20));
|
||||
daysIncludedTextField.setPreferredSize(new java.awt.Dimension(60, 20));
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(daysIncludedLabel, org.openide.util.NbBundle.getMessage(FilesSetDefsPanel.class, "FilesSetDefsPanel.daysIncludedLabel.text")); // NOI18N
|
||||
daysIncludedLabel.setEnabled(false);
|
||||
|
||||
javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
|
||||
jPanel1.setLayout(jPanel1Layout);
|
||||
jPanel1Layout.setHorizontalGroup(
|
||||
@ -867,83 +887,74 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp
|
||||
.addComponent(setsListLabel))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(separator, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(jPanel1Layout.createSequentialGroup()
|
||||
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(rulesListScrollPane, javax.swing.GroupLayout.Alignment.TRAILING)
|
||||
.addComponent(setDescScrollPanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addGroup(jPanel1Layout.createSequentialGroup()
|
||||
.addGap(101, 101, 101)
|
||||
.addComponent(filesRadioButton, javax.swing.GroupLayout.PREFERRED_SIZE, 47, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addGap(16, 16, 16)
|
||||
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
|
||||
.addComponent(jLabel7)
|
||||
.addComponent(jLabel8)
|
||||
.addComponent(jLabel2)
|
||||
.addComponent(jLabel4)
|
||||
.addComponent(modifiedDateLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(jLabel3, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||
.addComponent(dirsRadioButton)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(allRadioButton))
|
||||
.addGroup(jPanel1Layout.createSequentialGroup()
|
||||
.addGap(105, 105, 105)
|
||||
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(rulePathConditionTextField)
|
||||
.addComponent(fileNameTextField, javax.swing.GroupLayout.Alignment.TRAILING)
|
||||
.addComponent(mimeTypeComboBox, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addGroup(jPanel1Layout.createSequentialGroup()
|
||||
.addComponent(fileNameRadioButton)
|
||||
.addGap(4, 4, 4)
|
||||
.addComponent(fileNameExtensionRadioButton)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||
.addComponent(fileNameRegexCheckbox))
|
||||
.addComponent(rulePathConditionRegexCheckBox)))
|
||||
.addGroup(jPanel1Layout.createSequentialGroup()
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(rulesListLabel)
|
||||
.addComponent(jLabel1)
|
||||
.addGroup(jPanel1Layout.createSequentialGroup()
|
||||
.addComponent(newRuleButton)
|
||||
.addGap(18, 18, 18)
|
||||
.addComponent(editRuleButton)
|
||||
.addGap(18, 18, 18)
|
||||
.addComponent(deleteRuleButton))
|
||||
.addGroup(jPanel1Layout.createSequentialGroup()
|
||||
.addComponent(ignoreKnownFilesCheckbox)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(ingoreUnallocCheckbox, javax.swing.GroupLayout.PREFERRED_SIZE, 158, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addGroup(jPanel1Layout.createSequentialGroup()
|
||||
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(jLabel5)
|
||||
.addComponent(jLabel6))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(ingestWarningLabel)))))
|
||||
.addGap(24, 28, Short.MAX_VALUE))
|
||||
.addGroup(jPanel1Layout.createSequentialGroup()
|
||||
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(jPanel1Layout.createSequentialGroup()
|
||||
.addGap(22, 22, 22)
|
||||
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(jPanel1Layout.createSequentialGroup()
|
||||
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(jLabel7)
|
||||
.addComponent(jLabel8))
|
||||
.addGap(18, 18, 18))
|
||||
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(jPanel1Layout.createSequentialGroup()
|
||||
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(jLabel3)
|
||||
.addComponent(jLabel2))
|
||||
.addGap(6, 6, 6))
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup()
|
||||
.addComponent(jLabel4)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED))))
|
||||
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(jPanel1Layout.createSequentialGroup()
|
||||
.addComponent(equalitySignComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 44, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(equalitySignComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 60, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(fileSizeSpinner, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(fileSizeUnitComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 79, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addComponent(rulePathConditionTextField)
|
||||
.addComponent(fileNameTextField, javax.swing.GroupLayout.Alignment.TRAILING)
|
||||
.addComponent(mimeTypeComboBox, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup()
|
||||
.addGroup(jPanel1Layout.createSequentialGroup()
|
||||
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(fileNameRadioButton)
|
||||
.addComponent(rulePathConditionRegexCheckBox)
|
||||
.addGroup(jPanel1Layout.createSequentialGroup()
|
||||
.addComponent(daysIncludedTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(daysIncludedLabel))
|
||||
.addGroup(jPanel1Layout.createSequentialGroup()
|
||||
.addComponent(filesRadioButton, javax.swing.GroupLayout.PREFERRED_SIZE, 47, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||
.addComponent(dirsRadioButton)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(allRadioButton)))
|
||||
.addGap(0, 0, Short.MAX_VALUE)))))
|
||||
.addGap(8, 8, 8))
|
||||
.addGroup(jPanel1Layout.createSequentialGroup()
|
||||
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(rulesListLabel)
|
||||
.addGroup(jPanel1Layout.createSequentialGroup()
|
||||
.addComponent(ignoreKnownFilesCheckbox)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(ingoreUnallocCheckbox, javax.swing.GroupLayout.PREFERRED_SIZE, 158, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addGroup(jPanel1Layout.createSequentialGroup()
|
||||
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(rulesListScrollPane, javax.swing.GroupLayout.Alignment.TRAILING)
|
||||
.addComponent(setDescScrollPanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))))
|
||||
.addGap(8, 8, 8))))
|
||||
.addComponent(jLabel5)
|
||||
.addComponent(jLabel6))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(ingestWarningLabel))
|
||||
.addGroup(jPanel1Layout.createSequentialGroup()
|
||||
.addGap(174, 174, 174)
|
||||
.addComponent(fileNameExtensionRadioButton)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||
.addComponent(fileNameRegexCheckbox))
|
||||
.addComponent(jLabel1)
|
||||
.addGroup(jPanel1Layout.createSequentialGroup()
|
||||
.addComponent(newRuleButton)
|
||||
.addGap(18, 18, 18)
|
||||
.addComponent(editRuleButton)
|
||||
.addGap(18, 18, 18)
|
||||
.addComponent(deleteRuleButton)))
|
||||
.addGap(24, 28, Short.MAX_VALUE))))
|
||||
);
|
||||
|
||||
jPanel1Layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {copySetButton, deleteSetButton, editSetButton, exportSetButton, importSetButton, newSetButton});
|
||||
@ -963,15 +974,15 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp
|
||||
.addGap(1, 1, 1))
|
||||
.addComponent(ingestWarningLabel, javax.swing.GroupLayout.Alignment.TRAILING))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(setDescScrollPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 45, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addGap(6, 6, 6)
|
||||
.addComponent(setDescScrollPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 69, Short.MAX_VALUE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(ignoreKnownFilesCheckbox)
|
||||
.addComponent(ingoreUnallocCheckbox, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||
.addComponent(rulesListLabel)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(rulesListScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 82, Short.MAX_VALUE)
|
||||
.addComponent(rulesListScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 61, Short.MAX_VALUE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(newRuleButton)
|
||||
@ -979,13 +990,13 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp
|
||||
.addComponent(deleteRuleButton))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(jLabel1)
|
||||
.addGap(8, 8, 8)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(jLabel2)
|
||||
.addComponent(filesRadioButton)
|
||||
.addComponent(dirsRadioButton)
|
||||
.addComponent(allRadioButton))
|
||||
.addGap(8, 8, 8)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(jLabel3)
|
||||
.addComponent(fileNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
@ -994,23 +1005,28 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp
|
||||
.addComponent(fileNameRadioButton)
|
||||
.addComponent(fileNameExtensionRadioButton)
|
||||
.addComponent(fileNameRegexCheckbox))
|
||||
.addGap(8, 8, 8)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(jLabel4)
|
||||
.addComponent(rulePathConditionTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addGap(6, 6, 6)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(rulePathConditionRegexCheckBox)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(jLabel7)
|
||||
.addComponent(mimeTypeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(jLabel8)
|
||||
.addComponent(equalitySignComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(fileSizeSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(fileSizeUnitComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addGap(5, 5, 5))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(modifiedDateLabel)
|
||||
.addComponent(daysIncludedTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(daysIncludedLabel))
|
||||
.addContainerGap())
|
||||
.addGroup(jPanel1Layout.createSequentialGroup()
|
||||
.addComponent(jScrollPane2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
@ -1262,6 +1278,8 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JRadioButton allRadioButton;
|
||||
private javax.swing.JButton copySetButton;
|
||||
private javax.swing.JLabel daysIncludedLabel;
|
||||
private javax.swing.JTextField daysIncludedTextField;
|
||||
private javax.swing.JButton deleteRuleButton;
|
||||
private javax.swing.JButton deleteSetButton;
|
||||
private javax.swing.JRadioButton dirsRadioButton;
|
||||
@ -1294,6 +1312,7 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp
|
||||
private javax.swing.JScrollPane jScrollPane2;
|
||||
private javax.swing.JTextArea jTextArea1;
|
||||
private javax.swing.JComboBox<String> mimeTypeComboBox;
|
||||
private javax.swing.JLabel modifiedDateLabel;
|
||||
private javax.swing.JButton newRuleButton;
|
||||
private javax.swing.JButton newSetButton;
|
||||
private javax.swing.JCheckBox rulePathConditionRegexCheckBox;
|
||||
|
@ -23,82 +23,80 @@
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<Group type="103" groupAlignment="1" attributes="0">
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace min="-2" pref="8" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="ruleNameLabel" max="32767" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="ruleNameTextField" min="-2" pref="234" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" attributes="0">
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<Component id="ruleNameLabel" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" pref="5" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="mimeTypeComboBox" alignment="0" max="32767" attributes="0"/>
|
||||
<Component id="pathTextField" alignment="0" max="32767" attributes="0"/>
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<Component id="equalitySymbolComboBox" max="32767" attributes="0"/>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
<Component id="fileSizeSpinner" max="32767" attributes="0"/>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
<Component id="fileSizeComboBox" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="pathRegexCheckBox" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
<Component id="pathSeparatorInfoLabel" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" attributes="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<Component id="daysIncludedTextField" min="-2" pref="69" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="daysIncludedLabel" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="ruleNameTextField" pref="249" max="32767" attributes="0"/>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="fullNameRadioButton" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="extensionRadioButton" min="-2" pref="98" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="nameRegexCheckbox" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace min="-2" pref="1" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</Group>
|
||||
<Component id="jLabel5" min="-2" max="-2" attributes="0"/>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="nameCheck" alignment="1" min="-2" pref="95" max="-2" attributes="0"/>
|
||||
<Component id="jLabel1" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace min="-2" pref="16" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="jLabel5" min="-2" max="-2" attributes="0"/>
|
||||
<Group type="102" attributes="0">
|
||||
<Component id="jLabel1" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" pref="65" max="-2" attributes="0"/>
|
||||
<Component id="filesRadioButton" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="dirsRadioButton" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="allRadioButton" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="nameTextField" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</Group>
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="nameCheck" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
<Component id="nameTextField" min="-2" pref="249" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="pathCheck" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" pref="4" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="pathRegexCheckBox" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
<Component id="pathSeparatorInfoLabel" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
|
||||
<Component id="pathTextField" min="-2" pref="250" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="fullNameRadioButton" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="extensionRadioButton" min="-2" pref="114" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="nameRegexCheckbox" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</Group>
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="pathCheck" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="mimeCheck" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="fileSizeCheck" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="dateCheck" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
<Group type="103" groupAlignment="1" attributes="0">
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<Component id="equalitySymbolComboBox" min="-2" pref="36" max="-2" attributes="0"/>
|
||||
<EmptySpace type="separate" max="-2" attributes="0"/>
|
||||
<Component id="fileSizeSpinner" min="-2" pref="94" max="-2" attributes="0"/>
|
||||
<EmptySpace type="separate" max="-2" attributes="0"/>
|
||||
<Component id="fileSizeComboBox" min="-2" pref="82" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="mimeTypeComboBox" min="-2" pref="250" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
@ -106,17 +104,22 @@
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="jLabel5" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="jLabel1" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="filesRadioButton" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="dirsRadioButton" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="allRadioButton" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace min="-2" pref="5" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="nameTextField" alignment="3" min="-2" pref="20" max="-2" attributes="0"/>
|
||||
<Component id="nameCheck" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" pref="3" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="1" attributes="0">
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<Component id="jLabel1" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="-2" pref="10" max="-2" attributes="0"/>
|
||||
<Component id="nameCheck" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="filesRadioButton" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="dirsRadioButton" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="allRadioButton" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||
<Component id="nameTextField" min="-2" pref="20" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
@ -134,7 +137,7 @@
|
||||
<Component id="pathRegexCheckBox" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="pathSeparatorInfoLabel" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace pref="8" max="32767" attributes="0"/>
|
||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="mimeTypeComboBox" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="mimeCheck" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
@ -146,7 +149,13 @@
|
||||
<Component id="fileSizeSpinner" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="fileSizeCheck" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace min="-2" pref="15" max="-2" attributes="0"/>
|
||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="daysIncludedTextField" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="daysIncludedLabel" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="dateCheck" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace type="unrelated" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="ruleNameTextField" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="ruleNameLabel" alignment="3" max="32767" attributes="0"/>
|
||||
@ -170,9 +179,6 @@
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/modules/interestingitems/Bundle.properties" key="FilesSetRulePanel.ruleNameTextField.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="ruleNameTextFieldActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="jLabel1">
|
||||
<Properties>
|
||||
@ -380,5 +386,33 @@
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="allRadioButtonActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JTextField" name="daysIncludedTextField">
|
||||
<Properties>
|
||||
<Property name="enabled" type="boolean" value="false"/>
|
||||
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[60, 20]"/>
|
||||
</Property>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[60, 20]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="daysIncludedLabel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/modules/interestingitems/Bundle.properties" key="FilesSetRulePanel.daysIncludedLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JCheckBox" name="dateCheck">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/modules/interestingitems/Bundle.properties" key="FilesSetRulePanel.dateCheck.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="dateCheckActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Form>
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2014-2017 Basis Technology Corp.
|
||||
* Copyright 2014-2018 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -19,7 +19,6 @@
|
||||
package org.sleuthkit.autopsy.modules.interestingitems;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.List;
|
||||
import java.util.SortedSet;
|
||||
import java.util.logging.Level;
|
||||
@ -50,6 +49,8 @@ final class FilesSetRulePanel extends javax.swing.JPanel {
|
||||
"FilesSetRulePanel.NoMimeTypeError=Please select a valid MIME type.",
|
||||
"FilesSetRulePanel.NoNameError=Name cannot be empty",
|
||||
"FilesSetRulePanel.NoPathError=Path cannot be empty",
|
||||
"FilesSetRulePanel.DaysIncludedEmptyError=Number of days included cannot be empty.",
|
||||
"FilesSetRulePanel.DaysIncludedInvalidError=Number of days included must be a positive integer.",
|
||||
"FilesSetRulePanel.ZeroFileSizeError=File size condition value must not be 0 (Unless = is selected)."
|
||||
})
|
||||
|
||||
@ -82,6 +83,7 @@ final class FilesSetRulePanel extends javax.swing.JPanel {
|
||||
} else {
|
||||
populateMimeTypesComboBox();
|
||||
}
|
||||
this.dateCheckActionPerformed(null);
|
||||
populateComponentsWithDefaultValues();
|
||||
this.setButtons(okButton, cancelButton);
|
||||
}
|
||||
@ -108,12 +110,14 @@ final class FilesSetRulePanel extends javax.swing.JPanel {
|
||||
populateMimeTypesComboBox();
|
||||
populateMimeConditionComponents(rule);
|
||||
populateSizeConditionComponents(rule);
|
||||
|
||||
}
|
||||
populateMimeTypesComboBox();
|
||||
populateRuleNameComponent(rule);
|
||||
populateTypeConditionComponents(rule);
|
||||
populateNameConditionComponents(rule);
|
||||
populatePathConditionComponents(rule);
|
||||
populateDateConditionComponents(rule);
|
||||
this.setButtons(okButton, cancelButton);
|
||||
}
|
||||
|
||||
@ -176,7 +180,7 @@ final class FilesSetRulePanel extends javax.swing.JPanel {
|
||||
private void setOkButton() {
|
||||
if (this.okButton != null) {
|
||||
this.okButton.setEnabled(this.fileSizeCheck.isSelected() || this.mimeCheck.isSelected()
|
||||
|| this.nameCheck.isSelected() || this.pathCheck.isSelected());
|
||||
|| this.nameCheck.isSelected() || this.pathCheck.isSelected() || this.dateCheck.isSelected());
|
||||
}
|
||||
}
|
||||
|
||||
@ -274,6 +278,21 @@ final class FilesSetRulePanel extends javax.swing.JPanel {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the UI components that display the optional date condition for
|
||||
* a rule.
|
||||
*
|
||||
* @param rule The files set rule to be edited.
|
||||
*/
|
||||
private void populateDateConditionComponents(FilesSet.Rule rule) {
|
||||
FilesSet.Rule.DateCondition dateCondition = rule.getDateCondition();
|
||||
if (dateCondition != null) {
|
||||
this.dateCheck.setSelected(true);
|
||||
this.dateCheckActionPerformed(null);
|
||||
this.daysIncludedTextField.setText(Integer.toString(dateCondition.getDaysIncluded()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the data entered in the panel constitutes a valid
|
||||
* files set membership rule definition, displaying a dialog explaining the
|
||||
@ -283,7 +302,7 @@ final class FilesSetRulePanel extends javax.swing.JPanel {
|
||||
*/
|
||||
boolean isValidRuleDefinition() {
|
||||
|
||||
if (!(this.mimeCheck.isSelected() || this.fileSizeCheck.isSelected() || this.pathCheck.isSelected() || this.nameCheck.isSelected())) {
|
||||
if (!(this.mimeCheck.isSelected() || this.fileSizeCheck.isSelected() || this.pathCheck.isSelected() || this.nameCheck.isSelected() || this.dateCheck.isSelected())) {
|
||||
NotifyDescriptor notifyDesc = new NotifyDescriptor.Message(
|
||||
Bundle.FilesSetRulePanel_NoConditionError(),
|
||||
NotifyDescriptor.WARNING_MESSAGE);
|
||||
@ -367,6 +386,28 @@ final class FilesSetRulePanel extends javax.swing.JPanel {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.dateCheck.isSelected()) {
|
||||
if (this.daysIncludedTextField.getText().isEmpty()) {
|
||||
NotifyDescriptor notifyDesc = new NotifyDescriptor.Message(
|
||||
Bundle.FilesSetRulePanel_DaysIncludedEmptyError(),
|
||||
NotifyDescriptor.WARNING_MESSAGE);
|
||||
DialogDisplayer.getDefault().notify(notifyDesc);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
int value = Integer.parseInt(daysIncludedTextField.getText());
|
||||
if (value < 0) {
|
||||
throw new NumberFormatException("Negative numbers are not allowed for the within N days condition");
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
//field did not contain an integer
|
||||
NotifyDescriptor notifyDesc = new NotifyDescriptor.Message(
|
||||
Bundle.FilesSetRulePanel_DaysIncludedInvalidError(),
|
||||
NotifyDescriptor.WARNING_MESSAGE);
|
||||
DialogDisplayer.getDefault().notify(notifyDesc);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -504,6 +545,23 @@ final class FilesSetRulePanel extends javax.swing.JPanel {
|
||||
return condition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the optional date condition for the rule that was created or edited.
|
||||
* Should only be called if isValidDefintion() returns true.
|
||||
*
|
||||
* @return A date condition or null if no date condition was specified.
|
||||
*
|
||||
* @throws IllegalStateException if the specified date condition is not
|
||||
* valid.
|
||||
*/
|
||||
FilesSet.Rule.DateCondition getDateCondition() {
|
||||
FilesSet.Rule.DateCondition dateCondition = null;
|
||||
if (!daysIncludedTextField.getText().isEmpty()) {
|
||||
dateCondition = new FilesSet.Rule.DateCondition(Integer.parseInt(daysIncludedTextField.getText()));
|
||||
}
|
||||
return dateCondition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks an input string for the use of illegal characters.
|
||||
*
|
||||
@ -582,15 +640,13 @@ final class FilesSetRulePanel extends javax.swing.JPanel {
|
||||
filesRadioButton = new javax.swing.JRadioButton();
|
||||
dirsRadioButton = new javax.swing.JRadioButton();
|
||||
allRadioButton = new javax.swing.JRadioButton();
|
||||
daysIncludedTextField = new javax.swing.JTextField();
|
||||
daysIncludedLabel = new javax.swing.JLabel();
|
||||
dateCheck = new javax.swing.JCheckBox();
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(ruleNameLabel, org.openide.util.NbBundle.getMessage(FilesSetRulePanel.class, "FilesSetRulePanel.ruleNameLabel.text")); // NOI18N
|
||||
|
||||
ruleNameTextField.setText(org.openide.util.NbBundle.getMessage(FilesSetRulePanel.class, "FilesSetRulePanel.ruleNameTextField.text")); // NOI18N
|
||||
ruleNameTextField.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
ruleNameTextFieldActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(FilesSetRulePanel.class, "FilesSetRulePanel.jLabel1.text")); // NOI18N
|
||||
|
||||
@ -685,85 +741,99 @@ final class FilesSetRulePanel extends javax.swing.JPanel {
|
||||
}
|
||||
});
|
||||
|
||||
daysIncludedTextField.setEnabled(false);
|
||||
daysIncludedTextField.setMinimumSize(new java.awt.Dimension(60, 20));
|
||||
daysIncludedTextField.setPreferredSize(new java.awt.Dimension(60, 20));
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(daysIncludedLabel, org.openide.util.NbBundle.getMessage(FilesSetRulePanel.class, "FilesSetRulePanel.daysIncludedLabel.text")); // NOI18N
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(dateCheck, org.openide.util.NbBundle.getMessage(FilesSetRulePanel.class, "FilesSetRulePanel.dateCheck.text")); // NOI18N
|
||||
dateCheck.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
dateCheckActionPerformed(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()
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addGap(8, 8, 8)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addComponent(ruleNameLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(ruleNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 234, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
|
||||
.addComponent(ruleNameLabel)
|
||||
.addGap(5, 5, 5)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(mimeTypeComboBox, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(pathTextField)
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
|
||||
.addComponent(equalitySymbolComboBox, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(fileSizeSpinner)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(fileSizeComboBox, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addComponent(pathRegexCheckBox)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(pathSeparatorInfoLabel))
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addComponent(daysIncludedTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 69, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(daysIncludedLabel))
|
||||
.addComponent(ruleNameTextField, javax.swing.GroupLayout.DEFAULT_SIZE, 249, Short.MAX_VALUE)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addComponent(fullNameRadioButton)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(extensionRadioButton, javax.swing.GroupLayout.PREFERRED_SIZE, 98, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(nameRegexCheckbox)))
|
||||
.addGap(1, 1, 1))))
|
||||
.addComponent(jLabel5)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(jLabel5)
|
||||
.addComponent(nameCheck, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, 95, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(jLabel1))
|
||||
.addGap(16, 16, 16)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addComponent(jLabel1)
|
||||
.addGap(65, 65, 65)
|
||||
.addComponent(filesRadioButton)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(dirsRadioButton)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(allRadioButton)))
|
||||
.addGap(0, 0, Short.MAX_VALUE))))
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addComponent(nameCheck)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(nameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 249, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addComponent(pathCheck)
|
||||
.addGap(4, 4, 4)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addComponent(pathRegexCheckBox)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(pathSeparatorInfoLabel))
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
|
||||
.addGap(0, 0, Short.MAX_VALUE)
|
||||
.addComponent(pathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 250, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addComponent(fullNameRadioButton)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(extensionRadioButton, javax.swing.GroupLayout.PREFERRED_SIZE, 114, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(nameRegexCheckbox)
|
||||
.addGap(0, 0, Short.MAX_VALUE))))
|
||||
.addComponent(allRadioButton))
|
||||
.addComponent(nameTextField)))))
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(pathCheck)
|
||||
.addComponent(mimeCheck)
|
||||
.addComponent(fileSizeCheck))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addComponent(equalitySymbolComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 36, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addGap(18, 18, 18)
|
||||
.addComponent(fileSizeSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, 94, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addGap(18, 18, 18)
|
||||
.addComponent(fileSizeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 82, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addComponent(mimeTypeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 250, javax.swing.GroupLayout.PREFERRED_SIZE))))
|
||||
.addComponent(fileSizeCheck)
|
||||
.addComponent(dateCheck))
|
||||
.addGap(0, 0, Short.MAX_VALUE)))
|
||||
.addContainerGap())
|
||||
);
|
||||
layout.setVerticalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addComponent(jLabel5)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(jLabel1)
|
||||
.addComponent(filesRadioButton)
|
||||
.addComponent(dirsRadioButton)
|
||||
.addComponent(allRadioButton))
|
||||
.addGap(5, 5, 5)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(nameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(nameCheck))
|
||||
.addGap(3, 3, 3)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addComponent(jLabel1)
|
||||
.addGap(10, 10, 10)
|
||||
.addComponent(nameCheck))
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(filesRadioButton)
|
||||
.addComponent(dirsRadioButton)
|
||||
.addComponent(allRadioButton))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||
.addComponent(nameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE)))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(fullNameRadioButton)
|
||||
@ -777,7 +847,7 @@ final class FilesSetRulePanel extends javax.swing.JPanel {
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(pathRegexCheckBox)
|
||||
.addComponent(pathSeparatorInfoLabel))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 8, Short.MAX_VALUE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(mimeTypeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(mimeCheck))
|
||||
@ -787,7 +857,12 @@ final class FilesSetRulePanel extends javax.swing.JPanel {
|
||||
.addComponent(fileSizeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(fileSizeSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(fileSizeCheck))
|
||||
.addGap(15, 15, 15)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(daysIncludedTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(daysIncludedLabel)
|
||||
.addComponent(dateCheck))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(ruleNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(ruleNameLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
|
||||
@ -795,10 +870,6 @@ final class FilesSetRulePanel extends javax.swing.JPanel {
|
||||
);
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
private void ruleNameTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_ruleNameTextFieldActionPerformed
|
||||
// TODO add your handling code here:
|
||||
}//GEN-LAST:event_ruleNameTextFieldActionPerformed
|
||||
|
||||
private void nameCheckActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nameCheckActionPerformed
|
||||
if (!this.nameCheck.isSelected()) {
|
||||
this.nameTextField.setEnabled(false);
|
||||
@ -831,15 +902,30 @@ final class FilesSetRulePanel extends javax.swing.JPanel {
|
||||
this.setOkButton();
|
||||
}//GEN-LAST:event_pathCheckActionPerformed
|
||||
|
||||
private void mimeCheckActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_mimeCheckActionPerformed
|
||||
if (!this.mimeCheck.isSelected()) {
|
||||
this.mimeTypeComboBox.setEnabled(false);
|
||||
this.mimeTypeComboBox.setSelectedIndex(0);
|
||||
private void filesRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_filesRadioButtonActionPerformed
|
||||
|
||||
this.setComponentsForSearchType();
|
||||
}//GEN-LAST:event_filesRadioButtonActionPerformed
|
||||
|
||||
private void dirsRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_dirsRadioButtonActionPerformed
|
||||
this.setComponentsForSearchType();
|
||||
}//GEN-LAST:event_dirsRadioButtonActionPerformed
|
||||
|
||||
private void allRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_allRadioButtonActionPerformed
|
||||
this.setComponentsForSearchType();
|
||||
}//GEN-LAST:event_allRadioButtonActionPerformed
|
||||
|
||||
private void dateCheckActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_dateCheckActionPerformed
|
||||
if (!this.dateCheck.isSelected()) {
|
||||
this.daysIncludedTextField.setEnabled(false);
|
||||
this.daysIncludedLabel.setEnabled(false);
|
||||
this.daysIncludedTextField.setText("");
|
||||
} else {
|
||||
this.mimeTypeComboBox.setEnabled(true);
|
||||
this.daysIncludedTextField.setEnabled(true);
|
||||
this.daysIncludedLabel.setEnabled(true);
|
||||
}
|
||||
this.setOkButton();
|
||||
}//GEN-LAST:event_mimeCheckActionPerformed
|
||||
}//GEN-LAST:event_dateCheckActionPerformed
|
||||
|
||||
private void fileSizeCheckActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_fileSizeCheckActionPerformed
|
||||
if (!this.fileSizeCheck.isSelected()) {
|
||||
@ -855,21 +941,21 @@ final class FilesSetRulePanel extends javax.swing.JPanel {
|
||||
this.setOkButton();
|
||||
}//GEN-LAST:event_fileSizeCheckActionPerformed
|
||||
|
||||
private void filesRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_filesRadioButtonActionPerformed
|
||||
|
||||
this.setComponentsForSearchType();
|
||||
}//GEN-LAST:event_filesRadioButtonActionPerformed
|
||||
|
||||
private void dirsRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_dirsRadioButtonActionPerformed
|
||||
this.setComponentsForSearchType();
|
||||
}//GEN-LAST:event_dirsRadioButtonActionPerformed
|
||||
|
||||
private void allRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_allRadioButtonActionPerformed
|
||||
this.setComponentsForSearchType();
|
||||
}//GEN-LAST:event_allRadioButtonActionPerformed
|
||||
private void mimeCheckActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_mimeCheckActionPerformed
|
||||
if (!this.mimeCheck.isSelected()) {
|
||||
this.mimeTypeComboBox.setEnabled(false);
|
||||
this.mimeTypeComboBox.setSelectedIndex(0);
|
||||
} else {
|
||||
this.mimeTypeComboBox.setEnabled(true);
|
||||
}
|
||||
this.setOkButton();
|
||||
}//GEN-LAST:event_mimeCheckActionPerformed
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JRadioButton allRadioButton;
|
||||
private javax.swing.JCheckBox dateCheck;
|
||||
private javax.swing.JLabel daysIncludedLabel;
|
||||
private javax.swing.JTextField daysIncludedTextField;
|
||||
private javax.swing.JRadioButton dirsRadioButton;
|
||||
private javax.swing.JComboBox<String> equalitySymbolComboBox;
|
||||
private javax.swing.JRadioButton extensionRadioButton;
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2014 Basis Technology Corp.
|
||||
* Copyright 2014-2018 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -52,7 +52,7 @@ public final class FilesSetsManager extends Observable {
|
||||
{
|
||||
put(Bundle.FilesSetsManager_allFilesAndDirectories(),
|
||||
new Rule(Bundle.FilesSetsManager_allFilesAndDirectories(), null,
|
||||
new MetaTypeCondition(MetaTypeCondition.Type.ALL), null, null, null));
|
||||
new MetaTypeCondition(MetaTypeCondition.Type.ALL), null, null, null, null));
|
||||
}
|
||||
});
|
||||
private static final FilesSet FILES_DIRS_UNALLOC_INGEST_FILTER = new FilesSet(
|
||||
@ -61,7 +61,7 @@ public final class FilesSetsManager extends Observable {
|
||||
{
|
||||
put(Bundle.FilesSetsManager_allFilesDirectoriesAndUnallocated(),
|
||||
new Rule(Bundle.FilesSetsManager_allFilesDirectoriesAndUnallocated(), null,
|
||||
new MetaTypeCondition(MetaTypeCondition.Type.ALL), null, null, null));
|
||||
new MetaTypeCondition(MetaTypeCondition.Type.ALL), null, null, null, null));
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2017 Basis Technology Corp.
|
||||
* Copyright 2011-2018 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -44,6 +44,7 @@ import org.sleuthkit.autopsy.modules.interestingitems.FilesSet.Rule.FileSizeCond
|
||||
import org.sleuthkit.autopsy.modules.interestingitems.FilesSet.Rule.MetaTypeCondition;
|
||||
import org.sleuthkit.autopsy.modules.interestingitems.FilesSet.Rule.MimeTypeCondition;
|
||||
import org.sleuthkit.autopsy.modules.interestingitems.FilesSet.Rule.ParentPathCondition;
|
||||
import org.sleuthkit.autopsy.modules.interestingitems.FilesSet.Rule.DateCondition;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.NodeList;
|
||||
@ -67,6 +68,7 @@ class InterestingItemsFilesSetSettings implements Serializable {
|
||||
private static final String FILE_SET_TAG = "INTERESTING_FILE_SET"; //NON-NLS
|
||||
private static final String NAME_RULE_TAG = "NAME"; //NON-NLS
|
||||
private static final String NAME_ATTR = "name"; //NON-NLS
|
||||
private static final String DAYS_INCLUDED_ATTR = "daysIncluded";
|
||||
private static final String MIME_ATTR = "mimeType";
|
||||
private static final String FS_COMPARATOR_ATTR = "comparatorSymbol";
|
||||
private static final String FS_SIZE_ATTR = "sizeValue";
|
||||
@ -166,6 +168,35 @@ class InterestingItemsFilesSetSettings implements Serializable {
|
||||
return pathCondition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a date condition for a FilesSet membership rule from data in an
|
||||
* XML element.
|
||||
*
|
||||
* @param ruleElement The XML element.
|
||||
*
|
||||
* @return The date condition, or null if there is an error (logged).
|
||||
*
|
||||
* @throws
|
||||
* org.sleuthkit.autopsy.modules.interestingitems.FilesSetsManager.FilesSetsManagerException
|
||||
*/
|
||||
private static DateCondition readDateCondition(Element ruleElement) throws FilesSetsManager.FilesSetsManagerException {
|
||||
// Read in the optional path condition. Null is o.k., but if the attribute
|
||||
// is there, be sure it is not malformed.
|
||||
DateCondition dateCondition = null;
|
||||
if (!ruleElement.getAttribute(DAYS_INCLUDED_ATTR).isEmpty()) {
|
||||
String daysIncluded = ruleElement.getAttribute(DAYS_INCLUDED_ATTR);
|
||||
if (!daysIncluded.isEmpty()) {
|
||||
try {
|
||||
dateCondition = new DateCondition(Integer.parseInt(daysIncluded));
|
||||
} catch (NumberFormatException ex) {
|
||||
logger.log(Level.SEVERE, "Error creating condition for " + daysIncluded + ", ignoring malformed date condition definition", ex); // NON-NLS
|
||||
throw new FilesSetsManager.FilesSetsManagerException(String.format("error compiling %s regex", DAYS_INCLUDED_ATTR), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
return dateCondition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to compile a regular expression.
|
||||
*
|
||||
@ -183,12 +214,13 @@ class InterestingItemsFilesSetSettings implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a fileset membership rule from the data in an xml element for
|
||||
* Construct a fileset membership rule from the data in an xml element for
|
||||
* use in a FilesSet.
|
||||
*
|
||||
* @param elem The XML element.
|
||||
*
|
||||
* @return A file set constructed from the conditions available in the XML element
|
||||
* @return A file set constructed from the conditions available in the XML
|
||||
* element
|
||||
*
|
||||
* @throws
|
||||
* org.sleuthkit.autopsy.modules.interestingitems.FilesSetsManager.FilesSetsManagerException
|
||||
@ -200,17 +232,17 @@ class InterestingItemsFilesSetSettings implements Serializable {
|
||||
ParentPathCondition pathCondition = readPathCondition(elem);
|
||||
MimeTypeCondition mimeCondition = readMimeCondition(elem);
|
||||
FileSizeCondition sizeCondition = readSizeCondition(elem);
|
||||
//if meta type condition or all four types of conditions the user can create are all null then don't make the rule
|
||||
if (metaCondition == null || (nameCondition == null && pathCondition == null && mimeCondition == null && sizeCondition == null)) {
|
||||
DateCondition dateCondition = readDateCondition(elem); //if meta type condition or all four types of conditions the user can create are all null then don't make the rule
|
||||
if (metaCondition == null || (nameCondition == null && pathCondition == null && mimeCondition == null && sizeCondition == null && dateCondition == null)) {
|
||||
logger.log(Level.WARNING, "Error Reading Rule, " + ruleName + " was either missing a meta condition or contained only a meta condition. No rule was imported."); // NON-NLS
|
||||
throw new FilesSetsManager.FilesSetsManagerException(String.format("Invalid Rule in FilesSet xml, missing necessary conditions for %s", ruleName));
|
||||
}
|
||||
return new FilesSet.Rule(ruleName, nameCondition, metaCondition, pathCondition, mimeCondition, sizeCondition);
|
||||
return new FilesSet.Rule(ruleName, nameCondition, metaCondition, pathCondition, mimeCondition, sizeCondition, dateCondition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a file name condition for a FilesSet membership rule from data in an
|
||||
* XML element.
|
||||
* Construct a file name condition for a FilesSet membership rule from data
|
||||
* in an XML element.
|
||||
*
|
||||
* @param ruleElement The XML element.
|
||||
*
|
||||
@ -256,8 +288,8 @@ class InterestingItemsFilesSetSettings implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a MIME type condition for a FilesSet membership rule from data in an
|
||||
* XML element.
|
||||
* Construct a MIME type condition for a FilesSet membership rule from data
|
||||
* in an XML element.
|
||||
*
|
||||
* @param ruleElement The XML element.
|
||||
*
|
||||
@ -275,8 +307,8 @@ class InterestingItemsFilesSetSettings implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a file size condition for a FilesSet membership rule from data in an
|
||||
* XML element.
|
||||
* Construct a file size condition for a FilesSet membership rule from data
|
||||
* in an XML element.
|
||||
*
|
||||
* @param ruleElement The XML element.
|
||||
*
|
||||
@ -544,6 +576,13 @@ class InterestingItemsFilesSetSettings implements Serializable {
|
||||
ruleElement.setAttribute(FS_SIZE_ATTR, Integer.toString(sizeCondition.getSizeValue()));
|
||||
ruleElement.setAttribute(FS_UNITS_ATTR, sizeCondition.getUnit().getName());
|
||||
}
|
||||
|
||||
//Add the optional date condition
|
||||
DateCondition dateCondition = rule.getDateCondition();
|
||||
if (dateCondition != null) {
|
||||
ruleElement.setAttribute(DAYS_INCLUDED_ATTR, Integer.toString(dateCondition.getDaysIncluded()));
|
||||
}
|
||||
|
||||
setElement.appendChild(ruleElement);
|
||||
}
|
||||
rootElement.appendChild(setElement);
|
||||
|
@ -37,6 +37,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.logging.Level;
|
||||
import org.openide.modules.InstalledFileLocator;
|
||||
import org.openide.util.Exceptions;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.coreutils.ExecUtil;
|
||||
@ -78,6 +79,7 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
|
||||
|
||||
private static final String PHOTOREC_DIRECTORY = "photorec_exec"; //NON-NLS
|
||||
private static final String PHOTOREC_EXECUTABLE = "photorec_win.exe"; //NON-NLS
|
||||
private static final String PHOTOREC_LINUX_EXECUTABLE = "photorec";
|
||||
private static final String PHOTOREC_RESULTS_BASE = "results"; //NON-NLS
|
||||
private static final String PHOTOREC_RESULTS_EXTENDED = "results.1"; //NON-NLS
|
||||
private static final String PHOTOREC_REPORT = "report.xml"; //NON-NLS
|
||||
@ -136,8 +138,8 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
|
||||
|
||||
this.rootOutputDirPath = createModuleOutputDirectoryForCase();
|
||||
|
||||
Path execName = Paths.get(PHOTOREC_DIRECTORY, PHOTOREC_EXECUTABLE);
|
||||
executableFile = locateExecutable(execName.toString());
|
||||
//Set photorec executable directory based on operating system.
|
||||
executableFile = locateExecutable();
|
||||
|
||||
if (PhotoRecCarverFileIngestModule.refCounter.incrementAndGet(this.jobId) == 1) {
|
||||
try {
|
||||
@ -222,13 +224,13 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
|
||||
|
||||
// Scan the file with Unallocated Carver.
|
||||
ProcessBuilder processAndSettings = new ProcessBuilder(
|
||||
"\"" + executableFile + "\"",
|
||||
executableFile.toString(),
|
||||
"/d", // NON-NLS
|
||||
"\"" + outputDirPath.toAbsolutePath() + File.separator + PHOTOREC_RESULTS_BASE + "\"",
|
||||
outputDirPath.toAbsolutePath().toString() + File.separator + PHOTOREC_RESULTS_BASE,
|
||||
"/cmd", // NON-NLS
|
||||
"\"" + tempFilePath.toFile() + "\"",
|
||||
tempFilePath.toFile().toString(),
|
||||
"search"); // NON-NLS
|
||||
|
||||
|
||||
// Add environment variable to force PhotoRec to run with the same permissions Autopsy uses
|
||||
processAndSettings.environment().put("__COMPAT_LAYER", "RunAsInvoker"); //NON-NLS
|
||||
processAndSettings.redirectErrorStream(true);
|
||||
@ -435,17 +437,32 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
|
||||
*
|
||||
* @throws IngestModuleException
|
||||
*/
|
||||
public static File locateExecutable(String executableToFindName) throws IngestModule.IngestModuleException {
|
||||
// Must be running under a Windows operating system.
|
||||
if (!PlatformUtil.isWindowsOS()) {
|
||||
throw new IngestModule.IngestModuleException(Bundle.unsupportedOS_message());
|
||||
public static File locateExecutable() throws IngestModule.IngestModuleException {
|
||||
File exeFile = null;
|
||||
Path execName = null;
|
||||
String photorec_linux_directory = "/usr/bin";
|
||||
if (PlatformUtil.isWindowsOS()) {
|
||||
execName = Paths.get(PHOTOREC_DIRECTORY, PHOTOREC_EXECUTABLE);
|
||||
exeFile = InstalledFileLocator.getDefault().locate(execName.toString(), PhotoRecCarverFileIngestModule.class.getPackage().getName(), false);
|
||||
} else {
|
||||
File usrBin = new File("/usr/bin/photorec");
|
||||
File usrLocalBin = new File("/usr/local/bin/photorec");
|
||||
if (usrBin.canExecute() && usrBin.exists() && !usrBin.isDirectory()) {
|
||||
photorec_linux_directory = "/usr/bin";
|
||||
}else if(usrLocalBin.canExecute() && usrLocalBin.exists() && !usrLocalBin.isDirectory()){
|
||||
photorec_linux_directory = "/usr/local/bin";
|
||||
}else{
|
||||
throw new IngestModule.IngestModuleException("Photorec not found");
|
||||
}
|
||||
execName = Paths.get(photorec_linux_directory, PHOTOREC_LINUX_EXECUTABLE);
|
||||
exeFile = new File(execName.toString());
|
||||
}
|
||||
|
||||
File exeFile = InstalledFileLocator.getDefault().locate(executableToFindName, PhotoRecCarverFileIngestModule.class.getPackage().getName(), false);
|
||||
if (null == exeFile) {
|
||||
throw new IngestModule.IngestModuleException(Bundle.missingExecutable_message());
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (!exeFile.canExecute()) {
|
||||
throw new IngestModule.IngestModuleException(Bundle.cannotRunExecutable_message());
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ public class AddTaggedHashesToHashDb implements GeneralReportModule {
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Adds hashes of tagged files to a hash database.";
|
||||
return "Adds hashes of tagged files to a hash set.";
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -92,17 +92,17 @@ public class AddTaggedHashesToHashDb implements GeneralReportModule {
|
||||
try {
|
||||
hashSet.addHashes(tag.getContent(), Case.getCurrentCase().getDisplayName());
|
||||
} catch (TskCoreException ex) {
|
||||
Logger.getLogger(AddTaggedHashesToHashDb.class.getName()).log(Level.SEVERE, "Error adding hash for obj_id = " + tag.getContent().getId() + " to hash database " + hashSet.getHashSetName(), ex);
|
||||
Logger.getLogger(AddTaggedHashesToHashDb.class.getName()).log(Level.SEVERE, "Error adding hash for obj_id = " + tag.getContent().getId() + " to hash set " + hashSet.getHashSetName(), ex);
|
||||
failedExports.add(tag.getContent().getName());
|
||||
}
|
||||
} else {
|
||||
JOptionPane.showMessageDialog(null, "Unable to add the " + (tags.size() > 1 ? "files" : "file") + " to the hash database. Hashes have not been calculated. Please configure and run an appropriate ingest module.", "Add to Hash Database Error", JOptionPane.ERROR_MESSAGE);
|
||||
JOptionPane.showMessageDialog(null, "Unable to add the " + (tags.size() > 1 ? "files" : "file") + " to the hash set. Hashes have not been calculated. Please configure and run an appropriate ingest module.", "Add to Hash Set Error", JOptionPane.ERROR_MESSAGE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
Logger.getLogger(AddTaggedHashesToHashDb.class.getName()).log(Level.SEVERE, "Error adding to hash database", ex);
|
||||
Logger.getLogger(AddTaggedHashesToHashDb.class.getName()).log(Level.SEVERE, "Error adding to hash set", ex);
|
||||
JOptionPane.showMessageDialog(null, "Error getting selected tags for case.", "Hash Export Error", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -876,7 +876,7 @@ public class SharedConfiguration {
|
||||
|
||||
if (!sharedDbPath.exists()) {
|
||||
if (!sharedDbPath.mkdirs()) {
|
||||
throw new SharedConfigurationException("Error creating shared hash database directory " + sharedDbPath.getAbsolutePath());
|
||||
throw new SharedConfigurationException("Error creating shared hash set directory " + sharedDbPath.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1010,7 +1010,7 @@ public class SharedConfiguration {
|
||||
|
||||
if (!localDb.getParentFile().exists()) {
|
||||
if (!localDb.getParentFile().mkdirs()) {
|
||||
throw new SharedConfigurationException("Error creating hash database directory " + localDb.getParentFile().getAbsolutePath());
|
||||
throw new SharedConfigurationException("Error creating hash set directory " + localDb.getParentFile().getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1025,7 +1025,7 @@ public class SharedConfiguration {
|
||||
break;
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
throw new SharedConfigurationException(String.format("Error getting hash database path info for %s", localDb.getParentFile().getAbsolutePath()), ex);
|
||||
throw new SharedConfigurationException(String.format("Error getting hash set path info for %s", localDb.getParentFile().getAbsolutePath()), ex);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1033,7 +1033,7 @@ public class SharedConfiguration {
|
||||
try {
|
||||
HashDbManager.getInstance().removeHashDatabase(matchingDb);
|
||||
} catch (HashDbManager.HashDbManagerException ex) {
|
||||
throw new SharedConfigurationException(String.format("Error updating hash database info for %s", localDb.getAbsolutePath()), ex);
|
||||
throw new SharedConfigurationException(String.format("Error updating hash set info for %s", localDb.getAbsolutePath()), ex);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1130,7 +1130,7 @@ public class SharedConfiguration {
|
||||
}
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
throw new SharedConfigurationException("Unable to read hash databases", ex);
|
||||
throw new SharedConfigurationException("Unable to read hash sets", ex);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2017 Basis Technology Corp.
|
||||
* Copyright 2017-2018 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -51,7 +51,7 @@ import org.sleuthkit.datamodel.TskData;
|
||||
*/
|
||||
public class DeleteTagAction extends Action {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(DeleteTagAction.class.getName());
|
||||
private static final Logger logger = Logger.getLogger(DeleteTagAction.class.getName());
|
||||
|
||||
private final ImageGalleryController controller;
|
||||
private final long fileId;
|
||||
@ -83,17 +83,11 @@ public class DeleteTagAction extends Action {
|
||||
protected Void doInBackground() throws Exception {
|
||||
DrawableTagsManager tagsManager = controller.getTagsManager();
|
||||
|
||||
// Pull the from the global context to avoid unnecessary calls
|
||||
// to the database.
|
||||
final Collection<AbstractFile> selectedFilesList
|
||||
= new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class));
|
||||
AbstractFile file = selectedFilesList.iterator().next();
|
||||
|
||||
try {
|
||||
LOGGER.log(Level.INFO, "Removing tag {0} from {1}", new Object[]{tagName.getDisplayName(), file.getName()}); //NON-NLS
|
||||
logger.log(Level.INFO, "Removing tag {0} from {1}", new Object[]{tagName.getDisplayName(), contentTag.getContent().getName()}); //NON-NLS
|
||||
tagsManager.deleteContentTag(contentTag);
|
||||
} catch (TskCoreException tskCoreException) {
|
||||
LOGGER.log(Level.SEVERE, "Error untagging file", tskCoreException); //NON-NLS
|
||||
logger.log(Level.SEVERE, "Error untagging file", tskCoreException); //NON-NLS
|
||||
Platform.runLater(()
|
||||
-> new Alert(Alert.AlertType.ERROR, Bundle.DeleteDrawableTagAction_deleteTag_alert(fileId)).show()
|
||||
);
|
||||
@ -107,7 +101,7 @@ public class DeleteTagAction extends Action {
|
||||
try {
|
||||
get();
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Unexpected exception while untagging file", ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, "Unexpected exception while untagging file", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
}.execute();
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2017 Basis Technology Corp.
|
||||
* Copyright 2011-2018 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -284,15 +284,6 @@ class AccountsText implements IndexedText {
|
||||
}
|
||||
|
||||
@Override
|
||||
@NbBundle.Messages({"AccountsText.getMarkup.noMatchMsg="
|
||||
+ "<html><pre><span style\\\\='background\\\\:yellow'>There were no keyword hits on this page. <br />"
|
||||
+ "The keyword could have been in the file name."
|
||||
+ " <br />Advance to another page if present, or to view the original text, choose File Text"
|
||||
+ " <br />in the drop down menu to the right...</span></pre></html>",
|
||||
"AccountsText.getMarkup.queryFailedMsg="
|
||||
+ "<html><pre><span style\\\\='background\\\\:yellow'>Failed to retrieve keyword hit results."
|
||||
+ " <br />Confirm that Autopsy can connect to the Solr server. "
|
||||
+ "<br /></span></pre></html>"})
|
||||
public String getText() {
|
||||
try {
|
||||
loadPageInfo(); //inits once
|
||||
@ -321,7 +312,7 @@ class AccountsText implements IndexedText {
|
||||
return "<html><pre>" + highlightedText + "</pre></html>"; //NON-NLS
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Error getting highlighted text for Solr doc id " + this.solrObjectId + ", chunkID " + this.currentPage, ex); //NON-NLS
|
||||
return Bundle.AccountsText_getMarkup_queryFailedMsg();
|
||||
return Bundle.IndexedText_errorMessage_errorGettingText();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,11 +54,6 @@ AbstractKeywordSearchPerformer.search.noFilesInIdxMsg=<html>No files are in inde
|
||||
AbstractKeywordSearchPerformer.search.noFilesIdxdMsg=<html>No files were indexed.<br />Re-ingest the image with the Keyword Search Module enabled. </html>
|
||||
ExtractedContentViewer.toolTip=Displays extracted text from files and keyword-search results. Requires Keyword Search ingest to be run on a file to activate this viewer.
|
||||
ExtractedContentViewer.getTitle=Indexed Text
|
||||
ExtractedContentViewer.getSolrContent.knownFileMsg=<p style\=''font-style\:italic''>{0} is a known file (based on MD5 hash) and does not have text in the index.</p>
|
||||
ExtractedContentViewer.getSolrContent.noTxtYetMsg=<p style\=''font-style\:italic''>{0} does not have text in the index.<br/>It may have no text, not been analyzed yet, or keyword search was not enabled during ingest.</p>
|
||||
ExtractedContentViewer.getSolrContent.txtBodyItal=<span style\=''font-style\:italic''>{0}</span>
|
||||
HighlightedMatchesSource.getMarkup.noMatchMsg=<span>Failed to retrieve indexed text for keyword hit. Advance to another page if present, or to view the original text, choose File Text in the drop down menu to the right. Alternatively, you may choose to extract file content and search for the hit using an external application (e.g. a text editor).</span>
|
||||
HighlightedMatchesSource.getMarkup.queryFailedMsg=<html><pre><span style\\\\\='background\\\\\:yellow'>Failed to retrieve keyword hit results. <br />Confirm that Autopsy can connect to the Solr server. <br /></span></pre></html>
|
||||
HighlightedMatchesSource.toString=Search Results
|
||||
Installer.reportPortError=Indexing server port {0} is not available. Check if your security software does not block {1} and consider changing {2} in {3} property file in the application user folder. Then try rebooting your system if another process was causing the conflict.
|
||||
Installer.reportStopPortError=Indexing server stop port {0} is not available. Consider changing {1} in {2} property file in the application user folder.
|
||||
@ -229,7 +224,7 @@ KeywordSearchGlobalSearchSettingsPanel.timeRadioButton1.text=20 minutes (slowest
|
||||
KeywordSearchGlobalSearchSettingsPanel.timeRadioButton2.toolTipText=10 minutes (faster overall ingest time than default)
|
||||
KeywordSearchGlobalSearchSettingsPanel.timeRadioButton2.text=10 minutes (slower feedback, faster ingest)
|
||||
KeywordSearchGlobalSearchSettingsPanel.frequencyLabel.text=Results update frequency during ingest:
|
||||
KeywordSearchGlobalSearchSettingsPanel.skipNSRLCheckBox.toolTipText=Requires Hash DB service to had run previously, or be selected for next ingest.
|
||||
KeywordSearchGlobalSearchSettingsPanel.skipNSRLCheckBox.toolTipText=Requires Hash Set service to had run previously, or be selected for next ingest.
|
||||
KeywordSearchGlobalSearchSettingsPanel.skipNSRLCheckBox.text=Do not add files in NSRL (known files) to keyword index during ingest
|
||||
KeywordSearchGlobalSearchSettingsPanel.informationLabel.text=Information
|
||||
KeywordSearchGlobalSearchSettingsPanel.settingsLabel.text=Settings
|
||||
@ -272,7 +267,6 @@ KeywordSearchGlobalSearchSettingsPanel.timeRadioButton5.text=No periodic searche
|
||||
SolrConnectionCheck.HostnameOrPort=Invalid hostname and/or port number.
|
||||
SolrConnectionCheck.Hostname=Invalid hostname.
|
||||
SolrConnectionCheck.MissingHostname=Missing hostname.
|
||||
RawText.getText.error.msg=Error getting text
|
||||
GlobalListsManagementPanel.newListButton.text=New List
|
||||
GlobalListsManagementPanel.importButton.text=Import List
|
||||
GlobalListsManagementPanel.keywordListsLabel.text=Keyword Lists:
|
||||
|
@ -46,7 +46,6 @@ AbstractKeywordSearchPerformer.search.noFilesIdxdMsg=<html>\u30a4\u30f3\u30c7\u3
|
||||
ExtractedContentPanel.setMarkup.panelTxt=<span style\='font-style\:italic'>\u30c6\u30ad\u30b9\u30c8\u30ed\u30fc\u30c9\u4e2d...\u3057\u3070\u3089\u304f\u304a\u5f85\u3061\u304f\u3060\u3055\u3044\u3002</span>
|
||||
ExtractedContentViewer.toolTip=\u30d5\u30a1\u30a4\u30eb\u3084\u30ad\u30fc\u30ef\u30fc\u30c9\u691c\u7d22\u7d50\u679c\u304b\u3089\u62bd\u51fa\u3055\u308c\u305f\u30c6\u30ad\u30b9\u30c8\u3092\u8868\u793a\u3002\u3053\u306e\u30d3\u30e5\u30fc\u30a2\u3092\u6709\u52b9\u5316\u3059\u308b\u306b\u306f\u3001\u30d5\u30a1\u30a4\u30eb\u306b\u5bfe\u3057\u3066\u30ad\u30fc\u30ef\u30fc\u30c9\u691c\u7d22\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u3092\u5b9f\u884c\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002
|
||||
ExtractedContentViewer.getTitle=\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u5316\u3055\u308c\u305f\u30c6\u30ad\u30b9\u30c8
|
||||
ExtractedContentViewer.getSolrContent.knownFileMsg=<p style\=''font-style\:italic''>{0}\u306f\u65e2\u77e5\u30d5\u30a1\u30a4\u30eb\u3067\u3059\uff08MDS\u30cf\u30c3\u30b7\u30e5\u306b\u57fa\u3065\u304f\u3068\uff09\u3002\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u306b\u30c6\u30ad\u30b9\u30c8\u304c\u3042\u308a\u307e\u305b\u3093\u3002</p>
|
||||
ExtractedContentViewer.getSolrContent.noTxtYetMsg=<p style\=''font-style\:italic''>{0}\u306e\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u306b\u30c6\u30ad\u30b9\u30c8\u304c\u3042\u308a\u307e\u305b\u3093\u3002<br/>\u30c6\u30ad\u30b9\u30c8\u304c\u7121\u3044\u304b\u3001\u307e\u3060\u89e3\u6790\u3055\u308c\u3066\u3044\u306a\u3044\u304b\u3001\u30ad\u30fc\u30ef\u30fc\u30c9\u691c\u7d22\u304c\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u4e2d\u306b\u6709\u52b9\u5316\u3055\u308c\u3066\u3044\u306a\u304b\u3063\u305f\u304b\u3082\u3057\u308c\u307e\u305b\u3093\u3002</p>
|
||||
HighlightedMatchesSource.toString=\u691c\u7d22\u7d50\u679c
|
||||
Installer.reportPortError=\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u30b5\u30fc\u30d0\u30fc\u30dd\u30fc\u30c8 {0} \u306f\u5229\u7528\u3067\u304d\u307e\u305b\u3093\u3002\u4f7f\u7528\u3057\u3066\u3044\u308b\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u30bd\u30d5\u30c8\u30a6\u30a7\u30a2\u304c {1} \u3092\u30d6\u30ed\u30c3\u30af\u3057\u3066\u3044\u306a\u3044\u304b\u78ba\u8a8d\u3057\u3001\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30e6\u30fc\u30b6\u30fc\u30d5\u30a9\u30eb\u30c0\u30fc\u5185\u306e{3}\u30d7\u30ed\u30d1\u30c6\u30a3\u30d5\u30a1\u30a4\u30eb\u306e{2}\u3092\u5909\u66f4\u3059\u308b\u691c\u8a0e\u3092\u3057\u3066\u304f\u3060\u3055\u3044\u3002\u3082\u3057\u4ed6\u306e\u51e6\u7406\u304c\u554f\u984c\u306e\u539f\u56e0\u3067\u3042\u308c\u3070\u3001\u30b7\u30b9\u30c6\u30e0\u3092\u518d\u8d77\u52d5\u3057\u3066\u4e0b\u3055\u3044\u3002
|
||||
@ -200,7 +199,6 @@ KeywordSearchIngestModule.doInBackGround.finalizeMsg=- \u6700\u7d42\u51e6\u7406\
|
||||
KeywordSearchIngestModule.doInBackGround.pendingMsg=\uff08\u30da\u30f3\u30c7\u30a3\u30f3\u30b0\uff09
|
||||
SearchRunner.doInBackGround.cancelMsg=\uff08\u30ad\u30e3\u30f3\u30bb\u30eb\u4e2d\u2026\uff09
|
||||
Server.addDoc.exception.msg2=\u30a2\u30c3\u30d7\u30c7\u30fc\u30c8\u30cf\u30f3\u30c9\u30e9\u30fc\u3092\u4f7f\u7528\u3057\u307e\u3057\u305f\u304c\u3001\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u306b\u6b21\u306e\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u3092\u8ffd\u52a0\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\uff1a{0}
|
||||
ExtractedContentViewer.getSolrContent.txtBodyItal=<span style\=''font-style\:italic''>{0}</span>
|
||||
KeywordSearchJobSettingsPanel.keywordSearchEncodings.text=-
|
||||
KeywordSearchJobSettingsPanel.languagesValLabel.text=-
|
||||
KeywordSearchJobSettingsPanel.encodingsLabel.text=\u30a8\u30f3\u30b3\u30fc\u30c7\u30a3\u30f3\u30b0\uff1a
|
||||
@ -261,7 +259,6 @@ KeywordSearchModuleFactory.getIngestJobSettingsPanel.exception.msg=\u8a2d\u5b9a\
|
||||
SearchRunner.Searcher.done.err.msg=\u30ad\u30fc\u30ef\u30fc\u30c9\u691c\u7d22\u3092\u5b9f\u884c\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f
|
||||
KeywordSearchGlobalSearchSettingsPanel.timeRadioButton5.text=\u5b9a\u671f\u7684\u691c\u7d22\u7121\u3057
|
||||
KeywordSearchGlobalSearchSettingsPanel.timeRadioButton5.toolTipText=\u5168\u4f53\u7684\u306b\u4e00\u756a\u901f\u3044\u3067\u3059\u304c\u3001\u51e6\u7406\u304c\u5b8c\u4e86\u3059\u308b\u307e\u3067\u7d50\u679c\u306f\u8868\u793a\u3055\u308c\u307e\u305b\u3093
|
||||
HighlightedMatchesSource.getMarkup.queryFailedMsg=<html><pre><span style\\\\\='background\\\\\:yellow'>\u30ad\u30fc\u30ef\u30fc\u30c9\u306b\u30d2\u30c3\u30c8\u3057\u305f\u7d50\u679c\u3092\u53d6\u5f97\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002<br />Autopsy\u304cSolr\u30b5\u30fc\u30d0\u30fc\u306b\u63a5\u7d9a\u3067\u304d\u308b\u3053\u3068\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002<br /></span></pre></html>\
|
||||
KeywordSearch.openCore.notification.msg=\u30ad\u30fc\u30ef\u30fc\u30c9\u691c\u7d22\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u3092\u958b\u3051\u307e\u305b\u3093\u3067\u3057\u305f
|
||||
KeywordSearch.closeCore.notification.msg=\u30ad\u30fc\u30ef\u30fc\u30c9\u691c\u7d22\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u3092\u9589\u3058\u308b\u969b\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f
|
||||
KeywordSearchListsManagementPanel.fileExtensionFilterLb2=\u30ad\u30fc\u30ef\u30fc\u30c9\u30ea\u30b9\u30c8\u30d5\u30a1\u30a4\u30eb\u3092\u30a8\u30f3\u30b1\u30fc\u30b9\u3059\u308b(txt)
|
||||
@ -272,7 +269,7 @@ SolrConnectionCheck.HostnameOrPort=hostname\u3084\u30dd\u30fc\u30c8\u756a\u53f7\
|
||||
SolrConnectionCheck.Hostname=hostname\u304c\u7121\u52b9\u3067\u3059\u3002
|
||||
SolrConnectionCheck.Port=\u30dd\u30fc\u30c8\u756a\u53f7\u304c\u7121\u52b9\u3067\u3059\u3002
|
||||
SolrConnectionCheck.MissingHostname=hostname\u304c\u6b20\u3051\u3066\u307e\u3059\u3002
|
||||
RawText.getText.error.msg=\u30c6\u30ad\u30b9\u30c8\u3092\u53d6\u5f97\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f
|
||||
ExtractedContentViewer.getText.error.msg=\u30c6\u30ad\u30b9\u30c8\u3092\u53d6\u5f97\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f
|
||||
GlobalListsManagementPanel.exportButton.text=\u30ea\u30b9\u30c8\u3092\u30a8\u30af\u30b9\u30dd\u30fc\u30c8
|
||||
GlobalListsManagementPanel.deleteListButton.text=\u30ea\u30b9\u30c8\u3092\u524a\u9664
|
||||
GlobalListsManagementPanel.copyListButton.text=\u30ea\u30b9\u30c8\u3092\u30b3\u30d4\u30fc
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-17 Basis Technology Corp.
|
||||
* Copyright 2011-2018 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -24,6 +24,7 @@ import java.awt.event.ActionListener;
|
||||
import java.awt.event.ItemEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.logging.Level;
|
||||
import javax.swing.JTextPane;
|
||||
@ -647,8 +648,6 @@ class ExtractedContentPanel extends javax.swing.JPanel {
|
||||
return source.getText();
|
||||
}
|
||||
|
||||
@NbBundle.Messages({
|
||||
"ExtractedContentPanel.SetMarkup.error=There was an error getting the text for the selected source."})
|
||||
@Override
|
||||
protected void done() {
|
||||
super.done();
|
||||
@ -663,11 +662,9 @@ class ExtractedContentPanel extends javax.swing.JPanel {
|
||||
setPanelText("", false);
|
||||
}
|
||||
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
} catch (InterruptedException | CancellationException | ExecutionException ex) {
|
||||
logger.log(Level.SEVERE, "Error getting marked up text", ex); //NON-NLS
|
||||
setPanelText(Bundle.ExtractedContentPanel_SetMarkup_error(), true);
|
||||
} // catch and ignore if we were cancelled
|
||||
catch (java.util.concurrent.CancellationException ex) {
|
||||
setPanelText(Bundle.IndexedText_errorMessage_errorGettingText(), true);
|
||||
}
|
||||
|
||||
updateControls(source);
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2017 Basis Technology Corp.
|
||||
* Copyright 2011-2018 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -33,6 +33,7 @@ import org.openide.util.lookup.ServiceProvider;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.keywordsearch.KeywordSearchResultFactory.AdHocQueryResult;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.Account;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
@ -52,7 +53,6 @@ public class ExtractedContentViewer implements DataContentViewer {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(ExtractedContentViewer.class.getName());
|
||||
|
||||
private static final long INVALID_DOCUMENT_ID = 0L;
|
||||
private static final BlackboardAttribute.Type TSK_ASSOCIATED_ARTIFACT_TYPE = new BlackboardAttribute.Type(TSK_ASSOCIATED_ARTIFACT);
|
||||
public static final BlackboardAttribute.Type TSK_ACCOUNT_TYPE = new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE);
|
||||
|
||||
@ -92,54 +92,77 @@ public class ExtractedContentViewer implements DataContentViewer {
|
||||
currentNode = node;
|
||||
}
|
||||
|
||||
Lookup nodeLookup = node.getLookup();
|
||||
AbstractFile content = nodeLookup.lookup(AbstractFile.class);
|
||||
|
||||
/*
|
||||
* Assemble a collection of all of the indexed text "sources" for the
|
||||
* node.
|
||||
*/
|
||||
List<IndexedText> sources = new ArrayList<>();
|
||||
Lookup nodeLookup = node.getLookup();
|
||||
|
||||
AdHocQueryResult adHocQueryResult = nodeLookup.lookup(AdHocQueryResult.class);
|
||||
AbstractFile file = null;
|
||||
BlackboardArtifact artifact;
|
||||
|
||||
/*
|
||||
* If we have an ad hoc query result, pull the file and artifact objects
|
||||
* from that. Otherwise, pull them from the lookup.
|
||||
*/
|
||||
if (adHocQueryResult != null) {
|
||||
artifact = adHocQueryResult.getArtifact();
|
||||
Content content = adHocQueryResult.getContent();
|
||||
if (content instanceof AbstractFile) {
|
||||
file = (AbstractFile) content;
|
||||
}
|
||||
} else {
|
||||
artifact = nodeLookup.lookup(BlackboardArtifact.class);
|
||||
file = nodeLookup.lookup(AbstractFile.class);
|
||||
}
|
||||
|
||||
/*
|
||||
* First, get text with highlighted hits if this node is for a search
|
||||
* result.
|
||||
*/
|
||||
IndexedText highlightedHitText = null;
|
||||
IndexedText rawContentText = null;
|
||||
|
||||
if (null != content && solrHasContent(content.getId())) {
|
||||
QueryResults hits = nodeLookup.lookup(QueryResults.class);
|
||||
BlackboardArtifact artifact = nodeLookup.lookup(BlackboardArtifact.class);
|
||||
if (hits != null) {
|
||||
if (adHocQueryResult != null) {
|
||||
/*
|
||||
* The node is an ad hoc search result node.
|
||||
*/
|
||||
highlightedHitText = new HighlightedText(adHocQueryResult.getSolrObjectId(), adHocQueryResult.getResults());
|
||||
} else if (artifact != null) {
|
||||
if (artifact.getArtifactTypeID() == TSK_KEYWORD_HIT.getTypeID()) {
|
||||
/*
|
||||
* if there is a QueryReslt object, in the lookup use that. This
|
||||
* happens when a user selects a row in an ad-hoc search result
|
||||
* The node is a keyword hit artifact node.
|
||||
*/
|
||||
highlightedHitText = new HighlightedText(content.getId(), hits);
|
||||
} else if (artifact != null
|
||||
&& artifact.getArtifactTypeID() == TSK_ACCOUNT.getTypeID()) {
|
||||
try {
|
||||
// if the artifact is an account artifact, get an account text .
|
||||
highlightedHitText = getAccountsText(content, nodeLookup);
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Failed to create AccountsText for " + content, ex); //NON-NLS
|
||||
|
||||
}
|
||||
} else if (artifact != null
|
||||
&& artifact.getArtifactTypeID() == TSK_KEYWORD_HIT.getTypeID()) {
|
||||
try {
|
||||
//if there is kwh artifact use that to construct the HighlightedText
|
||||
highlightedHitText = new HighlightedText(artifact);
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Failed to create HighlightedText for " + artifact, ex); //NON-NLS
|
||||
}
|
||||
} else if (artifact.getArtifactTypeID() == TSK_ACCOUNT.getTypeID() && file != null) {
|
||||
try {
|
||||
BlackboardAttribute attribute = artifact.getAttribute(TSK_ACCOUNT_TYPE);
|
||||
if (attribute != null && Account.Type.CREDIT_CARD.getTypeName().equals(attribute.getValueString())) {
|
||||
/*
|
||||
* The node is an credit card account node.
|
||||
*/
|
||||
highlightedHitText = getAccountsText(file, nodeLookup);
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Failed to create AccountsText for " + file, ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
}
|
||||
if (highlightedHitText != null) {
|
||||
sources.add(highlightedHitText);
|
||||
}
|
||||
|
||||
if (highlightedHitText != null) {
|
||||
sources.add(highlightedHitText);
|
||||
}
|
||||
|
||||
/*
|
||||
* Next, add the "raw" (not highlighted) text, if any, for any
|
||||
* content associated with the node.
|
||||
*/
|
||||
rawContentText = new RawText(content, content.getId());
|
||||
/*
|
||||
* Next, add the "raw" (not highlighted) text, if any, for any file
|
||||
* associated with the node.
|
||||
*/
|
||||
IndexedText rawContentText = null;
|
||||
if (file != null) {
|
||||
rawContentText = new RawText(file, file.getId());
|
||||
sources.add(rawContentText);
|
||||
}
|
||||
|
||||
@ -149,22 +172,21 @@ public class ExtractedContentViewer implements DataContentViewer {
|
||||
*/
|
||||
IndexedText rawArtifactText = null;
|
||||
try {
|
||||
rawArtifactText = getRawArtifactText(nodeLookup);
|
||||
rawArtifactText = getRawArtifactText(artifact);
|
||||
if (rawArtifactText != null) {
|
||||
sources.add(rawArtifactText);
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Error creating RawText for " + content, ex); //NON-NLS
|
||||
|
||||
}
|
||||
if (rawArtifactText != null) {
|
||||
sources.add(rawArtifactText);
|
||||
logger.log(Level.SEVERE, "Error creating RawText for " + file, ex); //NON-NLS
|
||||
}
|
||||
|
||||
// Now set the default source to be displayed.
|
||||
if (null != highlightedHitText) {
|
||||
if (highlightedHitText != null) {
|
||||
currentSource = highlightedHitText;
|
||||
} else if (null != rawContentText) {
|
||||
currentSource = rawContentText;
|
||||
} else {
|
||||
} else if (rawArtifactText != null) {
|
||||
currentSource = rawArtifactText;
|
||||
} else {
|
||||
currentSource = rawContentText;
|
||||
}
|
||||
|
||||
// Push the text sources into the panel.
|
||||
@ -177,16 +199,15 @@ public class ExtractedContentViewer implements DataContentViewer {
|
||||
panel.updateControls(currentSource);
|
||||
|
||||
String contentName = "";
|
||||
if (content != null) {
|
||||
contentName = content.getName();
|
||||
if (file != null) {
|
||||
contentName = file.getName();
|
||||
}
|
||||
setPanel(contentName, sources);
|
||||
|
||||
}
|
||||
|
||||
static private IndexedText getRawArtifactText(Lookup nodeLookup) throws TskCoreException {
|
||||
static private IndexedText getRawArtifactText(BlackboardArtifact artifact) throws TskCoreException {
|
||||
IndexedText rawArtifactText = null;
|
||||
BlackboardArtifact artifact = nodeLookup.lookup(BlackboardArtifact.class);
|
||||
if (null != artifact) {
|
||||
/*
|
||||
* For keyword hit artifacts, add the text of the artifact that hit,
|
||||
@ -275,48 +296,84 @@ public class ExtractedContentViewer implements DataContentViewer {
|
||||
}
|
||||
|
||||
/*
|
||||
* Is there a credit card or keyword hit artifact in the lookup
|
||||
* If the lookup of the node contains an ad hoc search result object,
|
||||
* then there must be indexed text that produced the hit.
|
||||
*/
|
||||
Collection<? extends BlackboardArtifact> artifacts = node.getLookup().lookupAll(BlackboardArtifact.class);
|
||||
if (artifacts != null) {
|
||||
for (BlackboardArtifact art : artifacts) {
|
||||
final int artifactTypeID = art.getArtifactTypeID();
|
||||
if (artifactTypeID == TSK_ACCOUNT.getTypeID()) {
|
||||
try {
|
||||
BlackboardAttribute attribute = art.getAttribute(TSK_ACCOUNT_TYPE);
|
||||
if (attribute != null && Account.Type.CREDIT_CARD.getTypeName().equals(attribute.getValueString())) {
|
||||
return true;
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Error getting TSK_ACCOUNT_TYPE attribute from artifact " + art.getArtifactID(), ex);
|
||||
AdHocQueryResult adHocQueryResult = node.getLookup().lookup(AdHocQueryResult.class);
|
||||
if (adHocQueryResult != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the lookup of the node contains either a keyword hit artifact or a
|
||||
* credit card account artifact from a credit card account numbers
|
||||
* search, then there must be indexed text that produced the hit(s).
|
||||
*/
|
||||
BlackboardArtifact artifact = node.getLookup().lookup(BlackboardArtifact.class);
|
||||
if (artifact != null) {
|
||||
final int artifactTypeID = artifact.getArtifactTypeID();
|
||||
if (artifactTypeID == TSK_KEYWORD_HIT.getTypeID()) {
|
||||
return true;
|
||||
} else if (artifactTypeID == TSK_ACCOUNT.getTypeID()) {
|
||||
try {
|
||||
BlackboardAttribute attribute = artifact.getAttribute(TSK_ACCOUNT_TYPE);
|
||||
if (attribute != null && Account.Type.CREDIT_CARD.getTypeName().equals(attribute.getValueString())) {
|
||||
return true;
|
||||
}
|
||||
} else if (artifactTypeID == TSK_KEYWORD_HIT.getTypeID()) {
|
||||
return true;
|
||||
} catch (TskCoreException ex) {
|
||||
/*
|
||||
* If there was an error checking the account type, fall
|
||||
* back to the check below for the file associated with the
|
||||
* account (if there is one).
|
||||
*/
|
||||
logger.log(Level.SEVERE, "Error getting TSK_ACCOUNT_TYPE attribute from artifact " + artifact.getArtifactID(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* No highlighted text for a keyword hit, so is there any indexed text
|
||||
* at all for this node?
|
||||
* If the lookup of the node contains a file, check to see if there is
|
||||
* indexed text for the file. Note that there should be a file in the
|
||||
* lookup of all nodes except artifact nodes that are associated with a
|
||||
* data source instead of a file.
|
||||
*/
|
||||
long documentID = getDocumentId(node);
|
||||
if (INVALID_DOCUMENT_ID == documentID) {
|
||||
return false;
|
||||
AbstractFile file = node.getLookup().lookup(AbstractFile.class);
|
||||
if (file != null && solrHasContent(file.getId())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return solrHasContent(documentID);
|
||||
/*
|
||||
* If the lookup of the node contains an artifact that is neither a
|
||||
* keyword hit artifact nor a credit card account artifact, and the
|
||||
* artifact is not associated with a file, check to see if there is
|
||||
* indexed text for the artifact.
|
||||
*/
|
||||
if (artifact != null) {
|
||||
return solrHasContent(artifact.getArtifactID());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int isPreferred(Node node) {
|
||||
BlackboardArtifact art = node.getLookup().lookup(BlackboardArtifact.class);
|
||||
|
||||
if (art == null) {
|
||||
BlackboardArtifact artifact = node.getLookup().lookup(BlackboardArtifact.class);
|
||||
if (artifact == null) {
|
||||
return 4;
|
||||
} else if (art.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()
|
||||
|| art.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID()) {
|
||||
} else if (artifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) {
|
||||
return 6;
|
||||
} else if (artifact.getArtifactTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID()) {
|
||||
try {
|
||||
BlackboardAttribute attribute = artifact.getAttribute(TSK_ACCOUNT_TYPE);
|
||||
if (attribute != null && Account.Type.CREDIT_CARD.getTypeName().equals(attribute.getValueString())) {
|
||||
return 6;
|
||||
} else {
|
||||
return 4;
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Error getting TSK_ACCOUNT_TYPE attribute from artifact " + artifact.getArtifactID(), ex);
|
||||
return 4;
|
||||
}
|
||||
} else {
|
||||
return 4;
|
||||
}
|
||||
@ -357,57 +414,6 @@ public class ExtractedContentViewer implements DataContentViewer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the object ID to use as the document ID for accessing any indexed
|
||||
* text for the given node.
|
||||
*
|
||||
* @param node The node.
|
||||
*
|
||||
* @return The document ID or zero, which is an invalid document ID.
|
||||
*/
|
||||
private Long getDocumentId(Node node) {
|
||||
/**
|
||||
* If the node is a Blackboard artifact node for anything other than a
|
||||
* keyword hit, the document ID for the text extracted from the artifact
|
||||
* (the concatenation of its attributes) is the artifact ID, a large,
|
||||
* negative integer. If it is a keyword hit, see if there is an
|
||||
* associated artifact. If there is, get the associated artifact's ID
|
||||
* and return it.
|
||||
*/
|
||||
BlackboardArtifact artifact = node.getLookup().lookup(BlackboardArtifact.class);
|
||||
if (null != artifact) {
|
||||
if (artifact.getArtifactTypeID() != BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) {
|
||||
return artifact.getArtifactID();
|
||||
} else {
|
||||
try {
|
||||
// Get the associated artifact attribute and return its value as the ID
|
||||
BlackboardAttribute blackboardAttribute = artifact.getAttribute(TSK_ASSOCIATED_ARTIFACT_TYPE);
|
||||
if (blackboardAttribute != null) {
|
||||
return blackboardAttribute.getValueLong();
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Error getting associated artifact attributes", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* For keyword search hit artifact nodes and all other nodes, the
|
||||
* document ID for the extracted text is the ID of the associated
|
||||
* content, if any, unless there is an associated artifact, which is
|
||||
* handled above.
|
||||
*/
|
||||
Content content = node.getLookup().lookup(Content.class);
|
||||
if (content != null) {
|
||||
return content.getId();
|
||||
}
|
||||
|
||||
/*
|
||||
* No extracted text, return an invalid docuemnt ID.
|
||||
*/
|
||||
return 0L;
|
||||
}
|
||||
|
||||
private class NextFindActionListener implements ActionListener {
|
||||
|
||||
@Override
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2017 Basis Technology Corp.
|
||||
* Copyright 2011-2018 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.keywordsearch;
|
||||
|
||||
import com.google.common.collect.Iterators;
|
||||
import com.google.common.collect.Range;
|
||||
import com.google.common.collect.RangeSet;
|
||||
import com.google.common.collect.TreeRangeSet;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
@ -41,7 +40,6 @@ import org.apache.solr.client.solrj.SolrRequest.METHOD;
|
||||
import org.apache.solr.client.solrj.response.QueryResponse;
|
||||
import org.apache.solr.common.SolrDocumentList;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.Version;
|
||||
import org.sleuthkit.autopsy.keywordsearch.KeywordQueryFilter.FilterType;
|
||||
@ -70,7 +68,7 @@ class HighlightedText implements IndexedText {
|
||||
|
||||
final private Server solrServer = KeywordSearch.getServer();
|
||||
|
||||
private final long objectId;
|
||||
private final long solrObjectId;
|
||||
/*
|
||||
* The keywords to highlight
|
||||
*/
|
||||
@ -104,16 +102,16 @@ class HighlightedText implements IndexedText {
|
||||
/**
|
||||
* This constructor is used when keyword hits are accessed from the ad-hoc
|
||||
* search results. In that case we have the entire QueryResults object and
|
||||
need to arrange the paging.
|
||||
* need to arrange the paging.
|
||||
*
|
||||
* @param objectId The objectID of the content whose text will be
|
||||
* @param solrObjectId The solrObjectId of the content whose text will be
|
||||
* highlighted.
|
||||
* @param QueryResults The QueryResults for the ad-hoc search from whose
|
||||
results a selection was made leading to this
|
||||
HighlightedText.
|
||||
* results a selection was made leading to this
|
||||
* HighlightedText.
|
||||
*/
|
||||
HighlightedText(long objectId, QueryResults hits) {
|
||||
this.objectId = objectId;
|
||||
HighlightedText(long solrObjectId, QueryResults hits) {
|
||||
this.solrObjectId = solrObjectId;
|
||||
this.hits = hits;
|
||||
}
|
||||
|
||||
@ -129,9 +127,9 @@ class HighlightedText implements IndexedText {
|
||||
this.artifact = artifact;
|
||||
BlackboardAttribute attribute = artifact.getAttribute(TSK_ASSOCIATED_ARTIFACT);
|
||||
if (attribute != null) {
|
||||
this.objectId = attribute.getValueLong();
|
||||
this.solrObjectId = attribute.getValueLong();
|
||||
} else {
|
||||
this.objectId = artifact.getObjectID();
|
||||
this.solrObjectId = artifact.getObjectID();
|
||||
}
|
||||
|
||||
}
|
||||
@ -140,13 +138,12 @@ class HighlightedText implements IndexedText {
|
||||
* This method figures out which pages / chunks have hits. Invoking it a
|
||||
* second time has no effect.
|
||||
*/
|
||||
@Messages({"HighlightedText.query.exception.msg=Could not perform the query to get chunk info and get highlights:"})
|
||||
synchronized private void loadPageInfo() throws TskCoreException, KeywordSearchModuleException, NoOpenCoreException {
|
||||
if (isPageInfoLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.numberPages = solrServer.queryNumFileChunks(this.objectId);
|
||||
this.numberPages = solrServer.queryNumFileChunks(this.solrObjectId);
|
||||
|
||||
if (artifact != null) {
|
||||
loadPageInfoFromArtifact();
|
||||
@ -158,7 +155,6 @@ class HighlightedText implements IndexedText {
|
||||
this.numberPages = 1;
|
||||
this.currentPage = 1;
|
||||
numberOfHitsPerPage.put(1, 0);
|
||||
pages.add(1);
|
||||
currentHitPerPage.put(1, 0);
|
||||
isPageInfoLoaded = true;
|
||||
}
|
||||
@ -194,7 +190,7 @@ class HighlightedText implements IndexedText {
|
||||
// Run a query to figure out which chunks for the current object have
|
||||
// hits for this keyword.
|
||||
|
||||
chunksQuery.addFilter(new KeywordQueryFilter(FilterType.CHUNK, this.objectId));
|
||||
chunksQuery.addFilter(new KeywordQueryFilter(FilterType.CHUNK, this.solrObjectId));
|
||||
|
||||
hits = chunksQuery.performQuery();
|
||||
loadPageInfoFromHits();
|
||||
@ -207,16 +203,16 @@ class HighlightedText implements IndexedText {
|
||||
isLiteral = hits.getQuery().isLiteral();
|
||||
|
||||
/**
|
||||
* Organize the hits by page, filter as needed.
|
||||
* We process *every* keyword here because in the case of a regular
|
||||
* expression search there may be multiple different keyword
|
||||
* hits located in different chunks for the same file/artifact.
|
||||
* Organize the hits by page, filter as needed. We process *every*
|
||||
* keyword here because in the case of a regular expression search there
|
||||
* may be multiple different keyword hits located in different chunks
|
||||
* for the same file/artifact.
|
||||
*/
|
||||
for (Keyword k : hits.getKeywords()) {
|
||||
for (KeywordHit hit : hits.getResults(k)) {
|
||||
int chunkID = hit.getChunkId();
|
||||
if (artifact != null) {
|
||||
if (chunkID != 0 && this.objectId == hit.getSolrObjectId()) {
|
||||
if (chunkID != 0 && this.solrObjectId == hit.getSolrObjectId()) {
|
||||
String hit1 = hit.getHit();
|
||||
if (keywords.stream().anyMatch(hit1::contains)) {
|
||||
numberOfHitsPerPage.put(chunkID, 0); //unknown number of matches in the page
|
||||
@ -225,7 +221,7 @@ class HighlightedText implements IndexedText {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (chunkID != 0 && this.objectId == hit.getSolrObjectId()) {
|
||||
if (chunkID != 0 && this.solrObjectId == hit.getSolrObjectId()) {
|
||||
|
||||
numberOfHitsPerPage.put(chunkID, 0); //unknown number of matches in the page
|
||||
currentHitPerPage.put(chunkID, 0); //set current hit to 0th
|
||||
@ -354,7 +350,7 @@ class HighlightedText implements IndexedText {
|
||||
SolrQuery q = new SolrQuery();
|
||||
q.setShowDebugInfo(DEBUG); //debug
|
||||
|
||||
String contentIdStr = Long.toString(this.objectId);
|
||||
String contentIdStr = Long.toString(this.solrObjectId);
|
||||
if (numberPages != 0) {
|
||||
chunkID = Integer.toString(this.currentPage);
|
||||
contentIdStr += "0".equals(chunkID) ? "" : "_" + chunkID;
|
||||
@ -427,8 +423,8 @@ class HighlightedText implements IndexedText {
|
||||
|
||||
return "<html><pre>" + highlightedContent + "</pre></html>"; //NON-NLS
|
||||
} catch (TskCoreException | KeywordSearchModuleException | NoOpenCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Error getting highlighted text for Solr doc id " + objectId + ", chunkID " + chunkID + ", highlight query: " + highlightField, ex); //NON-NLS
|
||||
return NbBundle.getMessage(this.getClass(), "HighlightedMatchesSource.getMarkup.queryFailedMsg");
|
||||
logger.log(Level.SEVERE, "Error getting highlighted text for Solr doc id " + solrObjectId + ", chunkID " + chunkID + ", highlight query: " + highlightField, ex); //NON-NLS
|
||||
return Bundle.IndexedText_errorMessage_errorGettingText();
|
||||
}
|
||||
}
|
||||
|
||||
@ -466,12 +462,13 @@ class HighlightedText implements IndexedText {
|
||||
* to a Solr query. We expect there to only ever be
|
||||
* a single document.
|
||||
*
|
||||
* @return Either a string with the keyword highlighted via HTML span tags or a string
|
||||
* indicating that we did not find a hit in the document.
|
||||
* @return Either a string with the keyword highlighted via HTML span tags
|
||||
* or a string indicating that we did not find a hit in the
|
||||
* document.
|
||||
*/
|
||||
static String attemptManualHighlighting(SolrDocumentList solrDocumentList, String highlightField, Collection<String> keywords) {
|
||||
if (solrDocumentList.isEmpty()) {
|
||||
return NbBundle.getMessage(HighlightedText.class, "HighlightedMatchesSource.getMarkup.noMatchMsg");
|
||||
return Bundle.IndexedText_errorMessage_errorGettingText();
|
||||
}
|
||||
|
||||
// It doesn't make sense for there to be more than a single document in
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2017 Basis Technology Corp.
|
||||
* Copyright 2011-2018 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -18,12 +18,19 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.keywordsearch;
|
||||
|
||||
import org.openide.util.NbBundle;
|
||||
|
||||
/**
|
||||
* Interface to provide HTML text to display in ExtractedContentViewer. There is
|
||||
* a SOLR implementation of this that interfaces with SOLR to highlight the
|
||||
* keyword hits and a version that does not do markup so that you can simply
|
||||
* view the stored text.
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"IndexedText.errorMessage.errorGettingText=<span style='font-style:italic'>Error retrieving indexed text.</span>",
|
||||
"IndexedText.warningMessage.knownFile=<span style='font-style:italic'>This file is a known file (based on MD5 hash) and does not have indexed text.</span>",
|
||||
"IndexedText.warningMessage.noTextAvailable=<span style='font-style:italic'>No indexed text for this file.</span>"
|
||||
})
|
||||
interface IndexedText {
|
||||
|
||||
/**
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-2017 Basis Technology Corp.
|
||||
* Copyright 2013-2018 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -39,6 +39,7 @@ import org.sleuthkit.autopsy.actions.AddContentTagAction;
|
||||
import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction;
|
||||
import org.sleuthkit.autopsy.directorytree.HashSearchAction;
|
||||
import org.sleuthkit.autopsy.directorytree.NewWindowViewAction;
|
||||
import org.sleuthkit.autopsy.keywordsearch.KeywordSearchResultFactory.AdHocQueryResult;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.ContentVisitor;
|
||||
@ -52,12 +53,18 @@ import org.sleuthkit.datamodel.TskData;
|
||||
import org.sleuthkit.datamodel.VirtualDirectory;
|
||||
|
||||
/**
|
||||
*
|
||||
* FilterNode containing properties and actions for keyword search.
|
||||
*/
|
||||
class KeywordSearchFilterNode extends FilterNode {
|
||||
|
||||
KeywordSearchFilterNode(QueryResults highlights, Node original) {
|
||||
super(original, null, new ProxyLookup(Lookups.singleton(highlights), original.getLookup()));
|
||||
/**
|
||||
* Instantiate a KeywordSearchFilterNode.
|
||||
*
|
||||
* @param adHocQueryResult The query content.
|
||||
* @param original The original source node.
|
||||
*/
|
||||
KeywordSearchFilterNode(AdHocQueryResult adHocQueryResult, Node original) {
|
||||
super(original, null, new ProxyLookup(Lookups.singleton(adHocQueryResult), original.getLookup()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -37,7 +37,6 @@ import org.openide.nodes.ChildFactory;
|
||||
import org.openide.nodes.Children;
|
||||
import org.openide.nodes.Node;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.lookup.Lookups;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
@ -49,6 +48,7 @@ import org.sleuthkit.autopsy.datamodel.KeyValue;
|
||||
import org.sleuthkit.autopsy.datamodel.KeyValueNode;
|
||||
import org.sleuthkit.autopsy.keywordsearch.KeywordSearchResultFactory.KeyValueQueryContent;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD;
|
||||
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW;
|
||||
@ -66,7 +66,7 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
*/
|
||||
class KeywordSearchResultFactory extends ChildFactory<KeyValue> {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(KeywordSearchResultFactory.class.getName());
|
||||
private static final Logger logger = Logger.getLogger(KeywordSearchResultFactory.class.getName());
|
||||
|
||||
//common properties (superset of all Node properties) to be displayed as columns
|
||||
static final List<String> COMMON_PROPERTIES
|
||||
@ -75,10 +75,10 @@ class KeywordSearchResultFactory extends ChildFactory<KeyValue> {
|
||||
TSK_KEYWORD,
|
||||
TSK_KEYWORD_REGEXP,
|
||||
TSK_KEYWORD_PREVIEW)
|
||||
.map(BlackboardAttribute.ATTRIBUTE_TYPE::getDisplayName),
|
||||
.map(BlackboardAttribute.ATTRIBUTE_TYPE::getDisplayName),
|
||||
Arrays.stream(AbstractAbstractFileNode.AbstractFilePropertyType.values())
|
||||
.map(Object::toString))
|
||||
.collect(Collectors.toList());
|
||||
.map(Object::toString))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
private final Collection<QueryRequest> queryRequests;
|
||||
|
||||
@ -140,7 +140,7 @@ class KeywordSearchResultFactory extends ChildFactory<KeyValue> {
|
||||
try {
|
||||
queryResults = queryRequest.performQuery();
|
||||
} catch (KeywordSearchModuleException | NoOpenCoreException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Could not perform the query " + queryRequest.getQueryString(), ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, "Could not perform the query " + queryRequest.getQueryString(), ex); //NON-NLS
|
||||
MessageNotifyUtil.Notify.error(Bundle.KeywordSearchResultFactory_query_exception_msg() + queryRequest.getQueryString(), ex.getCause().getMessage());
|
||||
return false;
|
||||
}
|
||||
@ -148,7 +148,7 @@ class KeywordSearchResultFactory extends ChildFactory<KeyValue> {
|
||||
try {
|
||||
tskCase = Case.getCurrentCase().getSleuthkitCase();
|
||||
} catch (IllegalStateException ex) {
|
||||
LOGGER.log(Level.SEVERE, "There was no case open.", ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, "There was no case open.", ex); //NON-NLS
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -165,11 +165,11 @@ class KeywordSearchResultFactory extends ChildFactory<KeyValue> {
|
||||
try {
|
||||
content = tskCase.getContentById(hit.getContentID());
|
||||
if (content == null) {
|
||||
LOGGER.log(Level.SEVERE, "There was a error getting content by id."); //NON-NLS
|
||||
logger.log(Level.SEVERE, "There was a error getting content by id."); //NON-NLS
|
||||
return false;
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.SEVERE, "There was a error getting content by id.", ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, "There was a error getting content by id.", ex); //NON-NLS
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -188,18 +188,20 @@ class KeywordSearchResultFactory extends ChildFactory<KeyValue> {
|
||||
}
|
||||
|
||||
String hitName;
|
||||
BlackboardArtifact artifact = null;
|
||||
if (hit.isArtifactHit()) {
|
||||
try {
|
||||
hitName = tskCase.getBlackboardArtifact(hit.getArtifactID().get()).getDisplayName() + " Artifact"; //NON-NLS
|
||||
artifact = tskCase.getBlackboardArtifact(hit.getArtifactID().get());
|
||||
hitName = artifact.getDisplayName() + " Artifact"; //NON-NLS
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Error getting blckboard artifact by id", ex);
|
||||
logger.log(Level.SEVERE, "Error getting blckboard artifact by id", ex);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
hitName = contentName;
|
||||
}
|
||||
hitNumber++;
|
||||
tempList.add(new KeyValueQueryContent(hitName, properties, hitNumber, hit.getSolrObjectId(), content, queryRequest, queryResults));
|
||||
tempList.add(new KeyValueQueryContent(hitName, properties, hitNumber, hit.getSolrObjectId(), content, artifact, queryRequest, queryResults));
|
||||
|
||||
}
|
||||
|
||||
@ -250,13 +252,12 @@ class KeywordSearchResultFactory extends ChildFactory<KeyValue> {
|
||||
Node resultNode;
|
||||
|
||||
if (key instanceof KeyValueQueryContent) {
|
||||
final Content content = ((KeyValueQueryContent) key).getContent();
|
||||
QueryResults hits = ((KeyValueQueryContent) key).getHits();
|
||||
AdHocQueryResult adHocQueryResult = new AdHocQueryResult((KeyValueQueryContent) key);
|
||||
|
||||
Node kvNode = new KeyValueNode(key, Children.LEAF, Lookups.singleton(content));
|
||||
Node kvNode = new KeyValueNode(key, Children.LEAF);
|
||||
|
||||
//wrap in KeywordSearchFilterNode for the markup content, might need to override FilterNode for more customization
|
||||
resultNode = new KeywordSearchFilterNode(hits, kvNode);
|
||||
resultNode = new KeywordSearchFilterNode(adHocQueryResult, kvNode);
|
||||
} else {
|
||||
resultNode = new EmptyNode("This Node Is Empty");
|
||||
resultNode.setDisplayName(NbBundle.getMessage(this.getClass(), "KeywordSearchResultFactory.createNodeForKey.noResultsFound.text"));
|
||||
@ -266,6 +267,75 @@ class KeywordSearchResultFactory extends ChildFactory<KeyValue> {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This class encapsulates content, query results, and an associated Solr
|
||||
* object ID for storing in the Lookup to be read later.
|
||||
*/
|
||||
final class AdHocQueryResult {
|
||||
|
||||
private final long solrObjectId;
|
||||
private final Content content;
|
||||
private final BlackboardArtifact artifact;
|
||||
private final QueryResults results;
|
||||
|
||||
/**
|
||||
* Instantiate a AdHocQueryResult object.
|
||||
*
|
||||
* @param solrObjectId The Solr object ID associated with the object in
|
||||
* which the hit was found.
|
||||
* @param content The content for the query result.
|
||||
* @param artifact The artifact associated with the query result.
|
||||
* @param results The query results.
|
||||
*/
|
||||
AdHocQueryResult(KeyValueQueryContent key) {
|
||||
this.solrObjectId = key.getSolrObjectId();
|
||||
this.content = key.getContent();
|
||||
this.artifact = key.getArtifact();
|
||||
this.results = key.getHits();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Solr object ID associated with the object in which the hit
|
||||
* was found. This could be a file or an artifact.
|
||||
*
|
||||
* @return The Solr object ID.
|
||||
*/
|
||||
long getSolrObjectId() {
|
||||
return solrObjectId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the content for the query result. This can be either a file or a
|
||||
* data source, and it may or may not be the content in which the hit
|
||||
* occurred. If the hit is in a file, the Content object represents that
|
||||
* file. But if the hit is in an artifact, the Content object represents
|
||||
* the source file or data source of the artifact.
|
||||
*
|
||||
* @return The content object.
|
||||
*/
|
||||
Content getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the artifact for the query result.
|
||||
*
|
||||
* @return The artifact.
|
||||
*/
|
||||
BlackboardArtifact getArtifact() {
|
||||
return artifact;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query results.
|
||||
*
|
||||
* @return The query results.
|
||||
*/
|
||||
QueryResults getResults() {
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to display keyword search results in table. Eventually turned into a
|
||||
* node.
|
||||
@ -275,6 +345,7 @@ class KeywordSearchResultFactory extends ChildFactory<KeyValue> {
|
||||
private final long solrObjectId;
|
||||
|
||||
private final Content content;
|
||||
private final BlackboardArtifact artifact;
|
||||
private final QueryResults hits;
|
||||
private final KeywordSearchQuery query;
|
||||
|
||||
@ -286,15 +357,17 @@ class KeywordSearchResultFactory extends ChildFactory<KeyValue> {
|
||||
* @param map Contains content metadata, snippets, etc.
|
||||
* (property map)
|
||||
* @param id User incremented ID
|
||||
* @param solrObjectId
|
||||
* @param content File that had the hit.
|
||||
* @param solrObjectId The ID of the object.
|
||||
* @param content The content object.
|
||||
* @param artifact The blackboard artifact.
|
||||
* @param query Query used in search
|
||||
* @param hits Full set of search results (for all files! @@@)
|
||||
*/
|
||||
KeyValueQueryContent(String name, Map<String, Object> map, int id, long solrObjectId, Content content, KeywordSearchQuery query, QueryResults hits) {
|
||||
KeyValueQueryContent(String name, Map<String, Object> map, int id, long solrObjectId, Content content, BlackboardArtifact artifact, KeywordSearchQuery query, QueryResults hits) {
|
||||
super(name, map, id);
|
||||
this.solrObjectId = solrObjectId;
|
||||
this.content = content;
|
||||
this.artifact = artifact;
|
||||
|
||||
this.hits = hits;
|
||||
this.query = query;
|
||||
@ -304,6 +377,10 @@ class KeywordSearchResultFactory extends ChildFactory<KeyValue> {
|
||||
return content;
|
||||
}
|
||||
|
||||
BlackboardArtifact getArtifact() {
|
||||
return artifact;
|
||||
}
|
||||
|
||||
long getSolrObjectId() {
|
||||
return solrObjectId;
|
||||
}
|
||||
@ -358,9 +435,9 @@ class KeywordSearchResultFactory extends ChildFactory<KeyValue> {
|
||||
try {
|
||||
get();
|
||||
} catch (InterruptedException | CancellationException ex) {
|
||||
LOGGER.log(Level.WARNING, "User cancelled writing of ad hoc search query results for '{0}' to the blackboard", query.getQueryString()); //NON-NLS
|
||||
logger.log(Level.WARNING, "User cancelled writing of ad hoc search query results for '{0}' to the blackboard", query.getQueryString()); //NON-NLS
|
||||
} catch (ExecutionException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Error writing of ad hoc search query results for " + query.getQueryString() + " to the blackboard", ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, "Error writing of ad hoc search query results for " + query.getQueryString() + " to the blackboard", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2017 Basis Technology Corp.
|
||||
* Copyright 2011-2018 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -18,7 +18,6 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.keywordsearch;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.logging.Level;
|
||||
import org.apache.solr.client.solrj.SolrServerException;
|
||||
import org.openide.util.NbBundle;
|
||||
@ -153,10 +152,10 @@ class RawText implements IndexedText {
|
||||
} else if (this.blackboardArtifact != null) {
|
||||
return getArtifactText();
|
||||
}
|
||||
} catch (SolrServerException ex) {
|
||||
logger.log(Level.SEVERE, "Couldn't get extracted content", ex); //NON-NLS
|
||||
} catch (SolrServerException | NoOpenCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Couldn't get extracted text", ex); //NON-NLS
|
||||
}
|
||||
return NbBundle.getMessage(this.getClass(), "RawText.getText.error.msg");
|
||||
return Bundle.IndexedText_errorMessage_errorGettingText();
|
||||
}
|
||||
|
||||
@NbBundle.Messages({
|
||||
@ -186,7 +185,6 @@ class RawText implements IndexedText {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getNumberPages() {
|
||||
return numPages;
|
||||
@ -207,11 +205,8 @@ class RawText implements IndexedText {
|
||||
} else {
|
||||
hasChunks = true;
|
||||
}
|
||||
} catch (KeywordSearchModuleException ex) {
|
||||
logger.log(Level.WARNING, "Could not get number of chunks: ", ex); //NON-NLS
|
||||
|
||||
} catch (NoOpenCoreException ex) {
|
||||
logger.log(Level.WARNING, "Could not get number of chunks: ", ex); //NON-NLS
|
||||
} catch (KeywordSearchModuleException | NoOpenCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Could not get number of chunks: ", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
@ -225,31 +220,32 @@ class RawText implements IndexedText {
|
||||
* chunks. This means we need to address the content
|
||||
* pages specially.
|
||||
*
|
||||
* @return the extracted content
|
||||
* @return the extracted text
|
||||
*
|
||||
* @throws SolrServerException if something goes wrong
|
||||
* @throws NoOpenCoreException If no Solr core is available.
|
||||
* @throws SolrServerException If there's a Solr communication or parsing
|
||||
* issue.
|
||||
*/
|
||||
private String getContentText(int currentPage, boolean hasChunks) throws SolrServerException {
|
||||
private String getContentText(int currentPage, boolean hasChunks) throws NoOpenCoreException, SolrServerException {
|
||||
final Server solrServer = KeywordSearch.getServer();
|
||||
|
||||
if (hasChunks == false) {
|
||||
//if no chunks, it is safe to assume there is no text content
|
||||
//because we are storing extracted text in chunks only
|
||||
//and the non-chunk stores meta-data only
|
||||
String name = content.getName();
|
||||
String msg = null;
|
||||
|
||||
if (content instanceof AbstractFile) {
|
||||
//we know it's AbstractFile, but do quick check to make sure if we index other objects in future
|
||||
boolean isKnown = TskData.FileKnown.KNOWN.equals(((AbstractFile) content).getKnown());
|
||||
if (isKnown && KeywordSearchSettings.getSkipKnown()) {
|
||||
msg = NbBundle.getMessage(this.getClass(), "ExtractedContentViewer.getSolrContent.knownFileMsg", name);
|
||||
msg = Bundle.IndexedText_warningMessage_knownFile();
|
||||
}
|
||||
}
|
||||
if (msg == null) {
|
||||
msg = NbBundle.getMessage(this.getClass(), "ExtractedContentViewer.getSolrContent.noTxtYetMsg", name);
|
||||
msg = Bundle.IndexedText_warningMessage_noTextAvailable();
|
||||
}
|
||||
String htmlMsg = NbBundle.getMessage(this.getClass(), "ExtractedContentViewer.getSolrContent.txtBodyItal", msg);
|
||||
return htmlMsg;
|
||||
return msg;
|
||||
}
|
||||
|
||||
int chunkId = currentPage;
|
||||
@ -262,33 +258,46 @@ class RawText implements IndexedText {
|
||||
}
|
||||
|
||||
//not cached
|
||||
try {
|
||||
String indexedText = solrServer.getSolrContent(this.objectId, chunkId);
|
||||
if (indexedText == null) indexedText = "";
|
||||
cachedString = EscapeUtil.escapeHtml(indexedText).trim();
|
||||
StringBuilder sb = new StringBuilder(cachedString.length() + 20);
|
||||
sb.append("<pre>").append(cachedString).append("</pre>"); //NON-NLS
|
||||
cachedString = sb.toString();
|
||||
cachedChunk = chunkId;
|
||||
} catch (NoOpenCoreException ex) {
|
||||
logger.log(Level.SEVERE, "No open core", ex); //NON-NLS
|
||||
return "";
|
||||
String indexedText = solrServer.getSolrContent(this.objectId, chunkId);
|
||||
if (indexedText == null) {
|
||||
if (content instanceof AbstractFile) {
|
||||
return Bundle.IndexedText_errorMessage_errorGettingText();
|
||||
} else {
|
||||
return Bundle.IndexedText_warningMessage_noTextAvailable();
|
||||
}
|
||||
} else if (indexedText.isEmpty()) {
|
||||
return Bundle.IndexedText_warningMessage_noTextAvailable();
|
||||
}
|
||||
|
||||
cachedString = EscapeUtil.escapeHtml(indexedText).trim();
|
||||
StringBuilder sb = new StringBuilder(cachedString.length() + 20);
|
||||
sb.append("<pre>").append(cachedString).append("</pre>"); //NON-NLS
|
||||
cachedString = sb.toString();
|
||||
cachedChunk = chunkId;
|
||||
|
||||
return cachedString;
|
||||
}
|
||||
|
||||
private String getArtifactText() throws SolrServerException{
|
||||
try {
|
||||
String indexedText = KeywordSearch.getServer().getSolrContent(this.objectId, 1);
|
||||
if (indexedText == null) indexedText = "";
|
||||
indexedText = EscapeUtil.escapeHtml(indexedText).trim();
|
||||
StringBuilder sb = new StringBuilder(indexedText.length() + 20);
|
||||
sb.append("<pre>").append(indexedText).append("</pre>"); //NON-NLS
|
||||
return sb.toString();
|
||||
} catch (NoOpenCoreException ex) {
|
||||
logger.log(Level.SEVERE, "No open core", ex); //NON-NLS
|
||||
return "";
|
||||
|
||||
/**
|
||||
* Get extracted artifact for a node from Solr
|
||||
*
|
||||
* @return the extracted text
|
||||
*
|
||||
* @throws NoOpenCoreException If no Solr core is available.
|
||||
* @throws SolrServerException If there's a Solr communication or parsing
|
||||
* issue.
|
||||
*/
|
||||
private String getArtifactText() throws NoOpenCoreException, SolrServerException {
|
||||
String indexedText = KeywordSearch.getServer().getSolrContent(this.objectId, 1);
|
||||
if (indexedText == null || indexedText.isEmpty()) {
|
||||
return Bundle.IndexedText_errorMessage_errorGettingText();
|
||||
}
|
||||
|
||||
indexedText = EscapeUtil.escapeHtml(indexedText).trim();
|
||||
StringBuilder sb = new StringBuilder(indexedText.length() + 20);
|
||||
sb.append("<pre>").append(indexedText).append("</pre>"); //NON-NLS
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -76,10 +76,13 @@ class ExtractRegistry extends Extract {
|
||||
final private static UsbDeviceIdMapper USB_MAPPER = new UsbDeviceIdMapper();
|
||||
final private static String RIP_EXE = "rip.exe";
|
||||
final private static String RIP_PL = "rip.pl";
|
||||
final private static String PERL = "perl ";
|
||||
private List<String> rrCmd = new ArrayList<>();
|
||||
private List<String> rrFullCmd= new ArrayList<>();
|
||||
|
||||
|
||||
ExtractRegistry() throws IngestModuleException {
|
||||
moduleName = NbBundle.getMessage(ExtractIE.class, "ExtractRegistry.moduleName.text");
|
||||
|
||||
final File rrRoot = InstalledFileLocator.getDefault().locate("rr", ExtractRegistry.class.getPackage().getName(), false); //NON-NLS
|
||||
if (rrRoot == null) {
|
||||
throw new IngestModuleException(Bundle.RegRipperNotFound());
|
||||
@ -98,20 +101,33 @@ class ExtractRegistry extends Extract {
|
||||
RR_PATH = rrHome.resolve(executableToRun).toString();
|
||||
rrFullHome = rrFullRoot.toPath();
|
||||
RR_FULL_PATH = rrFullHome.resolve(executableToRun).toString();
|
||||
|
||||
|
||||
if (!(new File(RR_PATH).exists())) {
|
||||
throw new IngestModuleException(Bundle.RegRipperNotFound());
|
||||
}
|
||||
if (!(new File(RR_FULL_PATH).exists())) {
|
||||
throw new IngestModuleException(Bundle.RegRipperFullNotFound());
|
||||
}
|
||||
|
||||
if (!PlatformUtil.isWindowsOS()) {
|
||||
RR_PATH = PERL + RR_PATH;
|
||||
RR_FULL_PATH = PERL + RR_FULL_PATH;
|
||||
if(PlatformUtil.isWindowsOS()){
|
||||
rrCmd.add(RR_PATH);
|
||||
rrFullCmd.add(RR_FULL_PATH);
|
||||
}else{
|
||||
String perl;
|
||||
File usrBin = new File("/usr/bin/perl");
|
||||
File usrLocalBin = new File("/usr/local/bin/perl");
|
||||
if(usrBin.canExecute() && usrBin.exists() && !usrBin.isDirectory()){
|
||||
perl = "/usr/bin/perl";
|
||||
}else if(usrLocalBin.canExecute() && usrLocalBin.exists() && !usrLocalBin.isDirectory()){
|
||||
perl = "/usr/local/bin/perl";
|
||||
}else{
|
||||
throw new IngestModuleException("perl not found in your system");
|
||||
}
|
||||
rrCmd.add(perl);
|
||||
rrCmd.add(RR_PATH);
|
||||
rrFullCmd.add(perl);
|
||||
rrFullCmd.add(RR_FULL_PATH);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for the registry hives on the system.
|
||||
*/
|
||||
@ -262,7 +278,7 @@ class ExtractRegistry extends Extract {
|
||||
regOutputFiles.autopsyPlugins = outFilePathBase + "-autopsy.txt"; //NON-NLS
|
||||
String errFilePath = outFilePathBase + "-autopsy.err.txt"; //NON-NLS
|
||||
logger.log(Level.INFO, "Writing RegRipper results to: {0}", regOutputFiles.autopsyPlugins); //NON-NLS
|
||||
executeRegRipper(RR_PATH, rrHome, regFilePath, autopsyType, regOutputFiles.autopsyPlugins, errFilePath);
|
||||
executeRegRipper(rrCmd, rrHome, regFilePath, autopsyType, regOutputFiles.autopsyPlugins, errFilePath);
|
||||
}
|
||||
if (context.dataSourceIngestIsCancelled()) {
|
||||
return regOutputFiles;
|
||||
@ -273,15 +289,17 @@ class ExtractRegistry extends Extract {
|
||||
regOutputFiles.fullPlugins = outFilePathBase + "-full.txt"; //NON-NLS
|
||||
String errFilePath = outFilePathBase + "-full.err.txt"; //NON-NLS
|
||||
logger.log(Level.INFO, "Writing Full RegRipper results to: {0}", regOutputFiles.fullPlugins); //NON-NLS
|
||||
executeRegRipper(RR_FULL_PATH, rrFullHome, regFilePath, fullType, regOutputFiles.fullPlugins, errFilePath);
|
||||
executeRegRipper(rrFullCmd, rrFullHome, regFilePath, fullType, regOutputFiles.fullPlugins, errFilePath);
|
||||
}
|
||||
return regOutputFiles;
|
||||
}
|
||||
|
||||
private void executeRegRipper(String regRipperPath, Path regRipperHomeDir, String hiveFilePath, String hiveFileType, String outputFile, String errFile) {
|
||||
private void executeRegRipper(List<String> regRipperPath, Path regRipperHomeDir, String hiveFilePath, String hiveFileType, String outputFile, String errFile) {
|
||||
try {
|
||||
List<String> commandLine = new ArrayList<>();
|
||||
commandLine.add(regRipperPath);
|
||||
for(String cmd: regRipperPath){
|
||||
commandLine.add(cmd);
|
||||
}
|
||||
commandLine.add("-r"); //NON-NLS
|
||||
commandLine.add(hiveFilePath);
|
||||
commandLine.add("-f"); //NON-NLS
|
||||
|
@ -23,9 +23,11 @@
|
||||
package org.sleuthkit.autopsy.recentactivity;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import org.openide.util.Exceptions;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
@ -57,6 +59,7 @@ public final class RAImageIngestModule implements DataSourceIngestModule {
|
||||
public void startUp(IngestJobContext context) throws IngestModuleException {
|
||||
this.context = context;
|
||||
|
||||
|
||||
Extract registry = new ExtractRegistry();
|
||||
Extract iexplore = new ExtractIE();
|
||||
Extract recentDocuments = new RecentDocumentsByLnk();
|
||||
|
@ -208,7 +208,7 @@ public class AutopsyTestCases {
|
||||
databases.stream().map((database) -> {
|
||||
JButtonOperator importButtonOperator = new JButtonOperator(hashMainDialogOperator, "Import");
|
||||
importButtonOperator.pushNoBlock();
|
||||
JDialog addDatabaseDialog = JDialogOperator.waitJDialog("Import Hash Database", false, false);
|
||||
JDialog addDatabaseDialog = JDialogOperator.waitJDialog("Import Hash Set", false, false);
|
||||
JDialogOperator addDatabaseDialogOperator = new JDialogOperator(addDatabaseDialog);
|
||||
JButtonOperator browseButtonOperator = new JButtonOperator(addDatabaseDialogOperator, "Open...", 0);
|
||||
browseButtonOperator.pushNoBlock();
|
||||
|
31
build.xml
31
build.xml
@ -32,10 +32,12 @@
|
||||
</condition>
|
||||
<condition property="os.family" value="windows">
|
||||
<os family="windows"/>
|
||||
</condition>
|
||||
</condition>
|
||||
|
||||
<condition property="os.family" value="mac">
|
||||
<os family="mac"/>
|
||||
</condition>
|
||||
</condition>
|
||||
|
||||
<import file="build-${os.family}.xml"/>
|
||||
|
||||
<!-- Third party tools dependencies -->
|
||||
@ -91,7 +93,7 @@
|
||||
<unzip src="${thirdparty.dir}/gstreamer/${os.family}/i386/0.10.7/gstreamer.zip" dest="${zip-tmp}/${app.name}/gstreamer"/>
|
||||
</then>
|
||||
</if>
|
||||
|
||||
|
||||
<copy file="${basedir}/icons/icon.ico" tofile="${zip-tmp}/${app.name}/icon.ico" overwrite="true"/>
|
||||
|
||||
<!-- Copy the Autopsy documentation to the docs folder -->
|
||||
@ -101,16 +103,19 @@
|
||||
|
||||
<property name="app.property.file" value="${zip-tmp}/${app.name}/etc/${app.name}.conf" />
|
||||
<var name="jvm-value" value="--branding ${app.name} -J-Xms24m -J-Xmx4G -J-Xverify:none -J-XX:+UseG1GC -J-XX:+UseStringDeduplication"/>
|
||||
<!-- for Japanese localized version add option: -Duser.language=ja -->
|
||||
<if>
|
||||
<equals arg1="${os.family}" arg2="mac"/>
|
||||
<then>
|
||||
<property name="jvm.options" value=""${jvm-value} -J-Xdock:name=${app.title}""/>
|
||||
</then>
|
||||
<else>
|
||||
<property name="jvm.options" value=""${jvm-value}""/>
|
||||
</else>
|
||||
</if>
|
||||
<!-- for Japanese localized version add option: -Duser.language=ja -->
|
||||
|
||||
|
||||
<if>
|
||||
<equals arg1="${os.family}" arg2="mac"/>
|
||||
<then>
|
||||
<property name="jvm.options" value=""${jvm-value} -J-Xdock:name=${app.title}""/>
|
||||
</then>
|
||||
<else>
|
||||
<property name="jvm.options" value=""${jvm-value}""/>
|
||||
</else>
|
||||
</if>
|
||||
|
||||
<propertyfile file="${app.property.file}">
|
||||
<!-- Note: can be higher on 64 bit systems, should be in sync with project.properties -->
|
||||
<entry key="default_options" value="@JVM_OPTIONS" />
|
||||
|
@ -73,7 +73,7 @@ Typically a general report module should interact with both the Blackboard API i
|
||||
|
||||
\subsection report_create_module_showing Showing Results
|
||||
|
||||
You should call Case.addReport() with the path to your report so that it is shown in the Autopsy tree. You can specify a specific file or folder and the user can then view it later.
|
||||
You should call org.sleuthkit.autopsy.casemodule.Case.addReport() with the path to your report so that it is shown in the Autopsy tree. You can specify a specific file or folder and the user can then view it later.
|
||||
|
||||
\subsection report_create_module_layer Installing your Report Module
|
||||
|
||||
|
12
ruleset.xml
12
ruleset.xml
@ -62,7 +62,8 @@
|
||||
<!-- Commented out because it was flagged some of our header / copyright comments
|
||||
<rule ref="rulesets/java/comments.xml/CommentSize"/> -->
|
||||
<rule ref="rulesets/java/comments.xml/CommentContent"/>
|
||||
<rule ref="rulesets/java/comments.xml/CommentDefaultAccessModifier"/>
|
||||
<!-- Commented out because we use default (package) access often and commenting it does not make sense for us
|
||||
<rule ref="rulesets/java/comments.xml/CommentDefaultAccessModifier"/> -->
|
||||
<!--
|
||||
Commented out because they are controversial and we want basics right now.
|
||||
<rule ref="rulesets/java/controversial.xml/NullAssignment"/>
|
||||
@ -212,13 +213,16 @@
|
||||
<rule ref="rulesets/java/naming.xml/NoPackage"/>
|
||||
<rule ref="rulesets/java/naming.xml/MethodWithSameNameAsEnclosingClass"/>
|
||||
<rule ref="rulesets/java/naming.xml/ShortVariable"/>
|
||||
<rule ref="rulesets/java/naming.xml/LongVariable"/>
|
||||
<!-- Commented out because clarity trumps brevity, developers can use their own judgement
|
||||
<rule ref="rulesets/java/naming.xml/LongVariable"/> -->
|
||||
<rule ref="rulesets/java/naming.xml/ShortMethodName"/>
|
||||
<rule ref="rulesets/java/naming.xml/BooleanGetMethodName"/>
|
||||
<rule ref="rulesets/java/naming.xml/PackageCase"/>
|
||||
<!-- Commented out because this is a nice micro-refinement, but too much of our code does not do this, developers can, but don't have to ignore this practice for now
|
||||
<rule ref="rulesets/java/optimizations.xml/LocalVariableCouldBeFinal"/>
|
||||
<rule ref="rulesets/java/optimizations.xml/MethodArgumentCouldBeFinal"/>
|
||||
<rule ref="rulesets/java/optimizations.xml/AvoidInstantiatingObjectsInLoops"/>
|
||||
<rule ref="rulesets/java/optimizations.xml/MethodArgumentCouldBeFinal"/> -->
|
||||
<!-- Commented out because this is not typical coding practice and it seems of dubious value, e.g., https://stackoverflow.com/questions/17340421/pmd-avoid-instantiating-new-objects-inside-loops/17458503
|
||||
<rule ref="rulesets/java/optimizations.xml/AvoidInstantiatingObjectsInLoops"/> -->
|
||||
<rule ref="rulesets/java/optimizations.xml/UseArrayListInsteadOfVector"/>
|
||||
<rule ref="rulesets/java/optimizations.xml/SimplifyStartsWith"/>
|
||||
<rule ref="rulesets/java/optimizations.xml/UseStringBufferForStringAppends"/>
|
||||
|
@ -1454,10 +1454,10 @@ class Logs(object):
|
||||
Errors.print_error("Error: Unable to open autopsy.log.0.")
|
||||
Errors.print_error(str(e) + "\n")
|
||||
logging.warning(traceback.format_exc())
|
||||
# Start date must look like: "Fri Mar 27 13:27:34 EDT 2015"
|
||||
# Start date must look like: ""
|
||||
# End date must look like: "Mon Jul 16 13:02:42 2012"
|
||||
# *** If logging time format ever changes this will break ***
|
||||
start = datetime.datetime.strptime(test_data.start_date, "%a %b %d %H:%M:%S %Z %Y")
|
||||
start = datetime.datetime.strptime(test_data.start_date, "%Y-%m-%d %H:%M:%S.%f")
|
||||
end = datetime.datetime.strptime(test_data.end_date, "%a %b %d %H:%M:%S %Y")
|
||||
test_data.total_test_time = str(end - start)
|
||||
|
||||
|
1834
thirdparty/rr-full/Parse/Win32Registry.pm
vendored
Normal file
1834
thirdparty/rr-full/Parse/Win32Registry.pm
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1107
thirdparty/rr-full/Parse/Win32Registry/Base.pm
vendored
Normal file
1107
thirdparty/rr-full/Parse/Win32Registry/Base.pm
vendored
Normal file
File diff suppressed because it is too large
Load Diff
151
thirdparty/rr-full/Parse/Win32Registry/Entry.pm
vendored
Normal file
151
thirdparty/rr-full/Parse/Win32Registry/Entry.pm
vendored
Normal file
@ -0,0 +1,151 @@
|
||||
package Parse::Win32Registry::Entry;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Carp;
|
||||
use Parse::Win32Registry::Base qw(:all);
|
||||
|
||||
sub get_regfile {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_regfile};
|
||||
}
|
||||
|
||||
sub get_offset {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_offset};
|
||||
}
|
||||
|
||||
sub get_length {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_length};
|
||||
}
|
||||
|
||||
sub is_allocated {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_allocated};
|
||||
}
|
||||
|
||||
sub get_tag {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_tag};
|
||||
}
|
||||
|
||||
sub as_string {
|
||||
my $self = shift;
|
||||
|
||||
my $tag = $self->{_tag};
|
||||
$tag = 'unidentified entry' if !defined $tag;
|
||||
return "($tag)";
|
||||
}
|
||||
|
||||
sub parse_info {
|
||||
my $self = shift;
|
||||
|
||||
my $info = sprintf '0x%x %s len=0x%x',
|
||||
$self->{_offset},
|
||||
$self->{_tag},
|
||||
$self->{_length};
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
sub unparsed {
|
||||
my $self = shift;
|
||||
|
||||
return hexdump($self->get_raw_bytes, $self->get_offset);
|
||||
}
|
||||
|
||||
sub get_raw_bytes {
|
||||
my $self = shift;
|
||||
|
||||
my $regfile = $self->{_regfile};
|
||||
my $fh = $regfile->get_filehandle;
|
||||
my $offset = $self->{_offset};
|
||||
my $length = $self->{_length};
|
||||
|
||||
if (defined $self->{_header_length}) {
|
||||
$length = $self->{_header_length};
|
||||
}
|
||||
|
||||
sysseek($fh, $offset, 0);
|
||||
my $bytes_read = sysread($fh, my $buffer, $length);
|
||||
if ($bytes_read == $length) {
|
||||
return $buffer;
|
||||
}
|
||||
else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
sub looks_like_key {
|
||||
return UNIVERSAL::isa($_[0], "Parse::Win32Registry::Key");
|
||||
}
|
||||
|
||||
sub looks_like_value {
|
||||
return UNIVERSAL::isa($_[0], "Parse::Win32Registry::Value");
|
||||
}
|
||||
|
||||
sub looks_like_security {
|
||||
return UNIVERSAL::isa($_[0], "Parse::Win32Registry::WinNT::Security");
|
||||
}
|
||||
|
||||
sub _dumpvar {
|
||||
my $self = shift;
|
||||
my $depth = shift || 1;
|
||||
|
||||
my $dumpvar = '';
|
||||
foreach (sort keys %$self) {
|
||||
$dumpvar .= ' ' x ($depth*2);
|
||||
$dumpvar .= "$_ => ";
|
||||
my $var = $self->{$_};
|
||||
if (!defined $var) {
|
||||
$dumpvar .= "undef\n";
|
||||
}
|
||||
elsif (/offset/ || /_id$/ || /^_unk/) {
|
||||
$dumpvar .= sprintf "0x%x\n", $var;
|
||||
}
|
||||
elsif (/_flags$/) {
|
||||
$dumpvar .= sprintf "0x%x (0b%b)\n", $var, $var;
|
||||
}
|
||||
elsif (/length/ || /bytes_used/) {
|
||||
$dumpvar .= sprintf "0x%x (%d)\n", $var, $var;
|
||||
}
|
||||
elsif (/_data$/) {
|
||||
if (length($var) == 0) {
|
||||
$dumpvar .= '(no data)';
|
||||
}
|
||||
else {
|
||||
$dumpvar .= join(' ', unpack('(H2)20', $var));
|
||||
if (length($var) > 20) {
|
||||
$dumpvar .= '...';
|
||||
}
|
||||
}
|
||||
$dumpvar .= "\n";
|
||||
}
|
||||
elsif (/timestamp$/) {
|
||||
$dumpvar .= $var . " (" . iso8601($var) . ")\n";
|
||||
}
|
||||
elsif ($var =~ /^\d+$/) {
|
||||
$dumpvar .= sprintf "%d\n", $var;
|
||||
}
|
||||
elsif (ref($var)) {
|
||||
$dumpvar .= "$var\n"; # stringify object ref
|
||||
}
|
||||
else {
|
||||
$dumpvar .= qq{"$var"};
|
||||
$dumpvar .= ' ';
|
||||
$dumpvar .= Encode::is_utf8($var) ? "(UTF8)" : "(BYTES)";
|
||||
$dumpvar .= "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return $dumpvar;
|
||||
}
|
||||
|
||||
1;
|
66
thirdparty/rr-full/Parse/Win32Registry/File.pm
vendored
Normal file
66
thirdparty/rr-full/Parse/Win32Registry/File.pm
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
package Parse::Win32Registry::File;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
sub get_filehandle {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_filehandle};
|
||||
}
|
||||
|
||||
sub get_filename {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_filename};
|
||||
}
|
||||
|
||||
sub get_length {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_length};
|
||||
}
|
||||
|
||||
sub get_entry_iterator {
|
||||
my $self = shift;
|
||||
|
||||
my $entry_iter;
|
||||
my $block_iter = $self->get_block_iterator;
|
||||
|
||||
return Parse::Win32Registry::Iterator->new(sub {
|
||||
while (1) {
|
||||
if (defined $entry_iter) {
|
||||
my $entry = $entry_iter->();
|
||||
if (defined $entry) {
|
||||
return $entry;
|
||||
}
|
||||
}
|
||||
# entry iterator is undefined or finished
|
||||
my $block = $block_iter->();
|
||||
if (!defined $block) {
|
||||
return; # block iterator finished
|
||||
}
|
||||
$entry_iter = $block->get_entry_iterator;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
# method provided for backwards compatibility
|
||||
sub move_to_first_entry {
|
||||
my $self = shift;
|
||||
|
||||
$self->{_entry_iter} = undef;
|
||||
}
|
||||
|
||||
# method provided for backwards compatibility
|
||||
sub get_next_entry {
|
||||
my $self = shift;
|
||||
|
||||
my $entry_iter = $self->{_entry_iter};
|
||||
if (!defined $entry_iter) {
|
||||
$self->{_entry_iter} = $entry_iter = $self->get_entry_iterator;
|
||||
}
|
||||
return $entry_iter->();
|
||||
}
|
||||
|
||||
1;
|
245
thirdparty/rr-full/Parse/Win32Registry/Key.pm
vendored
Normal file
245
thirdparty/rr-full/Parse/Win32Registry/Key.pm
vendored
Normal file
@ -0,0 +1,245 @@
|
||||
package Parse::Win32Registry::Key;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base qw(Parse::Win32Registry::Entry);
|
||||
|
||||
use Carp;
|
||||
|
||||
sub get_name {
|
||||
my $self = shift;
|
||||
|
||||
# the root key of a windows 95 registry has no defined name
|
||||
# but this should be set to '' when created
|
||||
return $self->{_name};
|
||||
}
|
||||
|
||||
sub get_path {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_key_path};
|
||||
}
|
||||
|
||||
sub _look_up_subkey {
|
||||
my $self = shift;
|
||||
my $subkey_name = shift;
|
||||
|
||||
croak 'Missing subkey name' if !defined $subkey_name;
|
||||
|
||||
foreach my $subkey ($self->get_list_of_subkeys) {
|
||||
if (uc $subkey_name eq uc $subkey->{_name}) {
|
||||
return $subkey;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
sub get_subkey {
|
||||
my $self = shift;
|
||||
my $subkey_path = shift;
|
||||
|
||||
# check for definedness in case key name is '' or '0'
|
||||
croak "Usage: get_subkey('key name')" if !defined $subkey_path;
|
||||
|
||||
my $key = $self;
|
||||
|
||||
# Current path component separator is '\' to match that used in Windows.
|
||||
# split returns nothing if it is given an empty string,
|
||||
# and without a limit of -1 drops trailing empty fields.
|
||||
# The following returns a list with a single zero-length string ("")
|
||||
# for an empty string, as split(/\\/, $subkey_path, -1) returns (),
|
||||
# an empty list.
|
||||
my @path_components = index($subkey_path, "\\") == -1
|
||||
? ($subkey_path)
|
||||
: split(/\\/, $subkey_path, -1);
|
||||
|
||||
my %offsets_seen = ();
|
||||
$offsets_seen{$key->get_offset} = undef;
|
||||
|
||||
foreach my $subkey_name (@path_components) {
|
||||
if (my $subkey = $key->_look_up_subkey($subkey_name)) {
|
||||
if (exists $offsets_seen{$subkey->get_offset}) {
|
||||
return; # found loop
|
||||
}
|
||||
$key = $subkey;
|
||||
$offsets_seen{$key->get_offset} = undef;
|
||||
}
|
||||
else { # subkey name not found, abort look up
|
||||
return;
|
||||
}
|
||||
}
|
||||
return $key;
|
||||
}
|
||||
|
||||
sub get_value {
|
||||
my $self = shift;
|
||||
my $value_name = shift;
|
||||
|
||||
# check for definedness in case value name is '' or '0'
|
||||
croak "Usage: get_value('value name')" if !defined $value_name;
|
||||
|
||||
foreach my $value ($self->get_list_of_values) {
|
||||
if (uc $value_name eq uc $value->{_name}) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub print_summary {
|
||||
my $self = shift;
|
||||
|
||||
print $self->as_string, "\n";
|
||||
}
|
||||
|
||||
sub as_regedit_export {
|
||||
my $self = shift;
|
||||
|
||||
return "[" . $self->{_key_path} . "]\n";
|
||||
}
|
||||
|
||||
sub regenerate_path {
|
||||
my $self = shift;
|
||||
|
||||
# ascend to the root
|
||||
my $key = $self;
|
||||
my @key_names = ($key->get_name);
|
||||
|
||||
my %offsets_seen = ();
|
||||
while (!$key->is_root) {
|
||||
$offsets_seen{$key->get_offset}++;
|
||||
$key = $key->get_parent;
|
||||
if (!defined $key) { # found an undefined parent key
|
||||
unshift @key_names, '(Invalid Parent Key)';
|
||||
last;
|
||||
}
|
||||
if (exists $offsets_seen{$key->get_offset}) { # found loop
|
||||
unshift @key_names, '(Invalid Parent Key)';
|
||||
last;
|
||||
}
|
||||
unshift @key_names, $key->get_name;
|
||||
}
|
||||
|
||||
my $key_path = join('\\', @key_names);
|
||||
$self->{_key_path} = $key_path;
|
||||
return $key_path;
|
||||
}
|
||||
|
||||
sub get_value_data {
|
||||
my $self = shift;
|
||||
my $value_name = shift;
|
||||
|
||||
croak "Usage: get_value_data('value name')" if !defined $value_name;
|
||||
|
||||
if (my $value = $self->get_value($value_name)) {
|
||||
return $value->get_data;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
sub get_mru_list_of_values {
|
||||
my $self = shift;
|
||||
|
||||
my @values = ();
|
||||
|
||||
if (my $mrulist = $self->get_value('MRUList')) {
|
||||
foreach my $ch (split(//, $mrulist->get_data)) {
|
||||
if (my $value = $self->get_value($ch)) {
|
||||
push @values, $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
elsif (my $mrulistex = $self->get_value('MRUListEx')) {
|
||||
foreach my $item (unpack('V*', $mrulistex->get_data)) {
|
||||
last if $item == 0xffffffff;
|
||||
if (my $value = $self->get_value($item)) {
|
||||
push @values, $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return @values;
|
||||
}
|
||||
|
||||
sub get_list_of_subkeys {
|
||||
my $self = shift;
|
||||
|
||||
my $subkey_iter = $self->get_subkey_iterator;
|
||||
my @subkeys;
|
||||
while (my $subkey = $subkey_iter->()) {
|
||||
push @subkeys, $subkey;
|
||||
}
|
||||
return @subkeys;
|
||||
}
|
||||
|
||||
sub get_list_of_values {
|
||||
my $self = shift;
|
||||
|
||||
my $value_iter = $self->get_value_iterator;
|
||||
my @values;
|
||||
while (my $value = $value_iter->()) {
|
||||
push @values, $value;
|
||||
}
|
||||
return @values;
|
||||
}
|
||||
|
||||
sub get_subtree_iterator {
|
||||
my $self = shift;
|
||||
|
||||
my @start_keys = ($self);
|
||||
push my (@subkey_iters), Parse::Win32Registry::Iterator->new(sub {
|
||||
return shift @start_keys;
|
||||
});
|
||||
my $value_iter;
|
||||
my $key; # used to remember key while iterating values
|
||||
|
||||
return Parse::Win32Registry::Iterator->new(sub {
|
||||
if (defined $value_iter && wantarray) {
|
||||
my $value = $value_iter->();
|
||||
if (defined $value) {
|
||||
return ($key, $value);
|
||||
}
|
||||
# $value_iter finished, so fetch a new one
|
||||
# from the (current) $subkey_iter[-1]
|
||||
}
|
||||
while (@subkey_iters > 0) {
|
||||
$key = $subkey_iters[-1]->(); # depth-first
|
||||
if (defined $key) {
|
||||
push @subkey_iters, $key->get_subkey_iterator;
|
||||
$value_iter = $key->get_value_iterator;
|
||||
return $key;
|
||||
}
|
||||
pop @subkey_iters; # $subkey_iter finished, so remove it
|
||||
}
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
sub walk {
|
||||
my $self = shift;
|
||||
my $key_enter_func = shift;
|
||||
my $value_func = shift;
|
||||
my $key_leave_func = shift;
|
||||
|
||||
if (!defined $key_enter_func &&
|
||||
!defined $value_func &&
|
||||
!defined $key_leave_func) {
|
||||
$key_enter_func = sub { print "+ ", $_[0]->get_path, "\n"; };
|
||||
$value_func = sub { print " '", $_[0]->get_name, "'\n"; };
|
||||
$key_leave_func = sub { print "- ", $_[0]->get_path, "\n"; };
|
||||
}
|
||||
|
||||
$key_enter_func->($self) if ref $key_enter_func eq 'CODE';
|
||||
|
||||
foreach my $value ($self->get_list_of_values) {
|
||||
$value_func->($value) if ref $value_func eq 'CODE';
|
||||
}
|
||||
|
||||
foreach my $subkey ($self->get_list_of_subkeys) {
|
||||
$subkey->walk($key_enter_func, $value_func, $key_leave_func);
|
||||
}
|
||||
|
||||
$key_leave_func->($self) if ref $key_leave_func eq 'CODE';
|
||||
}
|
||||
|
||||
1;
|
101
thirdparty/rr-full/Parse/Win32Registry/Value.pm
vendored
Normal file
101
thirdparty/rr-full/Parse/Win32Registry/Value.pm
vendored
Normal file
@ -0,0 +1,101 @@
|
||||
package Parse::Win32Registry::Value;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base qw(Parse::Win32Registry::Entry);
|
||||
|
||||
use Carp;
|
||||
use Parse::Win32Registry::Base qw(:all);
|
||||
|
||||
sub get_name {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_name};
|
||||
}
|
||||
|
||||
sub get_type {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_type};
|
||||
}
|
||||
|
||||
our @Types = qw(
|
||||
REG_NONE
|
||||
REG_SZ
|
||||
REG_EXPAND_SZ
|
||||
REG_BINARY
|
||||
REG_DWORD
|
||||
REG_DWORD_BIG_ENDIAN
|
||||
REG_LINK
|
||||
REG_MULTI_SZ
|
||||
REG_RESOURCE_LIST
|
||||
REG_FULL_RESOURCE_DESCRIPTOR
|
||||
REG_RESOURCE_REQUIREMENTS_LIST
|
||||
REG_QWORD
|
||||
);
|
||||
|
||||
sub get_type_as_string {
|
||||
my $self = shift;
|
||||
|
||||
my $type = $self->get_type;
|
||||
if (exists $Types[$type]) {
|
||||
return $Types[$type];
|
||||
}
|
||||
else {
|
||||
# Return unrecognised types as REG_<number>
|
||||
# REGEDIT displays them as formatted hex numbers, e.g. 0x1f4
|
||||
return "REG_$type";
|
||||
}
|
||||
}
|
||||
|
||||
sub get_data_as_string {
|
||||
my $self = shift;
|
||||
|
||||
my $type = $self->get_type;
|
||||
my $data = $self->get_data;
|
||||
if (!defined($data)) {
|
||||
return '(invalid data)';
|
||||
}
|
||||
elsif (length($data) == 0) {
|
||||
return '(no data)';
|
||||
}
|
||||
elsif ($type == REG_SZ || $type == REG_EXPAND_SZ) {
|
||||
return $data;
|
||||
}
|
||||
elsif ($type == REG_MULTI_SZ) {
|
||||
my @data = $self->get_data;
|
||||
my $i = 0;
|
||||
return join(' ', map { "[" . $i++ . "] $_" } @data);
|
||||
}
|
||||
elsif ($type == REG_DWORD || $type == REG_DWORD_BIG_ENDIAN) {
|
||||
return sprintf '0x%08x (%u)', $data, $data;
|
||||
}
|
||||
else {
|
||||
return join(' ', unpack('(H2)*', $data));
|
||||
}
|
||||
}
|
||||
|
||||
sub get_raw_data {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_data};
|
||||
}
|
||||
|
||||
sub as_string {
|
||||
my $self = shift;
|
||||
|
||||
my $name = $self->get_name;
|
||||
$name = '(Default)' if $name eq '';
|
||||
my $type_as_string = $self->get_type_as_string;
|
||||
my $data_as_string = $self->get_data_as_string;
|
||||
return "$name ($type_as_string) = $data_as_string";
|
||||
}
|
||||
|
||||
sub print_summary {
|
||||
my $self = shift;
|
||||
|
||||
print $self->as_string, "\n";
|
||||
}
|
||||
|
||||
1;
|
540
thirdparty/rr-full/Parse/Win32Registry/Win95/File.pm
vendored
Normal file
540
thirdparty/rr-full/Parse/Win32Registry/Win95/File.pm
vendored
Normal file
@ -0,0 +1,540 @@
|
||||
package Parse::Win32Registry::Win95::File;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base qw(Parse::Win32Registry::File);
|
||||
|
||||
use Carp;
|
||||
use File::Basename;
|
||||
use Parse::Win32Registry::Base qw(:all);
|
||||
use Parse::Win32Registry::Win95::Key;
|
||||
|
||||
use constant CREG_HEADER_LENGTH => 0x20;
|
||||
use constant OFFSET_TO_RGKN_BLOCK => 0x20;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $filename = shift or croak 'No filename specified';
|
||||
|
||||
open my $fh, '<', $filename or croak "Unable to open '$filename': $!";
|
||||
|
||||
# CREG Header
|
||||
# 0x00 dword = 'CREG' signature
|
||||
# 0x04
|
||||
# 0x08 dword = offset to first rgdb block
|
||||
# 0x0c
|
||||
# 0x10 word = number of rgdb blocks
|
||||
|
||||
my $bytes_read = sysread($fh, my $creg_header, CREG_HEADER_LENGTH);
|
||||
if ($bytes_read != CREG_HEADER_LENGTH) {
|
||||
warnf('Could not read registry file header');
|
||||
return;
|
||||
}
|
||||
|
||||
my ($creg_sig,
|
||||
$offset_to_first_rgdb_block,
|
||||
$num_rgdb_blocks) = unpack('a4x4Vx4v', $creg_header);
|
||||
|
||||
if ($creg_sig ne 'CREG') {
|
||||
warnf('Invalid registry file signature');
|
||||
return;
|
||||
}
|
||||
|
||||
my $self = {};
|
||||
$self->{_filehandle} = $fh;
|
||||
$self->{_filename} = $filename;
|
||||
$self->{_length} = (stat $fh)[7];
|
||||
$self->{_offset_to_first_rgdb_block} = $offset_to_first_rgdb_block;
|
||||
$self->{_num_rgdb_blocks} = $num_rgdb_blocks;
|
||||
bless $self, $class;
|
||||
|
||||
# get_rgkn will cache the rgkn block for subsequent calls
|
||||
my $rgkn_block = $self->get_rgkn;
|
||||
return if !defined $rgkn_block; # warning will already have been made
|
||||
|
||||
# Index the rgdb entries by id for faster look up
|
||||
$self->_index_rgdb_entries;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub get_timestamp {
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub get_timestamp_as_string {
|
||||
return iso8601(undef);
|
||||
}
|
||||
|
||||
sub get_embedded_filename {
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub get_root_key {
|
||||
my $self = shift;
|
||||
|
||||
return $self->get_rgkn->get_root_key;
|
||||
}
|
||||
|
||||
sub get_virtual_root_key {
|
||||
my $self = shift;
|
||||
my $fake_root = shift;
|
||||
|
||||
my $root_key = $self->get_root_key;
|
||||
return if !defined $root_key;
|
||||
|
||||
if (!defined $fake_root) {
|
||||
# guess virtual root from filename
|
||||
my $filename = basename $self->{_filename};
|
||||
|
||||
if ($filename =~ /USER/i) {
|
||||
$fake_root = 'HKEY_USERS';
|
||||
}
|
||||
elsif ($filename =~ /SYSTEM/i) {
|
||||
$fake_root = 'HKEY_LOCAL_MACHINE';
|
||||
}
|
||||
else {
|
||||
$fake_root = 'HKEY_UNKNOWN';
|
||||
}
|
||||
}
|
||||
|
||||
$root_key->{_name} = $fake_root;
|
||||
$root_key->{_key_path} = $fake_root;
|
||||
|
||||
return $root_key;
|
||||
}
|
||||
|
||||
sub _index_rgdb_entries {
|
||||
my $self = shift;
|
||||
|
||||
my %index = ();
|
||||
|
||||
# Build index of rgdb key entries
|
||||
# Entries are only included if $key_block_num matches $rgdb_block_num
|
||||
my $rgdb_block_num = 0;
|
||||
my $rgdb_iter = $self->get_rgdb_iterator;
|
||||
while (my $rgdb = $rgdb_iter->()) {
|
||||
my $rgdb_key_iter = $rgdb->get_key_iterator;
|
||||
while (my $rgdb_key = $rgdb_key_iter->()) {
|
||||
my $key_id = $rgdb_key->{_id};
|
||||
my $key_block_num = $key_id >> 16;
|
||||
if ($rgdb_block_num == $key_block_num) {
|
||||
$index{$key_id} = $rgdb_key;
|
||||
}
|
||||
}
|
||||
$rgdb_block_num++;
|
||||
}
|
||||
|
||||
$self->{_rgdb_index} = \%index;
|
||||
}
|
||||
|
||||
sub _dump_rgdb_index {
|
||||
my $self = shift;
|
||||
|
||||
my $rgdb_index = $self->{_rgdb_index};
|
||||
|
||||
foreach my $key_id (sort { $a <=> $b } keys %$rgdb_index) {
|
||||
my $rgdb_key = $rgdb_index->{$key_id};
|
||||
printf qq{id=0x%x 0x%x,%d/%d "%s" vals=%d\n},
|
||||
$key_id,
|
||||
$rgdb_key->{_offset},
|
||||
$rgdb_key->{_length_used},
|
||||
$rgdb_key->{_length},
|
||||
$rgdb_key->{_name},
|
||||
$rgdb_key->{_num_values};
|
||||
}
|
||||
}
|
||||
|
||||
sub get_rgkn {
|
||||
my $self = shift;
|
||||
|
||||
# Return cached rgkn block if present
|
||||
if (defined $self->{_rgkn}) {
|
||||
return $self->{_rgkn};
|
||||
}
|
||||
|
||||
my $offset = OFFSET_TO_RGKN_BLOCK;
|
||||
my $rgkn_block = Parse::Win32Registry::Win95::RGKN->new($self, $offset);
|
||||
$self->{_rgkn} = $rgkn_block;
|
||||
return $rgkn_block;
|
||||
}
|
||||
|
||||
sub get_rgdb_iterator {
|
||||
my $self = shift;
|
||||
|
||||
my $offset_to_next_rgdb_block = $self->{_offset_to_first_rgdb_block};
|
||||
my $num_rgdb_blocks = $self->{_num_rgdb_blocks};
|
||||
|
||||
my $end_of_file = $self->{_length};
|
||||
|
||||
my $rgdb_block_num = 0;
|
||||
|
||||
return Parse::Win32Registry::Iterator->new(sub {
|
||||
if ($offset_to_next_rgdb_block > $end_of_file) {
|
||||
return; # no more rgdb blocks
|
||||
}
|
||||
if ($rgdb_block_num >= $num_rgdb_blocks) {
|
||||
return; # no more rgdb blocks
|
||||
}
|
||||
$rgdb_block_num++;
|
||||
if (my $rgdb_block = Parse::Win32Registry::Win95::RGDB->new($self,
|
||||
$offset_to_next_rgdb_block))
|
||||
{
|
||||
return unless $rgdb_block->get_length > 0;
|
||||
$offset_to_next_rgdb_block += $rgdb_block->get_length;
|
||||
return $rgdb_block;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
sub get_block_iterator {
|
||||
my $self = shift;
|
||||
|
||||
my $rgdb_iter;
|
||||
|
||||
return Parse::Win32Registry::Iterator->new(sub {
|
||||
if (!defined $rgdb_iter) {
|
||||
$rgdb_iter = $self->get_rgdb_iterator;
|
||||
return $self->get_rgkn;
|
||||
}
|
||||
return $rgdb_iter->();
|
||||
});
|
||||
}
|
||||
|
||||
*get_hbin_iterator = \&get_block_iterator;
|
||||
|
||||
|
||||
package Parse::Win32Registry::Win95::RGKN;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base qw(Parse::Win32Registry::Entry);
|
||||
|
||||
use Carp;
|
||||
use Parse::Win32Registry::Base qw(:all);
|
||||
|
||||
use constant RGKN_HEADER_LENGTH => 0x20;
|
||||
use constant OFFSET_TO_RGKN_BLOCK => 0x20;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $regfile = shift;
|
||||
my $offset = shift || OFFSET_TO_RGKN_BLOCK;
|
||||
|
||||
croak 'Missing registry file' if !defined $regfile;
|
||||
croak 'Missing offset' if !defined $offset;
|
||||
|
||||
my $fh = $regfile->get_filehandle;
|
||||
|
||||
# RGKN Block Header
|
||||
# 0x0 dword = 'RGKN' signature
|
||||
# 0x4 dword = length of rgkn block
|
||||
# 0x8 dword = offset to root key entry (relative to start of rgkn block)
|
||||
|
||||
sysseek($fh, $offset, 0);
|
||||
my $bytes_read = sysread($fh, my $rgkn_header, RGKN_HEADER_LENGTH);
|
||||
if ($bytes_read != RGKN_HEADER_LENGTH) {
|
||||
warnf('Could not read RGKN header at 0x%x', $offset);
|
||||
return;
|
||||
}
|
||||
|
||||
my ($sig,
|
||||
$rgkn_block_length,
|
||||
$offset_to_root_key) = unpack('a4VV', $rgkn_header);
|
||||
|
||||
if ($sig ne 'RGKN') {
|
||||
warnf('Invalid RGKN block signature at 0x%x', $offset);
|
||||
return;
|
||||
}
|
||||
|
||||
$offset_to_root_key += $offset;
|
||||
|
||||
my $self = {};
|
||||
$self->{_regfile} = $regfile;
|
||||
$self->{_offset} = $offset;
|
||||
$self->{_length} = $rgkn_block_length;
|
||||
$self->{_header_length} = RGKN_HEADER_LENGTH;
|
||||
$self->{_allocated} = 1;
|
||||
$self->{_tag} = 'rgkn block';
|
||||
$self->{_offset_to_root_key} = $offset_to_root_key;
|
||||
bless $self, $class;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub get_root_key {
|
||||
my $self = shift;
|
||||
|
||||
my $regfile = $self->{_regfile};
|
||||
my $offset_to_root_key = $self->{_offset_to_root_key};
|
||||
|
||||
my $root_key = Parse::Win32Registry::Win95::Key->new($regfile,
|
||||
$offset_to_root_key);
|
||||
return $root_key;
|
||||
}
|
||||
|
||||
sub get_entry_iterator {
|
||||
my $self = shift;
|
||||
|
||||
my $root_key = $self->get_root_key;
|
||||
|
||||
# In the unlikely event there is no root key, return an empty iterator
|
||||
if (defined $root_key) {
|
||||
return $root_key->get_subtree_iterator;
|
||||
}
|
||||
else {
|
||||
return Parse::Win32Registry::Iterator->new(sub {});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
package Parse::Win32Registry::Win95::RGDB;
|
||||
|
||||
use base qw(Parse::Win32Registry::Entry);
|
||||
|
||||
use Carp;
|
||||
use Parse::Win32Registry::Base qw(:all);
|
||||
|
||||
use constant RGDB_HEADER_LENGTH => 0x20;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $regfile = shift;
|
||||
my $offset = shift;
|
||||
|
||||
croak 'Missing registry file' if !defined $regfile;
|
||||
croak 'Missing offset' if !defined $offset;
|
||||
|
||||
my $fh = $regfile->get_filehandle;
|
||||
|
||||
# RGDB Block Header
|
||||
# 0x0 dword = 'RDGB' signature
|
||||
# 0x4 dword = length of rgdb block
|
||||
|
||||
sysseek($fh, $offset, 0);
|
||||
my $bytes_read = sysread($fh, my $rgdb_header, RGDB_HEADER_LENGTH);
|
||||
if ($bytes_read != RGDB_HEADER_LENGTH) {
|
||||
return;
|
||||
}
|
||||
|
||||
my ($sig,
|
||||
$rgdb_block_length) = unpack('a4V', $rgdb_header);
|
||||
|
||||
if ($sig ne 'RGDB') {
|
||||
return;
|
||||
}
|
||||
|
||||
my $self = {};
|
||||
$self->{_regfile} = $regfile;
|
||||
$self->{_offset} = $offset;
|
||||
$self->{_length} = $rgdb_block_length;
|
||||
$self->{_header_length} = RGDB_HEADER_LENGTH;
|
||||
$self->{_allocated} = 1;
|
||||
$self->{_tag} = 'rgdb block';
|
||||
bless $self, $class;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub get_key_iterator {
|
||||
my $self = shift;
|
||||
|
||||
my $regfile = $self->{_regfile};
|
||||
my $offset = $self->{_offset};
|
||||
my $length = $self->{_length};
|
||||
|
||||
my $offset_to_next_rgdb_key = $offset + RGDB_HEADER_LENGTH;
|
||||
my $end_of_rgdb_block = $offset + $length;
|
||||
|
||||
return Parse::Win32Registry::Iterator->new(sub {
|
||||
if ($offset_to_next_rgdb_key >= $end_of_rgdb_block) {
|
||||
return;
|
||||
}
|
||||
if (my $rgdb_key = Parse::Win32Registry::Win95::RGDBKey->new($regfile,
|
||||
$offset_to_next_rgdb_key))
|
||||
{
|
||||
return unless $rgdb_key->get_length > 0;
|
||||
$offset_to_next_rgdb_key += $rgdb_key->get_length;
|
||||
|
||||
# Check rgdb key has not run past end of rgdb block
|
||||
if ($offset_to_next_rgdb_key > $end_of_rgdb_block) {
|
||||
return;
|
||||
}
|
||||
return $rgdb_key;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
sub get_entry_iterator {
|
||||
my $self = shift;
|
||||
|
||||
my $value_iter;
|
||||
my $key_iter = $self->get_key_iterator;
|
||||
|
||||
return Parse::Win32Registry::Iterator->new(sub {
|
||||
if (defined $value_iter) {
|
||||
my $value = $value_iter->();
|
||||
if (defined $value) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
my $key = $key_iter->();
|
||||
if (!defined $key) {
|
||||
return; # key iterator finished
|
||||
}
|
||||
|
||||
$value_iter = $key->get_value_iterator;
|
||||
return $key;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
package Parse::Win32Registry::Win95::RGDBKey;
|
||||
|
||||
use base qw(Parse::Win32Registry::Entry);
|
||||
|
||||
use Carp;
|
||||
use Encode;
|
||||
use Parse::Win32Registry::Base qw(:all);
|
||||
|
||||
use constant RGDB_ENTRY_HEADER_LENGTH => 0x14;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $regfile = shift;
|
||||
my $offset = shift;
|
||||
|
||||
croak 'Missing registry file' if !defined $regfile;
|
||||
croak 'Missing offset' if !defined $offset;
|
||||
|
||||
my $fh = $regfile->get_filehandle;
|
||||
|
||||
# RGDB Key Entry
|
||||
# 0x00 dword = length of rgdb entry / offset to next rgdb entry
|
||||
# (this length includes any following value entries)
|
||||
# 0x04 dword = id (top word = block num, bottom word = id)
|
||||
# 0x08 dword = bytes used (unpacked, but not used)
|
||||
# 0x0c word = key name length
|
||||
# 0x0e word = number of values
|
||||
# 0x10 dword
|
||||
# 0x14 = key name [for key name length bytes]
|
||||
# followed immediately by any RGDB Value Entries belonging to this key
|
||||
|
||||
sysseek($fh, $offset, 0);
|
||||
my $bytes_read = sysread($fh, my $rgdb_key_entry, RGDB_ENTRY_HEADER_LENGTH);
|
||||
if ($bytes_read != RGDB_ENTRY_HEADER_LENGTH) {
|
||||
return;
|
||||
}
|
||||
|
||||
my ($length,
|
||||
$key_id,
|
||||
$length_used,
|
||||
$name_length,
|
||||
$num_values) = unpack('VVVvv', $rgdb_key_entry);
|
||||
|
||||
$bytes_read = sysread($fh, my $name, $name_length);
|
||||
if ($bytes_read != $name_length) {
|
||||
return;
|
||||
}
|
||||
$name = decode($Parse::Win32Registry::Base::CODEPAGE, $name);
|
||||
|
||||
# Calculate the length of the entry's key header
|
||||
my $header_length = RGDB_ENTRY_HEADER_LENGTH + $name_length;
|
||||
|
||||
# Check for invalid/unused entries
|
||||
if ($key_id == 0xffffffff || $length_used == 0xffffffff
|
||||
|| $header_length > $length)
|
||||
{
|
||||
$name = '';
|
||||
$header_length = RGDB_ENTRY_HEADER_LENGTH;
|
||||
}
|
||||
|
||||
my $self = {};
|
||||
$self->{_regfile} = $regfile;
|
||||
$self->{_offset} = $offset;
|
||||
$self->{_length} = $length;
|
||||
$self->{_length_used} = $length_used;
|
||||
$self->{_header_length} = $header_length;
|
||||
$self->{_allocated} = 1;
|
||||
$self->{_tag} = 'rgdb key';
|
||||
$self->{_id} = $key_id;
|
||||
$self->{_name} = $name;
|
||||
$self->{_name_length} = $name_length;
|
||||
$self->{_num_values} = $num_values;
|
||||
bless $self, $class;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub get_name {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_name};
|
||||
}
|
||||
|
||||
sub parse_info {
|
||||
my $self = shift;
|
||||
|
||||
my $info = sprintf '0x%x rgdb key len=0x%x/0x%x "%s" id=0x%x vals=%d',
|
||||
$self->{_offset},
|
||||
$self->{_length_used},
|
||||
$self->{_length},
|
||||
$self->{_name},
|
||||
$self->{_id},
|
||||
$self->{_num_values};
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
sub get_value_iterator {
|
||||
my $self = shift;
|
||||
|
||||
my $regfile = $self->{_regfile};
|
||||
|
||||
my $num_values_remaining = $self->{_num_values};
|
||||
|
||||
my $offset = $self->{_offset};
|
||||
|
||||
# offset_to_next_rgdb_value can only be set to a valid offset
|
||||
# if num_values_remaining > 0
|
||||
my $offset_to_next_rgdb_value = 0xffffffff;
|
||||
if ($num_values_remaining > 0) {
|
||||
$offset_to_next_rgdb_value = $offset
|
||||
+ $self->{_header_length};
|
||||
}
|
||||
|
||||
my $end_of_rgdb_key = $offset + $self->{_length};
|
||||
|
||||
# don't attempt to return values if id is invalid...
|
||||
if ($self->{_id} == 0xffffffff) {
|
||||
$num_values_remaining = 0;
|
||||
}
|
||||
|
||||
return Parse::Win32Registry::Iterator->new(sub {
|
||||
if ($num_values_remaining-- <= 0) {
|
||||
return;
|
||||
}
|
||||
if ($offset_to_next_rgdb_value == 0xffffffff) {
|
||||
return;
|
||||
}
|
||||
if ($offset_to_next_rgdb_value > $end_of_rgdb_key) {
|
||||
return;
|
||||
}
|
||||
if (my $value = Parse::Win32Registry::Win95::Value->new($regfile,
|
||||
$offset_to_next_rgdb_value))
|
||||
{
|
||||
return unless $value->get_length > 0;
|
||||
$offset_to_next_rgdb_value += $value->get_length;
|
||||
return $value;
|
||||
}
|
||||
else {
|
||||
return; # no more values
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
1;
|
||||
|
207
thirdparty/rr-full/Parse/Win32Registry/Win95/Key.pm
vendored
Normal file
207
thirdparty/rr-full/Parse/Win32Registry/Win95/Key.pm
vendored
Normal file
@ -0,0 +1,207 @@
|
||||
package Parse::Win32Registry::Win95::Key;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base qw(Parse::Win32Registry::Key);
|
||||
|
||||
use Carp;
|
||||
use Parse::Win32Registry::Base qw(:all);
|
||||
use Parse::Win32Registry::Win95::Value;
|
||||
|
||||
use constant RGKN_ENTRY_LENGTH => 0x1c;
|
||||
use constant OFFSET_TO_RGKN_BLOCK => 0x20;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $regfile = shift;
|
||||
my $offset = shift; # offset to RGKN key entry relative to start of RGKN
|
||||
my $parent_key_path = shift; # parent key path (optional)
|
||||
|
||||
croak 'Missing registry file' if !defined $regfile;
|
||||
croak 'Missing offset' if !defined $offset;
|
||||
|
||||
my $fh = $regfile->get_filehandle;
|
||||
|
||||
# RGKN Key Entry
|
||||
# 0x00 dword
|
||||
# 0x04 dword
|
||||
# 0x08 dword
|
||||
# 0x0c dword = offset to parent RGKN entry
|
||||
# 0x10 dword = offset to first child RGKN entry
|
||||
# 0x14 dword = offset to next sibling RGKN entry
|
||||
# 0x18 dword = entry id of RGDB entry
|
||||
|
||||
# Extracted offsets are relative to the start of the RGKN block
|
||||
|
||||
# Any offset of 0xffffffff marks the end of a list.
|
||||
# An entry id of 0xffffffff means the RGKN entry has no RGDB entry.
|
||||
# This occurs for the root key of the registry file.
|
||||
|
||||
sysseek($fh, $offset, 0);
|
||||
my $bytes_read = sysread($fh, my $rgkn_entry, RGKN_ENTRY_LENGTH);
|
||||
if ($bytes_read != RGKN_ENTRY_LENGTH) {
|
||||
warnf('Could not read RGKN key at 0x%x', $offset);
|
||||
return;
|
||||
}
|
||||
|
||||
my ($offset_to_parent,
|
||||
$offset_to_first_child,
|
||||
$offset_to_next_sibling,
|
||||
$key_id) = unpack('x12VVVV', $rgkn_entry);
|
||||
|
||||
$offset_to_parent += OFFSET_TO_RGKN_BLOCK
|
||||
if $offset_to_parent != 0xffffffff;
|
||||
$offset_to_first_child += OFFSET_TO_RGKN_BLOCK
|
||||
if $offset_to_first_child != 0xffffffff;
|
||||
$offset_to_next_sibling += OFFSET_TO_RGKN_BLOCK
|
||||
if $offset_to_next_sibling != 0xffffffff;
|
||||
|
||||
my $self = {};
|
||||
$self->{_regfile} = $regfile;
|
||||
$self->{_offset} = $offset;
|
||||
$self->{_length} = RGKN_ENTRY_LENGTH;
|
||||
$self->{_allocated} = 1;
|
||||
$self->{_tag} = 'rgkn key';
|
||||
$self->{_offset_to_parent} = $offset_to_parent;
|
||||
$self->{_offset_to_first_child} = $offset_to_first_child;
|
||||
$self->{_offset_to_next_sibling} = $offset_to_next_sibling;
|
||||
$self->{_id} = $key_id;
|
||||
bless $self, $class;
|
||||
|
||||
# Look up corresponding rgdb entry
|
||||
my $index = $regfile->{_rgdb_index};
|
||||
croak 'Missing rgdb index' if !defined $index;
|
||||
if (exists $index->{$key_id}) {
|
||||
my $rgdb_key = $index->{$key_id};
|
||||
$self->{_rgdb_key} = $rgdb_key;
|
||||
$self->{_name} = $rgdb_key->get_name;
|
||||
}
|
||||
else {
|
||||
$self->{_name} = '';
|
||||
# Only the root key should have no matching RGDB entry
|
||||
if (!$self->is_root) {
|
||||
warnf('Could not find RGDB entry for RGKN key at 0x%x', $offset);
|
||||
}
|
||||
}
|
||||
|
||||
my $name = $self->{_name};
|
||||
$self->{_key_path} = defined($parent_key_path)
|
||||
? "$parent_key_path\\$name"
|
||||
: $name;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub get_timestamp {
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub get_timestamp_as_string {
|
||||
return iso8601(undef);
|
||||
}
|
||||
|
||||
sub get_class_name {
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub is_root {
|
||||
my $self = shift;
|
||||
|
||||
my $offset = $self->{_offset};
|
||||
my $regfile = $self->{_regfile};
|
||||
|
||||
my $rgkn_block = $regfile->get_rgkn;
|
||||
my $offset_to_root_key = $rgkn_block->{_offset_to_root_key};
|
||||
|
||||
# This gives better results than checking id == 0xffffffff
|
||||
return $offset == $offset_to_root_key;
|
||||
}
|
||||
|
||||
sub get_parent {
|
||||
my $self = shift;
|
||||
|
||||
my $regfile = $self->{_regfile};
|
||||
my $offset_to_parent = $self->{_offset_to_parent};
|
||||
my $key_path = $self->{_key_path};
|
||||
|
||||
return if $self->is_root;
|
||||
|
||||
my $grandparent_key_path;
|
||||
my @keys = split(/\\/, $key_path, -1);
|
||||
if (@keys > 2) {
|
||||
$grandparent_key_path = join("\\", @keys[0..$#keys-2]);
|
||||
}
|
||||
|
||||
return Parse::Win32Registry::Win95::Key->new($regfile,
|
||||
$offset_to_parent,
|
||||
$grandparent_key_path);
|
||||
}
|
||||
|
||||
sub get_security {
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub as_string {
|
||||
my $self = shift;
|
||||
|
||||
return $self->get_path;
|
||||
}
|
||||
|
||||
sub parse_info {
|
||||
my $self = shift;
|
||||
|
||||
my $info = sprintf '0x%x rgkn key len=0x%x par=0x%x,child=0x%x,next=0x%x id=0x%x',
|
||||
$self->{_offset},
|
||||
$self->{_length},
|
||||
$self->{_offset_to_parent},
|
||||
$self->{_offset_to_first_child},
|
||||
$self->{_offset_to_next_sibling},
|
||||
$self->{_id};
|
||||
return $info;
|
||||
}
|
||||
|
||||
sub get_subkey_iterator {
|
||||
my $self = shift;
|
||||
|
||||
my $regfile = $self->{_regfile};
|
||||
my $key_path = $self->{_key_path};
|
||||
|
||||
my $offset_to_next_key = $self->{_offset_to_first_child};
|
||||
|
||||
my $end_of_file = $regfile->get_length;
|
||||
my $rgkn_block = $regfile->get_rgkn;
|
||||
my $end_of_rgkn_block = $rgkn_block->get_offset + $rgkn_block->get_length;
|
||||
|
||||
return Parse::Win32Registry::Iterator->new(sub {
|
||||
if ($offset_to_next_key == 0xffffffff) {
|
||||
return; # no more subkeys
|
||||
}
|
||||
if ($offset_to_next_key > $end_of_rgkn_block) {
|
||||
return;
|
||||
}
|
||||
if (my $key = Parse::Win32Registry::Win95::Key->new($regfile,
|
||||
$offset_to_next_key, $key_path))
|
||||
{
|
||||
$offset_to_next_key = $key->{_offset_to_next_sibling};
|
||||
return $key;
|
||||
}
|
||||
else {
|
||||
return; # no more subkeys
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
sub get_value_iterator {
|
||||
my $self = shift;
|
||||
|
||||
my $rgdb_key = $self->{_rgdb_key};
|
||||
if (defined $rgdb_key) {
|
||||
return $rgdb_key->get_value_iterator;
|
||||
}
|
||||
else {
|
||||
return Parse::Win32Registry::Iterator->new(sub {});
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
177
thirdparty/rr-full/Parse/Win32Registry/Win95/Value.pm
vendored
Normal file
177
thirdparty/rr-full/Parse/Win32Registry/Win95/Value.pm
vendored
Normal file
@ -0,0 +1,177 @@
|
||||
package Parse::Win32Registry::Win95::Value;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base qw(Parse::Win32Registry::Value);
|
||||
|
||||
use Carp;
|
||||
use Encode;
|
||||
use Parse::Win32Registry::Base qw(:all);
|
||||
|
||||
use constant RGDB_VALUE_HEADER_LENGTH => 0xc;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $regfile = shift;
|
||||
my $offset = shift; # offset to RGDB value entry
|
||||
|
||||
croak 'Missing registry file' if !defined $regfile;
|
||||
croak 'Missing offset' if !defined $offset;
|
||||
|
||||
my $fh = $regfile->get_filehandle;
|
||||
|
||||
# RGDB Value Entry
|
||||
# 0x00 dword = value type
|
||||
# 0x04
|
||||
# 0x08 word = value name length
|
||||
# 0x0a word = value data length
|
||||
# 0x0c = value name [for name length bytes]
|
||||
# + value data [for data length bytes]
|
||||
# Value type may just be a word, not a dword;
|
||||
# following word always appears to be zero.
|
||||
|
||||
sysseek($fh, $offset, 0);
|
||||
my $bytes_read = sysread($fh, my $rgdb_value_entry,
|
||||
RGDB_VALUE_HEADER_LENGTH);
|
||||
if ($bytes_read != RGDB_VALUE_HEADER_LENGTH) {
|
||||
warnf('Could not read RGDB value at 0x%x', $offset);
|
||||
return;
|
||||
}
|
||||
|
||||
my ($type,
|
||||
$name_length,
|
||||
$data_length) = unpack('Vx4vv', $rgdb_value_entry);
|
||||
|
||||
$bytes_read = sysread($fh, my $name, $name_length);
|
||||
if ($bytes_read != $name_length) {
|
||||
warnf('Could not read name for RGDB value at 0x%x', $offset);
|
||||
return;
|
||||
}
|
||||
$name = decode($Parse::Win32Registry::Base::CODEPAGE, $name);
|
||||
|
||||
$bytes_read = sysread($fh, my $data, $data_length);
|
||||
if ($bytes_read != $data_length) {
|
||||
warnf('Could not read data for RGDB value at 0x%x', $offset);
|
||||
return;
|
||||
}
|
||||
|
||||
my $self = {};
|
||||
$self->{_regfile} = $regfile;
|
||||
$self->{_offset} = $offset;
|
||||
$self->{_length} = RGDB_VALUE_HEADER_LENGTH + $name_length + $data_length;
|
||||
$self->{_allocated} = 1;
|
||||
$self->{_tag} = 'rgdb value';
|
||||
$self->{_name} = $name;
|
||||
$self->{_name_length} = $name_length;
|
||||
$self->{_type} = $type;
|
||||
$self->{_data} = $data;
|
||||
$self->{_data_length} = $data_length;
|
||||
bless $self, $class;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub get_data {
|
||||
my $self = shift;
|
||||
|
||||
my $type = $self->get_type;
|
||||
|
||||
my $data = $self->{_data};
|
||||
return if !defined $data; # actually, Win95 value data is always defined
|
||||
|
||||
# apply decoding to appropriate data types
|
||||
if ($type == REG_DWORD) {
|
||||
if (length($data) == 4) {
|
||||
$data = unpack('V', $data);
|
||||
}
|
||||
else {
|
||||
# incorrect length for dword data
|
||||
$data = undef;
|
||||
}
|
||||
}
|
||||
elsif ($type == REG_DWORD_BIG_ENDIAN) {
|
||||
if (length($data) == 4) {
|
||||
$data = unpack('N', $data);
|
||||
}
|
||||
else {
|
||||
# incorrect length for dword data
|
||||
$data = undef;
|
||||
}
|
||||
}
|
||||
elsif ($type == REG_SZ || $type == REG_EXPAND_SZ) {
|
||||
# Snip off any terminating null.
|
||||
# Typically, REG_SZ values will not have a terminating null,
|
||||
# while REG_EXPAND_SZ values will have a terminating null
|
||||
chop $data if substr($data, -1, 1) eq "\0";
|
||||
}
|
||||
elsif ($type == REG_MULTI_SZ) {
|
||||
# Snip off any terminating nulls
|
||||
chop $data if substr($data, -1, 1) eq "\0";
|
||||
chop $data if substr($data, -1, 1) eq "\0";
|
||||
my @multi_sz = split("\0", $data, -1);
|
||||
# Make sure there is at least one empty string
|
||||
@multi_sz = ('') if @multi_sz == 0;
|
||||
return wantarray ? @multi_sz : join($", @multi_sz);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
sub as_regedit_export {
|
||||
my $self = shift;
|
||||
my $version = shift || 5;
|
||||
|
||||
my $name = $self->get_name;
|
||||
my $export = $name eq '' ? '@=' : '"' . $name . '"=';
|
||||
|
||||
my $type = $self->get_type;
|
||||
|
||||
# XXX
|
||||
# if (!defined $self->{_data}) {
|
||||
# $name = $name eq '' ? '@' : qq{"$name"};
|
||||
# return qq{; $name=(invalid data)\n};
|
||||
# }
|
||||
|
||||
if ($type == REG_SZ) {
|
||||
$export .= '"' . $self->get_data . '"';
|
||||
$export .= "\n";
|
||||
}
|
||||
elsif ($type == REG_BINARY) {
|
||||
$export .= 'hex:';
|
||||
$export .= format_octets($self->{_data}, length($export));
|
||||
}
|
||||
elsif ($type == REG_DWORD) {
|
||||
my $data = $self->get_data;
|
||||
$export .= defined($data)
|
||||
? sprintf("dword:%08x", $data)
|
||||
: "dword:";
|
||||
$export .= "\n";
|
||||
}
|
||||
elsif ($type == REG_EXPAND_SZ || $type == REG_MULTI_SZ) {
|
||||
my $data = $version == 4
|
||||
? $self->{_data} # raw data
|
||||
: encode("UCS-2LE", $self->{_data}); # ansi->unicode
|
||||
$export .= sprintf("hex(%x):", $type);
|
||||
$export .= format_octets($data, length($export));
|
||||
}
|
||||
else {
|
||||
$export .= sprintf("hex(%x):", $type);
|
||||
$export .= format_octets($self->{_data}, length($export));
|
||||
}
|
||||
return $export;
|
||||
}
|
||||
|
||||
sub parse_info {
|
||||
my $self = shift;
|
||||
|
||||
my $info = sprintf '0x%x rgdb value len=0x%x "%s" type=%d data,len=0x%x',
|
||||
$self->{_offset},
|
||||
$self->{_length},
|
||||
$self->{_name},
|
||||
$self->{_type},
|
||||
$self->{_data_length};
|
||||
return $info;
|
||||
}
|
||||
|
||||
1;
|
109
thirdparty/rr-full/Parse/Win32Registry/WinNT/Entry.pm
vendored
Normal file
109
thirdparty/rr-full/Parse/Win32Registry/WinNT/Entry.pm
vendored
Normal file
@ -0,0 +1,109 @@
|
||||
package Parse::Win32Registry::WinNT::Entry;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base qw(Parse::Win32Registry::Entry);
|
||||
|
||||
use Carp;
|
||||
use Parse::Win32Registry::Base qw(:all);
|
||||
use Parse::Win32Registry::WinNT::Key;
|
||||
use Parse::Win32Registry::WinNT::Value;
|
||||
use Parse::Win32Registry::WinNT::Security;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $regfile = shift;
|
||||
my $offset = shift;
|
||||
|
||||
croak 'Missing registry file' if !defined $regfile;
|
||||
croak 'Missing offset' if !defined $offset;
|
||||
|
||||
my $fh = $regfile->get_filehandle;
|
||||
|
||||
sysseek($fh, $offset, 0);
|
||||
my $bytes_read = sysread($fh, my $entry_header, 8);
|
||||
if ($bytes_read != 8) {
|
||||
return;
|
||||
}
|
||||
|
||||
my ($length,
|
||||
$tag) = unpack('Va2', $entry_header);
|
||||
|
||||
my $allocated = 0;
|
||||
if ($length > 0x7fffffff) {
|
||||
$allocated = 1;
|
||||
$length = (0xffffffff - $length) + 1;
|
||||
}
|
||||
|
||||
$tag = '' if $tag !~ /(nk|vk|lh|lf|li|ri|sk)/;
|
||||
|
||||
if ($tag eq 'nk') {
|
||||
if (my $key = Parse::Win32Registry::WinNT::Key->new($regfile,
|
||||
$offset))
|
||||
{
|
||||
$key->regenerate_path;
|
||||
return $key;
|
||||
}
|
||||
}
|
||||
elsif ($tag eq 'vk') {
|
||||
if (my $value = Parse::Win32Registry::WinNT::Value->new($regfile,
|
||||
$offset))
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
elsif ($tag eq 'sk') {
|
||||
if (my $value = Parse::Win32Registry::WinNT::Security->new($regfile,
|
||||
$offset))
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
my $self = {};
|
||||
$self->{_regfile} = $regfile,
|
||||
$self->{_offset} = $offset,
|
||||
$self->{_length} = $length,
|
||||
$self->{_tag} = $tag,
|
||||
$self->{_allocated} = $allocated,
|
||||
bless $self, $class;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub as_string {
|
||||
my $self = shift;
|
||||
|
||||
my $tag = $self->{_tag};
|
||||
if ($tag eq 'nk') {
|
||||
return '(key entry)';
|
||||
}
|
||||
elsif ($tag eq 'vk') {
|
||||
return '(value entry)';
|
||||
}
|
||||
elsif ($tag eq 'sk') {
|
||||
return '(security entry)';
|
||||
}
|
||||
elsif ($tag =~ /(lh|lf|li|ri)/) {
|
||||
return '(subkey list entry)';
|
||||
}
|
||||
return '(unidentified entry)';
|
||||
}
|
||||
|
||||
sub parse_info {
|
||||
my $self = shift;
|
||||
|
||||
my $tag = $self->{_tag};
|
||||
$tag = defined($tag) && $tag ne ''
|
||||
? $tag . ' '
|
||||
: '.. ';
|
||||
my $info = sprintf '0x%x %slen=0x%x alloc=%d',
|
||||
$self->{_offset},
|
||||
$tag,
|
||||
$self->{_length},
|
||||
$self->{_allocated};
|
||||
return $info;
|
||||
}
|
||||
|
||||
1;
|
297
thirdparty/rr-full/Parse/Win32Registry/WinNT/File.pm
vendored
Normal file
297
thirdparty/rr-full/Parse/Win32Registry/WinNT/File.pm
vendored
Normal file
@ -0,0 +1,297 @@
|
||||
package Parse::Win32Registry::WinNT::File;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base qw(Parse::Win32Registry::File);
|
||||
|
||||
use Carp;
|
||||
use Encode;
|
||||
use File::Basename;
|
||||
use Parse::Win32Registry::Base qw(:all);
|
||||
use Parse::Win32Registry::WinNT::Key;
|
||||
|
||||
use constant REGF_HEADER_LENGTH => 0x200;
|
||||
use constant OFFSET_TO_FIRST_HBIN => 0x1000;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $filename = shift or croak "No filename specified";
|
||||
|
||||
open my $fh, '<', $filename or croak "Unable to open '$filename': $!";
|
||||
|
||||
# 0x00 dword = 'regf' signature
|
||||
# 0x04 dword = seq1
|
||||
# 0x08 dword = seq2
|
||||
# 0x0c qword = timestamp
|
||||
# 0x14 dword = major version
|
||||
# 0x18 dword = minor version
|
||||
# 0x1c dword = type (0 = registry file, 1 = log file)
|
||||
# 0x20 dword = (1)
|
||||
# 0x24 dword = offset to root key
|
||||
# 0x28 dword = total length of all hbins (excludes header)
|
||||
# 0x2c dword = (1)
|
||||
# 0x30 = embedded filename
|
||||
|
||||
# Extracted offsets are always relative to first hbin
|
||||
|
||||
my $bytes_read = sysread($fh, my $regf_header, REGF_HEADER_LENGTH);
|
||||
if ($bytes_read != REGF_HEADER_LENGTH) {
|
||||
warnf('Could not read registry file header');
|
||||
return;
|
||||
}
|
||||
|
||||
my ($regf_sig,
|
||||
$seq1,
|
||||
$seq2,
|
||||
$timestamp,
|
||||
$major_version,
|
||||
$minor_version,
|
||||
$type,
|
||||
$offset_to_root_key,
|
||||
$total_hbin_length,
|
||||
$embedded_filename,
|
||||
) = unpack('a4VVa8VVVx4VVx4a64', $regf_header);
|
||||
|
||||
$offset_to_root_key += OFFSET_TO_FIRST_HBIN;
|
||||
|
||||
if ($regf_sig ne 'regf') {
|
||||
warnf('Invalid registry file signature');
|
||||
return;
|
||||
}
|
||||
|
||||
$embedded_filename = unpack('Z*', decode('UCS-2LE', $embedded_filename));
|
||||
|
||||
# The header checksum is the xor of the first 127 dwords.
|
||||
# The checksum is stored in the 128th dword, at offset 0x1fc (508).
|
||||
my $checksum = 0;
|
||||
foreach my $x (unpack('V127', $regf_header)) {
|
||||
$checksum ^= $x;
|
||||
}
|
||||
my $embedded_checksum = unpack('x508V', $regf_header);
|
||||
if ($checksum != $embedded_checksum) {
|
||||
warnf('Invalid checksum for registry file header');
|
||||
}
|
||||
|
||||
my $self = {};
|
||||
$self->{_filehandle} = $fh;
|
||||
$self->{_filename} = $filename;
|
||||
$self->{_length} = (stat $fh)[7];
|
||||
$self->{_offset_to_root_key} = $offset_to_root_key;
|
||||
$self->{_timestamp} = unpack_windows_time($timestamp);
|
||||
$self->{_embedded_filename} = $embedded_filename;
|
||||
$self->{_seq1} = $seq1;
|
||||
$self->{_seq2} = $seq2;
|
||||
$self->{_version} = "$major_version.$minor_version";
|
||||
$self->{_type} = $type;
|
||||
$self->{_total_hbin_length} = $total_hbin_length;
|
||||
$self->{_embedded_checksum} = $embedded_checksum;
|
||||
$self->{_security_cache} = {}; # comment out to disable cache
|
||||
bless $self, $class;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub get_root_key {
|
||||
my $self = shift;
|
||||
|
||||
my $offset_to_root_key = $self->{_offset_to_root_key};
|
||||
|
||||
my $root_key = Parse::Win32Registry::WinNT::Key->new($self,
|
||||
$offset_to_root_key);
|
||||
return $root_key;
|
||||
}
|
||||
|
||||
sub get_virtual_root_key {
|
||||
my $self = shift;
|
||||
my $fake_root = shift;
|
||||
|
||||
my $root_key = $self->get_root_key;
|
||||
return if !defined $root_key;
|
||||
|
||||
if (!defined $fake_root) {
|
||||
# guess virtual root from filename
|
||||
my $filename = basename $self->{_filename};
|
||||
|
||||
if ($filename =~ /NTUSER/i) {
|
||||
$fake_root = 'HKEY_CURRENT_USER';
|
||||
}
|
||||
elsif ($filename =~ /USRCLASS/i) {
|
||||
$fake_root = 'HKEY_CLASSES_ROOT';
|
||||
}
|
||||
elsif ($filename =~ /SOFTWARE/i) {
|
||||
$fake_root = 'HKEY_LOCAL_MACHINE\SOFTWARE';
|
||||
}
|
||||
elsif ($filename =~ /SYSTEM/i) {
|
||||
$fake_root = 'HKEY_LOCAL_MACHINE\SYSTEM';
|
||||
}
|
||||
elsif ($filename =~ /SAM/i) {
|
||||
$fake_root = 'HKEY_LOCAL_MACHINE\SAM';
|
||||
}
|
||||
elsif ($filename =~ /SECURITY/i) {
|
||||
$fake_root = 'HKEY_LOCAL_MACHINE\SECURITY';
|
||||
}
|
||||
else {
|
||||
$fake_root = 'HKEY_UNKNOWN';
|
||||
}
|
||||
}
|
||||
|
||||
$root_key->{_name} = $fake_root;
|
||||
$root_key->{_key_path} = $fake_root;
|
||||
|
||||
return $root_key;
|
||||
}
|
||||
|
||||
sub get_timestamp {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_timestamp};
|
||||
}
|
||||
|
||||
sub get_timestamp_as_string {
|
||||
my $self = shift;
|
||||
|
||||
return iso8601($self->{_timestamp});
|
||||
}
|
||||
|
||||
sub get_embedded_filename {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_embedded_filename};
|
||||
}
|
||||
|
||||
sub get_block_iterator {
|
||||
my $self = shift;
|
||||
|
||||
my $offset_to_next_hbin = OFFSET_TO_FIRST_HBIN;
|
||||
my $end_of_file = $self->{_length};
|
||||
|
||||
return Parse::Win32Registry::Iterator->new(sub {
|
||||
if ($offset_to_next_hbin > $end_of_file) {
|
||||
return; # no more hbins
|
||||
}
|
||||
if (my $hbin = Parse::Win32Registry::WinNT::Hbin->new($self,
|
||||
$offset_to_next_hbin))
|
||||
{
|
||||
return unless $hbin->get_length > 0;
|
||||
$offset_to_next_hbin += $hbin->get_length;
|
||||
return $hbin;
|
||||
}
|
||||
else {
|
||||
return; # no more hbins
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
*get_hbin_iterator = \&get_block_iterator;
|
||||
|
||||
sub _dump_security_cache {
|
||||
my $self = shift;
|
||||
|
||||
if (defined(my $cache = $self->{_security_cache})) {
|
||||
foreach my $offset (sort { $a <=> $b } keys %$cache) {
|
||||
my $security = $cache->{$offset};
|
||||
printf '0x%x %s\n', $offset, $security->as_string;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
package Parse::Win32Registry::WinNT::Hbin;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base qw(Parse::Win32Registry::Entry);
|
||||
|
||||
use Carp;
|
||||
use Parse::Win32Registry::Base qw(:all);
|
||||
use Parse::Win32Registry::WinNT::Entry;
|
||||
|
||||
use constant HBIN_HEADER_LENGTH => 0x20;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $regfile = shift;
|
||||
my $offset = shift;
|
||||
|
||||
croak 'Missing registry file' if !defined $regfile;
|
||||
croak 'Missing offset' if !defined $offset;
|
||||
|
||||
my $fh = $regfile->get_filehandle;
|
||||
|
||||
# 0x00 dword = 'hbin' signature
|
||||
# 0x04 dword = offset from first hbin to this hbin
|
||||
# 0x08 dword = length of this hbin / relative offset to next hbin
|
||||
# 0x14 qword = timestamp (first hbin only)
|
||||
|
||||
# Extracted offsets are always relative to first hbin
|
||||
|
||||
sysseek($fh, $offset, 0);
|
||||
my $bytes_read = sysread($fh, my $hbin_header, HBIN_HEADER_LENGTH);
|
||||
if ($bytes_read != HBIN_HEADER_LENGTH) {
|
||||
return;
|
||||
}
|
||||
|
||||
my ($sig,
|
||||
$offset_to_hbin,
|
||||
$length,
|
||||
$timestamp) = unpack('a4VVx8a8x4', $hbin_header);
|
||||
|
||||
if ($sig ne 'hbin') {
|
||||
return;
|
||||
}
|
||||
|
||||
my $self = {};
|
||||
$self->{_regfile} = $regfile;
|
||||
$self->{_offset} = $offset;
|
||||
$self->{_length} = $length;
|
||||
$self->{_header_length} = HBIN_HEADER_LENGTH;
|
||||
$self->{_allocated} = 1;
|
||||
$self->{_tag} = $sig;
|
||||
$self->{_timestamp} = unpack_windows_time($timestamp);
|
||||
bless $self, $class;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub get_timestamp {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_timestamp};
|
||||
}
|
||||
|
||||
sub get_timestamp_as_string {
|
||||
my $self = shift;
|
||||
|
||||
return iso8601($self->{_timestamp});
|
||||
}
|
||||
|
||||
sub get_entry_iterator {
|
||||
my $self = shift;
|
||||
|
||||
my $regfile = $self->{_regfile};
|
||||
my $offset = $self->{_offset};
|
||||
my $length = $self->{_length};
|
||||
|
||||
my $offset_to_next_entry = $offset + HBIN_HEADER_LENGTH;
|
||||
my $end_of_hbin = $offset + $length;
|
||||
|
||||
return Parse::Win32Registry::Iterator->new(sub {
|
||||
if ($offset_to_next_entry >= $end_of_hbin) {
|
||||
return; # no more entries
|
||||
}
|
||||
if (my $entry = Parse::Win32Registry::WinNT::Entry->new($regfile,
|
||||
$offset_to_next_entry))
|
||||
{
|
||||
return unless $entry->get_length > 0;
|
||||
$offset_to_next_entry += $entry->get_length;
|
||||
return $entry;
|
||||
}
|
||||
else {
|
||||
return; # no more entries
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
1;
|
444
thirdparty/rr-full/Parse/Win32Registry/WinNT/Key.pm
vendored
Normal file
444
thirdparty/rr-full/Parse/Win32Registry/WinNT/Key.pm
vendored
Normal file
@ -0,0 +1,444 @@
|
||||
package Parse::Win32Registry::WinNT::Key;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base qw(Parse::Win32Registry::Key);
|
||||
|
||||
use Carp;
|
||||
use Encode;
|
||||
use Parse::Win32Registry::Base qw(:all);
|
||||
use Parse::Win32Registry::WinNT::Value;
|
||||
use Parse::Win32Registry::WinNT::Security;
|
||||
|
||||
use constant NK_HEADER_LENGTH => 0x50;
|
||||
use constant OFFSET_TO_FIRST_HBIN => 0x1000;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $regfile = shift;
|
||||
my $offset = shift; # offset to nk record relative to start of file
|
||||
my $parent_key_path = shift; # parent key path (optional)
|
||||
|
||||
croak 'Missing registry file' if !defined $regfile;
|
||||
croak 'Missing offset' if !defined $offset;
|
||||
|
||||
my $fh = $regfile->get_filehandle;
|
||||
|
||||
# 0x00 dword = key length (negative = allocated)
|
||||
# 0x04 word = 'nk' signature
|
||||
# 0x06 word = flags
|
||||
# 0x08 qword = timestamp
|
||||
# 0x10
|
||||
# 0x14 dword = offset to parent
|
||||
# 0x18 dword = number of subkeys
|
||||
# 0x1c
|
||||
# 0x20 dword = offset to subkey list (lf, lh, ri, li)
|
||||
# 0x24
|
||||
# 0x28 dword = number of values
|
||||
# 0x2c dword = offset to value list
|
||||
# 0x30 dword = offset to security
|
||||
# 0x34 dword = offset to class name
|
||||
# 0x38 dword = max subkey name length
|
||||
# 0x3c dword = max class name length
|
||||
# 0x40 dword = max value name length
|
||||
# 0x44 dword = max value data length
|
||||
# 0x48
|
||||
# 0x4c word = key name length
|
||||
# 0x4e word = class name length
|
||||
# 0x50 = key name [for key name length bytes]
|
||||
|
||||
# Extracted offsets are always relative to first hbin
|
||||
|
||||
sysseek($fh, $offset, 0);
|
||||
my $bytes_read = sysread($fh, my $nk_header, NK_HEADER_LENGTH);
|
||||
if ($bytes_read != NK_HEADER_LENGTH) {
|
||||
warnf('Could not read key at 0x%x', $offset);
|
||||
return;
|
||||
}
|
||||
|
||||
my ($length,
|
||||
$sig,
|
||||
$flags,
|
||||
$timestamp,
|
||||
$offset_to_parent,
|
||||
$num_subkeys,
|
||||
$offset_to_subkey_list,
|
||||
$num_values,
|
||||
$offset_to_value_list,
|
||||
$offset_to_security,
|
||||
$offset_to_class_name,
|
||||
$name_length,
|
||||
$class_name_length,
|
||||
) = unpack('Va2va8x4VVx4Vx4VVVVx20vv', $nk_header);
|
||||
|
||||
$offset_to_parent += OFFSET_TO_FIRST_HBIN
|
||||
if $offset_to_parent != 0xffffffff;
|
||||
$offset_to_subkey_list += OFFSET_TO_FIRST_HBIN
|
||||
if $offset_to_subkey_list != 0xffffffff;
|
||||
$offset_to_value_list += OFFSET_TO_FIRST_HBIN
|
||||
if $offset_to_value_list != 0xffffffff;
|
||||
$offset_to_security += OFFSET_TO_FIRST_HBIN
|
||||
if $offset_to_security != 0xffffffff;
|
||||
$offset_to_class_name += OFFSET_TO_FIRST_HBIN
|
||||
if $offset_to_class_name != 0xffffffff;
|
||||
|
||||
my $allocated = 0;
|
||||
if ($length > 0x7fffffff) {
|
||||
$allocated = 1;
|
||||
$length = (0xffffffff - $length) + 1;
|
||||
}
|
||||
# allocated should be true
|
||||
|
||||
if ($length < NK_HEADER_LENGTH) {
|
||||
warnf('Invalid value entry length at 0x%x', $offset);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($sig ne 'nk') {
|
||||
warnf('Invalid signature for key at 0x%x', $offset);
|
||||
return;
|
||||
}
|
||||
|
||||
$bytes_read = sysread($fh, my $name, $name_length);
|
||||
if ($bytes_read != $name_length) {
|
||||
warnf('Could not read name for key at 0x%x', $offset);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($flags & 0x20) {
|
||||
$name = decode($Parse::Win32Registry::Base::CODEPAGE, $name);
|
||||
}
|
||||
else {
|
||||
$name = decode('UCS-2LE', $name);
|
||||
}
|
||||
|
||||
my $key_path = (defined $parent_key_path)
|
||||
? "$parent_key_path\\$name"
|
||||
: "$name";
|
||||
|
||||
my $class_name;
|
||||
if ($offset_to_class_name != 0xffffffff) {
|
||||
sysseek($fh, $offset_to_class_name + 4, 0);
|
||||
$bytes_read = sysread($fh, $class_name, $class_name_length);
|
||||
if ($bytes_read != $class_name_length) {
|
||||
warnf('Could not read class name at 0x%x', $offset_to_class_name);
|
||||
$class_name = undef;
|
||||
}
|
||||
else {
|
||||
$class_name = decode('UCS-2LE', $class_name);
|
||||
}
|
||||
}
|
||||
|
||||
my $self = {};
|
||||
$self->{_regfile} = $regfile;
|
||||
$self->{_offset} = $offset;
|
||||
$self->{_length} = $length;
|
||||
$self->{_allocated} = $allocated;
|
||||
$self->{_tag} = $sig;
|
||||
$self->{_name} = $name;
|
||||
$self->{_name_length} = $name_length;
|
||||
$self->{_key_path} = $key_path;
|
||||
$self->{_flags} = $flags;
|
||||
$self->{_offset_to_parent} = $offset_to_parent;
|
||||
$self->{_num_subkeys} = $num_subkeys;
|
||||
$self->{_offset_to_subkey_list} = $offset_to_subkey_list;
|
||||
$self->{_num_values} = $num_values;
|
||||
$self->{_offset_to_value_list} = $offset_to_value_list;
|
||||
$self->{_timestamp} = unpack_windows_time($timestamp);
|
||||
$self->{_offset_to_security} = $offset_to_security;
|
||||
$self->{_offset_to_class_name} = $offset_to_class_name;
|
||||
$self->{_class_name_length} = $class_name_length;
|
||||
$self->{_class_name} = $class_name;
|
||||
bless $self, $class;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub get_timestamp {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_timestamp};
|
||||
}
|
||||
|
||||
sub get_timestamp_as_string {
|
||||
my $self = shift;
|
||||
|
||||
return iso8601($self->get_timestamp);
|
||||
}
|
||||
|
||||
sub get_class_name {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_class_name};
|
||||
}
|
||||
|
||||
sub is_root {
|
||||
my $self = shift;
|
||||
|
||||
my $flags = $self->{_flags};
|
||||
return $flags & 4 || $flags & 8;
|
||||
}
|
||||
|
||||
sub get_parent {
|
||||
my $self = shift;
|
||||
|
||||
my $regfile = $self->{_regfile};
|
||||
my $offset_to_parent = $self->{_offset_to_parent};
|
||||
my $key_path = $self->{_key_path};
|
||||
|
||||
return if $self->is_root;
|
||||
|
||||
my $grandparent_key_path;
|
||||
my @keys = split /\\/, $key_path, -1;
|
||||
if (@keys > 2) {
|
||||
$grandparent_key_path = join('\\', @keys[0..$#keys-2]);
|
||||
}
|
||||
|
||||
return Parse::Win32Registry::WinNT::Key->new($regfile,
|
||||
$offset_to_parent,
|
||||
$grandparent_key_path);
|
||||
}
|
||||
|
||||
sub get_security {
|
||||
my $self = shift;
|
||||
|
||||
my $regfile = $self->{_regfile};
|
||||
my $offset_to_security = $self->{_offset_to_security};
|
||||
my $key_path = $self->{_key_path};
|
||||
|
||||
if ($offset_to_security == 0xffffffff) {
|
||||
return;
|
||||
}
|
||||
|
||||
return Parse::Win32Registry::WinNT::Security->new($regfile,
|
||||
$offset_to_security,
|
||||
$key_path);
|
||||
}
|
||||
|
||||
sub as_string {
|
||||
my $self = shift;
|
||||
|
||||
my $string = $self->get_path . ' [' . $self->get_timestamp_as_string . ']';
|
||||
return $string;
|
||||
}
|
||||
|
||||
sub parse_info {
|
||||
my $self = shift;
|
||||
|
||||
my $info = sprintf '0x%x nk len=0x%x alloc=%d "%s" par=0x%x keys=%d,0x%x vals=%d,0x%x sec=0x%x class=0x%x',
|
||||
$self->{_offset},
|
||||
$self->{_length},
|
||||
$self->{_allocated},
|
||||
$self->{_name},
|
||||
$self->{_offset_to_parent},
|
||||
$self->{_num_subkeys}, $self->{_offset_to_subkey_list},
|
||||
$self->{_num_values}, $self->{_offset_to_value_list},
|
||||
$self->{_offset_to_security},
|
||||
$self->{_offset_to_class_name};
|
||||
if (defined $self->{_class_name}) {
|
||||
$info .= sprintf ',len=0x%x', $self->{_class_name_length};
|
||||
}
|
||||
return $info;
|
||||
}
|
||||
|
||||
sub _get_offsets_to_subkeys {
|
||||
my $self = shift;
|
||||
|
||||
# Offset is passed as a parameter for recursive lists such as 'ri'
|
||||
my $offset_to_subkey_list = shift || $self->{_offset_to_subkey_list};
|
||||
|
||||
my $regfile = $self->{_regfile};
|
||||
my $fh = $regfile->get_filehandle;
|
||||
|
||||
return if $offset_to_subkey_list == 0xffffffff
|
||||
|| $self->{_num_subkeys} == 0;
|
||||
|
||||
sysseek($fh, $offset_to_subkey_list, 0);
|
||||
my $bytes_read = sysread($fh, my $subkey_list_header, 8);
|
||||
if ($bytes_read != 8) {
|
||||
warnf('Could not read subkey list header at 0x%x',
|
||||
$offset_to_subkey_list);
|
||||
return;
|
||||
}
|
||||
|
||||
# 0x00 dword = subkey list length (negative = allocated)
|
||||
# 0x04 word = 'lf' signature
|
||||
# 0x06 word = number of entries
|
||||
# 0x08 dword = offset to 1st subkey
|
||||
# 0x0c dword = first four characters of the key name
|
||||
# 0x10 dword = offset to 2nd subkey
|
||||
# 0x14 dword = first four characters of the key name
|
||||
# ...
|
||||
|
||||
# 0x00 dword = subkey list length (negative = allocated)
|
||||
# 0x04 word = 'lh' signature
|
||||
# 0x06 word = number of entries
|
||||
# 0x08 dword = offset to 1st subkey
|
||||
# 0x0c dword = hash of the key name
|
||||
# 0x10 dword = offset to 2nd subkey
|
||||
# 0x14 dword = hash of the key name
|
||||
# ...
|
||||
|
||||
# 0x00 dword = subkey list length (negative = allocated)
|
||||
# 0x04 word = 'ri' signature
|
||||
# 0x06 word = number of entries in ri list
|
||||
# 0x08 dword = offset to 1st lf/lh/li list
|
||||
# 0x0c dword = offset to 2nd lf/lh/li list
|
||||
# 0x10 dword = offset to 3rd lf/lh/li list
|
||||
# ...
|
||||
|
||||
# 0x00 dword = subkey list length (negative = allocated)
|
||||
# 0x04 word = 'li' signature
|
||||
# 0x06 word = number of entries in li list
|
||||
# 0x08 dword = offset to 1st subkey
|
||||
# 0x0c dword = offset to 2nd subkey
|
||||
# ...
|
||||
|
||||
# Extracted offsets are always relative to first hbin
|
||||
|
||||
my @offsets_to_subkeys = ();
|
||||
|
||||
my ($length,
|
||||
$sig,
|
||||
$num_entries,
|
||||
) = unpack('Va2v', $subkey_list_header);
|
||||
|
||||
my $subkey_list_length;
|
||||
if ($sig eq 'lf' || $sig eq 'lh') {
|
||||
$subkey_list_length = 2 * 4 * $num_entries;
|
||||
}
|
||||
elsif ($sig eq 'ri' || $sig eq 'li') {
|
||||
$subkey_list_length = 4 * $num_entries;
|
||||
}
|
||||
else {
|
||||
warnf('Invalid signature for subkey list at 0x%x',
|
||||
$offset_to_subkey_list);
|
||||
return;
|
||||
}
|
||||
|
||||
$bytes_read = sysread($fh, my $subkey_list, $subkey_list_length);
|
||||
if ($bytes_read != $subkey_list_length) {
|
||||
warnf('Could not read subkey list at 0x%x',
|
||||
$offset_to_subkey_list);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($sig eq 'lf') {
|
||||
foreach my $offset (unpack("(Vx4)$num_entries", $subkey_list)) {
|
||||
push @offsets_to_subkeys, OFFSET_TO_FIRST_HBIN + $offset;
|
||||
}
|
||||
}
|
||||
elsif ($sig eq 'lh') {
|
||||
foreach my $offset (unpack("(Vx4)$num_entries", $subkey_list)) {
|
||||
push @offsets_to_subkeys, OFFSET_TO_FIRST_HBIN + $offset;
|
||||
}
|
||||
}
|
||||
elsif ($sig eq 'ri') {
|
||||
foreach my $offset (unpack("V$num_entries", $subkey_list)) {
|
||||
my $offsets_ref =
|
||||
$self->_get_offsets_to_subkeys(OFFSET_TO_FIRST_HBIN + $offset);
|
||||
if (defined $offsets_ref && ref $offsets_ref eq 'ARRAY') {
|
||||
push @offsets_to_subkeys, @{ $offsets_ref };
|
||||
}
|
||||
}
|
||||
}
|
||||
elsif ($sig eq 'li') {
|
||||
foreach my $offset (unpack("V$num_entries", $subkey_list)) {
|
||||
push @offsets_to_subkeys, OFFSET_TO_FIRST_HBIN + $offset;
|
||||
}
|
||||
}
|
||||
|
||||
return \@offsets_to_subkeys;
|
||||
}
|
||||
|
||||
sub get_subkey_iterator {
|
||||
my $self = shift;
|
||||
|
||||
my $regfile = $self->{_regfile};
|
||||
my $key_path = $self->{_key_path};
|
||||
|
||||
my @offsets_to_subkeys = ();
|
||||
if ($self->{_num_subkeys} > 0) {
|
||||
my $offsets_to_subkeys_ref = $self->_get_offsets_to_subkeys;
|
||||
if (defined $offsets_to_subkeys_ref) {
|
||||
@offsets_to_subkeys = @{$self->_get_offsets_to_subkeys};
|
||||
}
|
||||
}
|
||||
|
||||
return Parse::Win32Registry::Iterator->new(sub {
|
||||
while (defined(my $offset_to_subkey = shift @offsets_to_subkeys)) {
|
||||
my $subkey = Parse::Win32Registry::WinNT::Key->new($regfile,
|
||||
$offset_to_subkey, $key_path);
|
||||
if (defined $subkey) {
|
||||
return $subkey;
|
||||
}
|
||||
}
|
||||
return; # no more offsets to subkeys
|
||||
});
|
||||
}
|
||||
|
||||
sub _get_offsets_to_values {
|
||||
my $self = shift;
|
||||
|
||||
my $regfile = $self->{_regfile};
|
||||
my $fh = $regfile->get_filehandle;
|
||||
my $offset_to_value_list = $self->{_offset_to_value_list};
|
||||
|
||||
my $num_values = $self->{_num_values};
|
||||
return if $num_values == 0;
|
||||
# Actually, this could probably just fall through
|
||||
# as unpack("x4V0", ...) would return an empty array.
|
||||
|
||||
my @offsets_to_values = ();
|
||||
|
||||
# 0x00 dword = value list length (negative = allocated)
|
||||
# 0x04 dword = 1st offset
|
||||
# 0x08 dword = 2nd offset
|
||||
# ...
|
||||
|
||||
# Extracted offsets are always relative to first hbin
|
||||
|
||||
sysseek($fh, $offset_to_value_list, 0);
|
||||
my $value_list_length = 0x4 + $num_values * 4;
|
||||
my $bytes_read = sysread($fh, my $value_list, $value_list_length);
|
||||
if ($bytes_read != $value_list_length) {
|
||||
warnf("Could not read value list at 0x%x",
|
||||
$offset_to_value_list);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach my $offset (unpack("x4V$num_values", $value_list)) {
|
||||
push @offsets_to_values, OFFSET_TO_FIRST_HBIN + $offset;
|
||||
}
|
||||
|
||||
return \@offsets_to_values;
|
||||
}
|
||||
|
||||
sub get_value_iterator {
|
||||
my $self = shift;
|
||||
|
||||
my $regfile = $self->{_regfile};
|
||||
my $key_path = $self->{_key_path};
|
||||
|
||||
my @offsets_to_values = ();
|
||||
if ($self->{_num_values} > 0) {
|
||||
my $offsets_to_values_ref = $self->_get_offsets_to_values;
|
||||
if (defined $offsets_to_values_ref) {
|
||||
@offsets_to_values = @{$self->_get_offsets_to_values};
|
||||
}
|
||||
}
|
||||
|
||||
return Parse::Win32Registry::Iterator->new(sub {
|
||||
while (defined(my $offset_to_value = shift @offsets_to_values)) {
|
||||
my $value = Parse::Win32Registry::WinNT::Value->new($regfile,
|
||||
$offset_to_value);
|
||||
if (defined $value) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
return; # no more offsets to values
|
||||
});
|
||||
}
|
||||
|
||||
1;
|
157
thirdparty/rr-full/Parse/Win32Registry/WinNT/Security.pm
vendored
Normal file
157
thirdparty/rr-full/Parse/Win32Registry/WinNT/Security.pm
vendored
Normal file
@ -0,0 +1,157 @@
|
||||
package Parse::Win32Registry::WinNT::Security;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base qw(Parse::Win32Registry::Entry);
|
||||
|
||||
use Carp;
|
||||
use Parse::Win32Registry::Base qw(:all);
|
||||
|
||||
use constant SK_HEADER_LENGTH => 0x18;
|
||||
use constant OFFSET_TO_FIRST_HBIN => 0x1000;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $regfile = shift;
|
||||
my $offset = shift; # offset to sk record relative to start of file
|
||||
|
||||
croak 'Missing registry file' if !defined $regfile;
|
||||
croak 'Missing offset' if !defined $offset;
|
||||
|
||||
if (defined(my $cache = $regfile->{_security_cache})) {
|
||||
if (exists $cache->{$offset}) {
|
||||
return $cache->{$offset};
|
||||
}
|
||||
}
|
||||
|
||||
my $fh = $regfile->get_filehandle;
|
||||
|
||||
# 0x00 dword = security length (negative = allocated)
|
||||
# 0x04 word = 'sk' signature
|
||||
# 0x08 dword = offset to previous sk
|
||||
# 0x0c dword = offset to next sk
|
||||
# 0x10 dword = ref count
|
||||
# 0x14 dword = length of security descriptor
|
||||
# 0x18 = start of security descriptor
|
||||
|
||||
# Extracted offsets are always relative to first hbin
|
||||
|
||||
sysseek($fh, $offset, 0);
|
||||
my $bytes_read = sysread($fh, my $sk_header, SK_HEADER_LENGTH);
|
||||
if ($bytes_read != SK_HEADER_LENGTH) {
|
||||
warnf('Could not read security at 0x%x', $offset);
|
||||
return;
|
||||
}
|
||||
|
||||
my ($length,
|
||||
$sig,
|
||||
$offset_to_previous,
|
||||
$offset_to_next,
|
||||
$ref_count,
|
||||
$sd_length,
|
||||
) = unpack('Va2x2VVVV', $sk_header);
|
||||
|
||||
$offset_to_previous += OFFSET_TO_FIRST_HBIN
|
||||
if $offset_to_previous != 0xffffffff;
|
||||
$offset_to_next += OFFSET_TO_FIRST_HBIN
|
||||
if $offset_to_next != 0xffffffff;
|
||||
|
||||
my $allocated = 0;
|
||||
if ($length > 0x7fffffff) {
|
||||
$allocated = 1;
|
||||
$length = (0xffffffff - $length) + 1;
|
||||
}
|
||||
# allocated should be true
|
||||
|
||||
if ($sig ne 'sk') {
|
||||
warnf('Invalid signature for security at 0x%x', $offset);
|
||||
return;
|
||||
}
|
||||
|
||||
$bytes_read = sysread($fh, my $sd_data, $sd_length);
|
||||
if ($bytes_read != $sd_length) {
|
||||
warnf('Could not read security descriptor for security at 0x%x',
|
||||
$offset);
|
||||
return;
|
||||
}
|
||||
|
||||
my $sd = unpack_security_descriptor($sd_data);
|
||||
if (!defined $sd) {
|
||||
warnf('Invalid security descriptor for security at 0x%x',
|
||||
$offset);
|
||||
# Abandon security object if security descriptor is invalid
|
||||
return;
|
||||
}
|
||||
|
||||
my $self = {};
|
||||
$self->{_regfile} = $regfile;
|
||||
$self->{_offset} = $offset;
|
||||
$self->{_length} = $length;
|
||||
$self->{_allocated} = $allocated;
|
||||
$self->{_tag} = $sig;
|
||||
$self->{_offset_to_previous} = $offset_to_previous;
|
||||
$self->{_offset_to_next} = $offset_to_next;
|
||||
$self->{_ref_count} = $ref_count;
|
||||
$self->{_security_descriptor_length} = $sd_length;
|
||||
$self->{_security_descriptor} = $sd;
|
||||
bless $self, $class;
|
||||
|
||||
if (defined(my $cache = $regfile->{_security_cache})) {
|
||||
$cache->{$offset} = $self;
|
||||
}
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub get_previous {
|
||||
my $self = shift;
|
||||
my $regfile = $self->{_regfile};
|
||||
my $offset_to_previous = $self->{_offset_to_previous};
|
||||
|
||||
return Parse::Win32Registry::WinNT::Security->new($regfile,
|
||||
$offset_to_previous);
|
||||
}
|
||||
|
||||
sub get_next {
|
||||
my $self = shift;
|
||||
my $regfile = $self->{_regfile};
|
||||
my $offset_to_next = $self->{_offset_to_next};
|
||||
|
||||
return Parse::Win32Registry::WinNT::Security->new($regfile,
|
||||
$offset_to_next);
|
||||
}
|
||||
|
||||
sub get_reference_count {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_ref_count};
|
||||
}
|
||||
|
||||
sub get_security_descriptor {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_security_descriptor};
|
||||
}
|
||||
|
||||
sub as_string {
|
||||
my $self = shift;
|
||||
|
||||
return '(security entry)';
|
||||
}
|
||||
|
||||
sub parse_info {
|
||||
my $self = shift;
|
||||
|
||||
my $info = sprintf '0x%x sk len=0x%x alloc=%d prev=0x%x,next=0x%x refs=%d',
|
||||
$self->{_offset},
|
||||
$self->{_length},
|
||||
$self->{_allocated},
|
||||
$self->{_offset_to_previous},
|
||||
$self->{_offset_to_next},
|
||||
$self->{_ref_count};
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
1;
|
332
thirdparty/rr-full/Parse/Win32Registry/WinNT/Value.pm
vendored
Normal file
332
thirdparty/rr-full/Parse/Win32Registry/WinNT/Value.pm
vendored
Normal file
@ -0,0 +1,332 @@
|
||||
package Parse::Win32Registry::WinNT::Value;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base qw(Parse::Win32Registry::Value);
|
||||
|
||||
use Carp;
|
||||
use Encode;
|
||||
use Parse::Win32Registry::Base qw(:all);
|
||||
|
||||
use constant VK_HEADER_LENGTH => 0x18;
|
||||
use constant OFFSET_TO_FIRST_HBIN => 0x1000;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $regfile = shift;
|
||||
my $offset = shift; # offset to vk record relative to first hbin
|
||||
|
||||
croak 'Missing registry file' if !defined $regfile;
|
||||
croak 'Missing offset' if !defined $offset;
|
||||
|
||||
my $fh = $regfile->get_filehandle;
|
||||
|
||||
# 0x00 dword = value length (negative = allocated)
|
||||
# 0x04 word = 'vk' signature
|
||||
# 0x06 word = value name length
|
||||
# 0x08 dword = value data length (bit 31 set => data stored inline)
|
||||
# 0x0c dword = offset to data/inline data
|
||||
# 0x10 dword = value type
|
||||
# 0x14 word = flags (bit 1 set => compressed name)
|
||||
# 0x16 word
|
||||
# 0x18 = value name [for value name length bytes]
|
||||
|
||||
# Extracted offsets are always relative to first hbin
|
||||
|
||||
sysseek($fh, $offset, 0);
|
||||
my $bytes_read = sysread($fh, my $vk_header, VK_HEADER_LENGTH);
|
||||
if ($bytes_read != VK_HEADER_LENGTH) {
|
||||
warnf('Could not read value at 0x%x', $offset);
|
||||
return;
|
||||
}
|
||||
|
||||
my ($length,
|
||||
$sig,
|
||||
$name_length,
|
||||
$data_length,
|
||||
$offset_to_data,
|
||||
$type,
|
||||
$flags,
|
||||
) = unpack('Va2vVVVv', $vk_header);
|
||||
|
||||
my $allocated = 0;
|
||||
if ($length > 0x7fffffff) {
|
||||
$allocated = 1;
|
||||
$length = (0xffffffff - $length) + 1;
|
||||
}
|
||||
# allocated should be true
|
||||
|
||||
if ($length < VK_HEADER_LENGTH) {
|
||||
warnf('Invalid value entry length at 0x%x', $offset);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($sig ne 'vk') {
|
||||
warnf('Invalid signature for value at 0x%x', $offset);
|
||||
return;
|
||||
}
|
||||
|
||||
$bytes_read = sysread($fh, my $name, $name_length);
|
||||
if ($bytes_read != $name_length) {
|
||||
warnf('Could not read name for value at 0x%x', $offset);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($flags & 1) {
|
||||
$name = decode($Parse::Win32Registry::Base::CODEPAGE, $name);
|
||||
}
|
||||
else {
|
||||
$name = decode('UCS-2LE', $name);
|
||||
};
|
||||
|
||||
# If the top bit of the data_length is set, then
|
||||
# the value is inline and stored in the offset to data field (at 0xc).
|
||||
my $data;
|
||||
my $data_inline = $data_length >> 31;
|
||||
if ($data_inline) {
|
||||
# REG_DWORDs are always inline, but I've also seen
|
||||
# REG_SZ, REG_BINARY, REG_EXPAND_SZ, and REG_NONE inline
|
||||
$data_length &= 0x7fffffff;
|
||||
if ($data_length > 4) {
|
||||
warnf("Invalid inline data length for value '%s' at 0x%x",
|
||||
$name, $offset);
|
||||
$data = undef;
|
||||
}
|
||||
else {
|
||||
# unpack inline data from header
|
||||
$data = substr($vk_header, 0xc, $data_length);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ($offset_to_data != 0 && $offset_to_data != 0xffffffff) {
|
||||
$offset_to_data += OFFSET_TO_FIRST_HBIN;
|
||||
if ($offset_to_data < ($regfile->get_length - $data_length)) {
|
||||
$data = _extract_data($fh, $offset_to_data, $data_length);
|
||||
}
|
||||
else {
|
||||
warnf("Invalid offset to data for value '%s' at 0x%x",
|
||||
$name, $offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my $self = {};
|
||||
$self->{_regfile} = $regfile;
|
||||
$self->{_offset} = $offset;
|
||||
$self->{_length} = $length;
|
||||
$self->{_allocated} = $allocated;
|
||||
$self->{_tag} = $sig;
|
||||
$self->{_name} = $name;
|
||||
$self->{_name_length} = $name_length;
|
||||
$self->{_type} = $type;
|
||||
$self->{_data} = $data;
|
||||
$self->{_data_length} = $data_length;
|
||||
$self->{_data_inline} = $data_inline;
|
||||
$self->{_offset_to_data} = $offset_to_data;
|
||||
$self->{_flags} = $flags;
|
||||
bless $self, $class;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub _extract_data {
|
||||
my $fh = shift;
|
||||
my $offset_to_data = shift;
|
||||
my $data_length = shift;
|
||||
|
||||
if ($offset_to_data == 0 || $offset_to_data == 0xffffffff) {
|
||||
return undef;
|
||||
}
|
||||
|
||||
sysseek($fh, $offset_to_data, 0);
|
||||
my $bytes_read = sysread($fh, my $data_header, 4);
|
||||
if ($bytes_read != 4) {
|
||||
warnf('Could not read data at 0x%x', $offset_to_data);
|
||||
return undef;
|
||||
}
|
||||
|
||||
my ($max_data_length) = unpack('V', $data_header);
|
||||
|
||||
my $data_allocated = 0;
|
||||
if ($max_data_length > 0x7fffffff) {
|
||||
$data_allocated = 1;
|
||||
$max_data_length = (0xffffffff - $max_data_length) + 1;
|
||||
}
|
||||
# data_allocated should be true
|
||||
|
||||
my $data;
|
||||
|
||||
if ($data_length > $max_data_length) {
|
||||
$bytes_read = sysread($fh, my $db_entry, 8);
|
||||
if ($bytes_read != 8) {
|
||||
warnf('Could not read data at 0x%x', $offset_to_data);
|
||||
return undef;
|
||||
}
|
||||
|
||||
my ($sig, $num_data_blocks, $offset_to_data_block_list)
|
||||
= unpack('a2vV', $db_entry);
|
||||
if ($sig ne 'db') {
|
||||
warnf('Invalid signature for big data at 0x%x', $offset_to_data);
|
||||
return undef;
|
||||
}
|
||||
$offset_to_data_block_list += OFFSET_TO_FIRST_HBIN;
|
||||
|
||||
sysseek($fh, $offset_to_data_block_list + 4, 0);
|
||||
$bytes_read = sysread($fh, my $data_block_list, $num_data_blocks * 4);
|
||||
if ($bytes_read != $num_data_blocks * 4) {
|
||||
warnf('Could not read data block list at 0x%x',
|
||||
$offset_to_data_block_list);
|
||||
return undef;
|
||||
}
|
||||
|
||||
$data = "";
|
||||
my @offsets = map { OFFSET_TO_FIRST_HBIN + $_ }
|
||||
unpack("V$num_data_blocks", $data_block_list);
|
||||
foreach my $offset (@offsets) {
|
||||
sysseek($fh, $offset, 0);
|
||||
$bytes_read = sysread($fh, my $block_header, 4);
|
||||
if ($bytes_read != 4) {
|
||||
warnf('Could not read data block at 0x%x', $offset);
|
||||
return undef;
|
||||
}
|
||||
my ($block_length) = unpack('V', $block_header);
|
||||
if ($block_length > 0x7fffffff) {
|
||||
$block_length = (0xffffffff - $block_length) + 1;
|
||||
}
|
||||
$bytes_read = sysread($fh, my $block_data, $block_length - 8);
|
||||
if ($bytes_read != $block_length - 8) {
|
||||
warnf('Could not read data block at 0x%x', $offset);
|
||||
return undef;
|
||||
}
|
||||
$data .= $block_data;
|
||||
}
|
||||
if (length($data) < $data_length) {
|
||||
warnf("Insufficient data blocks for data at 0x%x", $offset_to_data);
|
||||
return undef;
|
||||
}
|
||||
$data = substr($data, 0, $data_length);
|
||||
return $data;
|
||||
}
|
||||
else {
|
||||
$bytes_read = sysread($fh, $data, $data_length);
|
||||
if ($bytes_read != $data_length) {
|
||||
warnf("Could not read data at 0x%x", $offset_to_data);
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
sub get_data {
|
||||
my $self = shift;
|
||||
|
||||
my $type = $self->get_type;
|
||||
|
||||
my $data = $self->{_data};
|
||||
return if !defined $data;
|
||||
|
||||
# apply decoding to appropriate data types
|
||||
if ($type == REG_DWORD) {
|
||||
if (length($data) == 4) {
|
||||
$data = unpack('V', $data);
|
||||
}
|
||||
else {
|
||||
# incorrect length for dword data
|
||||
$data = undef;
|
||||
}
|
||||
}
|
||||
elsif ($type == REG_DWORD_BIG_ENDIAN) {
|
||||
if (length($data) == 4) {
|
||||
$data = unpack('N', $data);
|
||||
}
|
||||
else {
|
||||
# incorrect length for dword data
|
||||
$data = undef;
|
||||
}
|
||||
}
|
||||
elsif ($type == REG_SZ || $type == REG_EXPAND_SZ) {
|
||||
$data = decode('UCS-2LE', $data);
|
||||
# snip off any terminating null
|
||||
chop $data if substr($data, -1, 1) eq "\0";
|
||||
}
|
||||
elsif ($type == REG_MULTI_SZ) {
|
||||
$data = decode('UCS-2LE', $data);
|
||||
# snip off any terminating nulls
|
||||
chop $data if substr($data, -1, 1) eq "\0";
|
||||
chop $data if substr($data, -1, 1) eq "\0";
|
||||
my @multi_sz = split("\0", $data, -1);
|
||||
# make sure there is at least one empty string
|
||||
@multi_sz = ('') if @multi_sz == 0;
|
||||
return wantarray ? @multi_sz : join($", @multi_sz);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
sub as_regedit_export {
|
||||
my $self = shift;
|
||||
my $version = shift || 5;
|
||||
|
||||
my $name = $self->get_name;
|
||||
my $export = $name eq '' ? '@=' : '"' . $name . '"=';
|
||||
|
||||
my $type = $self->get_type;
|
||||
|
||||
# XXX
|
||||
# if (!defined $self->{_data}) {
|
||||
# $name = $name eq '' ? '@' : qq{"$name"};
|
||||
# return qq{; $name=(invalid data)\n};
|
||||
# }
|
||||
|
||||
if ($type == REG_SZ) {
|
||||
$export .= '"' . $self->get_data . '"';
|
||||
$export .= "\n";
|
||||
}
|
||||
elsif ($type == REG_BINARY) {
|
||||
$export .= "hex:";
|
||||
$export .= format_octets($self->{_data}, length($export));
|
||||
}
|
||||
elsif ($type == REG_DWORD) {
|
||||
my $data = $self->get_data;
|
||||
$export .= defined($data)
|
||||
? sprintf("dword:%08x", $data)
|
||||
: "dword:";
|
||||
$export .= "\n";
|
||||
}
|
||||
elsif ($type == REG_EXPAND_SZ || $type == REG_MULTI_SZ) {
|
||||
my $data = $version == 4
|
||||
? encode("ascii", $self->{_data}) # unicode->ascii
|
||||
: $self->{_data}; # raw data
|
||||
$export .= sprintf("hex(%x):", $type);
|
||||
$export .= format_octets($data, length($export));
|
||||
}
|
||||
else {
|
||||
$export .= sprintf("hex(%x):", $type);
|
||||
$export .= format_octets($self->{_data}, length($export));
|
||||
}
|
||||
return $export;
|
||||
}
|
||||
|
||||
sub parse_info {
|
||||
my $self = shift;
|
||||
|
||||
my $info = sprintf '0x%x vk len=0x%x alloc=%d "%s" type=%d',
|
||||
$self->{_offset},
|
||||
$self->{_length},
|
||||
$self->{_allocated},
|
||||
$self->{_name},
|
||||
$self->{_type};
|
||||
if ($self->{_data_inline}) {
|
||||
$info .= sprintf ' data=inline,len=0x%x',
|
||||
$self->{_data_length};
|
||||
}
|
||||
else {
|
||||
$info .= sprintf ' data=0x%x,len=0x%x',
|
||||
$self->{_offset_to_data},
|
||||
$self->{_data_length};
|
||||
}
|
||||
return $info;
|
||||
}
|
||||
|
||||
1;
|
1834
thirdparty/rr/Parse/Win32Registry.pm
vendored
Normal file
1834
thirdparty/rr/Parse/Win32Registry.pm
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1107
thirdparty/rr/Parse/Win32Registry/Base.pm
vendored
Normal file
1107
thirdparty/rr/Parse/Win32Registry/Base.pm
vendored
Normal file
File diff suppressed because it is too large
Load Diff
151
thirdparty/rr/Parse/Win32Registry/Entry.pm
vendored
Normal file
151
thirdparty/rr/Parse/Win32Registry/Entry.pm
vendored
Normal file
@ -0,0 +1,151 @@
|
||||
package Parse::Win32Registry::Entry;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Carp;
|
||||
use Parse::Win32Registry::Base qw(:all);
|
||||
|
||||
sub get_regfile {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_regfile};
|
||||
}
|
||||
|
||||
sub get_offset {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_offset};
|
||||
}
|
||||
|
||||
sub get_length {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_length};
|
||||
}
|
||||
|
||||
sub is_allocated {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_allocated};
|
||||
}
|
||||
|
||||
sub get_tag {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_tag};
|
||||
}
|
||||
|
||||
sub as_string {
|
||||
my $self = shift;
|
||||
|
||||
my $tag = $self->{_tag};
|
||||
$tag = 'unidentified entry' if !defined $tag;
|
||||
return "($tag)";
|
||||
}
|
||||
|
||||
sub parse_info {
|
||||
my $self = shift;
|
||||
|
||||
my $info = sprintf '0x%x %s len=0x%x',
|
||||
$self->{_offset},
|
||||
$self->{_tag},
|
||||
$self->{_length};
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
sub unparsed {
|
||||
my $self = shift;
|
||||
|
||||
return hexdump($self->get_raw_bytes, $self->get_offset);
|
||||
}
|
||||
|
||||
sub get_raw_bytes {
|
||||
my $self = shift;
|
||||
|
||||
my $regfile = $self->{_regfile};
|
||||
my $fh = $regfile->get_filehandle;
|
||||
my $offset = $self->{_offset};
|
||||
my $length = $self->{_length};
|
||||
|
||||
if (defined $self->{_header_length}) {
|
||||
$length = $self->{_header_length};
|
||||
}
|
||||
|
||||
sysseek($fh, $offset, 0);
|
||||
my $bytes_read = sysread($fh, my $buffer, $length);
|
||||
if ($bytes_read == $length) {
|
||||
return $buffer;
|
||||
}
|
||||
else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
sub looks_like_key {
|
||||
return UNIVERSAL::isa($_[0], "Parse::Win32Registry::Key");
|
||||
}
|
||||
|
||||
sub looks_like_value {
|
||||
return UNIVERSAL::isa($_[0], "Parse::Win32Registry::Value");
|
||||
}
|
||||
|
||||
sub looks_like_security {
|
||||
return UNIVERSAL::isa($_[0], "Parse::Win32Registry::WinNT::Security");
|
||||
}
|
||||
|
||||
sub _dumpvar {
|
||||
my $self = shift;
|
||||
my $depth = shift || 1;
|
||||
|
||||
my $dumpvar = '';
|
||||
foreach (sort keys %$self) {
|
||||
$dumpvar .= ' ' x ($depth*2);
|
||||
$dumpvar .= "$_ => ";
|
||||
my $var = $self->{$_};
|
||||
if (!defined $var) {
|
||||
$dumpvar .= "undef\n";
|
||||
}
|
||||
elsif (/offset/ || /_id$/ || /^_unk/) {
|
||||
$dumpvar .= sprintf "0x%x\n", $var;
|
||||
}
|
||||
elsif (/_flags$/) {
|
||||
$dumpvar .= sprintf "0x%x (0b%b)\n", $var, $var;
|
||||
}
|
||||
elsif (/length/ || /bytes_used/) {
|
||||
$dumpvar .= sprintf "0x%x (%d)\n", $var, $var;
|
||||
}
|
||||
elsif (/_data$/) {
|
||||
if (length($var) == 0) {
|
||||
$dumpvar .= '(no data)';
|
||||
}
|
||||
else {
|
||||
$dumpvar .= join(' ', unpack('(H2)20', $var));
|
||||
if (length($var) > 20) {
|
||||
$dumpvar .= '...';
|
||||
}
|
||||
}
|
||||
$dumpvar .= "\n";
|
||||
}
|
||||
elsif (/timestamp$/) {
|
||||
$dumpvar .= $var . " (" . iso8601($var) . ")\n";
|
||||
}
|
||||
elsif ($var =~ /^\d+$/) {
|
||||
$dumpvar .= sprintf "%d\n", $var;
|
||||
}
|
||||
elsif (ref($var)) {
|
||||
$dumpvar .= "$var\n"; # stringify object ref
|
||||
}
|
||||
else {
|
||||
$dumpvar .= qq{"$var"};
|
||||
$dumpvar .= ' ';
|
||||
$dumpvar .= Encode::is_utf8($var) ? "(UTF8)" : "(BYTES)";
|
||||
$dumpvar .= "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return $dumpvar;
|
||||
}
|
||||
|
||||
1;
|
66
thirdparty/rr/Parse/Win32Registry/File.pm
vendored
Normal file
66
thirdparty/rr/Parse/Win32Registry/File.pm
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
package Parse::Win32Registry::File;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
sub get_filehandle {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_filehandle};
|
||||
}
|
||||
|
||||
sub get_filename {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_filename};
|
||||
}
|
||||
|
||||
sub get_length {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_length};
|
||||
}
|
||||
|
||||
sub get_entry_iterator {
|
||||
my $self = shift;
|
||||
|
||||
my $entry_iter;
|
||||
my $block_iter = $self->get_block_iterator;
|
||||
|
||||
return Parse::Win32Registry::Iterator->new(sub {
|
||||
while (1) {
|
||||
if (defined $entry_iter) {
|
||||
my $entry = $entry_iter->();
|
||||
if (defined $entry) {
|
||||
return $entry;
|
||||
}
|
||||
}
|
||||
# entry iterator is undefined or finished
|
||||
my $block = $block_iter->();
|
||||
if (!defined $block) {
|
||||
return; # block iterator finished
|
||||
}
|
||||
$entry_iter = $block->get_entry_iterator;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
# method provided for backwards compatibility
|
||||
sub move_to_first_entry {
|
||||
my $self = shift;
|
||||
|
||||
$self->{_entry_iter} = undef;
|
||||
}
|
||||
|
||||
# method provided for backwards compatibility
|
||||
sub get_next_entry {
|
||||
my $self = shift;
|
||||
|
||||
my $entry_iter = $self->{_entry_iter};
|
||||
if (!defined $entry_iter) {
|
||||
$self->{_entry_iter} = $entry_iter = $self->get_entry_iterator;
|
||||
}
|
||||
return $entry_iter->();
|
||||
}
|
||||
|
||||
1;
|
245
thirdparty/rr/Parse/Win32Registry/Key.pm
vendored
Normal file
245
thirdparty/rr/Parse/Win32Registry/Key.pm
vendored
Normal file
@ -0,0 +1,245 @@
|
||||
package Parse::Win32Registry::Key;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base qw(Parse::Win32Registry::Entry);
|
||||
|
||||
use Carp;
|
||||
|
||||
sub get_name {
|
||||
my $self = shift;
|
||||
|
||||
# the root key of a windows 95 registry has no defined name
|
||||
# but this should be set to '' when created
|
||||
return $self->{_name};
|
||||
}
|
||||
|
||||
sub get_path {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_key_path};
|
||||
}
|
||||
|
||||
sub _look_up_subkey {
|
||||
my $self = shift;
|
||||
my $subkey_name = shift;
|
||||
|
||||
croak 'Missing subkey name' if !defined $subkey_name;
|
||||
|
||||
foreach my $subkey ($self->get_list_of_subkeys) {
|
||||
if (uc $subkey_name eq uc $subkey->{_name}) {
|
||||
return $subkey;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
sub get_subkey {
|
||||
my $self = shift;
|
||||
my $subkey_path = shift;
|
||||
|
||||
# check for definedness in case key name is '' or '0'
|
||||
croak "Usage: get_subkey('key name')" if !defined $subkey_path;
|
||||
|
||||
my $key = $self;
|
||||
|
||||
# Current path component separator is '\' to match that used in Windows.
|
||||
# split returns nothing if it is given an empty string,
|
||||
# and without a limit of -1 drops trailing empty fields.
|
||||
# The following returns a list with a single zero-length string ("")
|
||||
# for an empty string, as split(/\\/, $subkey_path, -1) returns (),
|
||||
# an empty list.
|
||||
my @path_components = index($subkey_path, "\\") == -1
|
||||
? ($subkey_path)
|
||||
: split(/\\/, $subkey_path, -1);
|
||||
|
||||
my %offsets_seen = ();
|
||||
$offsets_seen{$key->get_offset} = undef;
|
||||
|
||||
foreach my $subkey_name (@path_components) {
|
||||
if (my $subkey = $key->_look_up_subkey($subkey_name)) {
|
||||
if (exists $offsets_seen{$subkey->get_offset}) {
|
||||
return; # found loop
|
||||
}
|
||||
$key = $subkey;
|
||||
$offsets_seen{$key->get_offset} = undef;
|
||||
}
|
||||
else { # subkey name not found, abort look up
|
||||
return;
|
||||
}
|
||||
}
|
||||
return $key;
|
||||
}
|
||||
|
||||
sub get_value {
|
||||
my $self = shift;
|
||||
my $value_name = shift;
|
||||
|
||||
# check for definedness in case value name is '' or '0'
|
||||
croak "Usage: get_value('value name')" if !defined $value_name;
|
||||
|
||||
foreach my $value ($self->get_list_of_values) {
|
||||
if (uc $value_name eq uc $value->{_name}) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub print_summary {
|
||||
my $self = shift;
|
||||
|
||||
print $self->as_string, "\n";
|
||||
}
|
||||
|
||||
sub as_regedit_export {
|
||||
my $self = shift;
|
||||
|
||||
return "[" . $self->{_key_path} . "]\n";
|
||||
}
|
||||
|
||||
sub regenerate_path {
|
||||
my $self = shift;
|
||||
|
||||
# ascend to the root
|
||||
my $key = $self;
|
||||
my @key_names = ($key->get_name);
|
||||
|
||||
my %offsets_seen = ();
|
||||
while (!$key->is_root) {
|
||||
$offsets_seen{$key->get_offset}++;
|
||||
$key = $key->get_parent;
|
||||
if (!defined $key) { # found an undefined parent key
|
||||
unshift @key_names, '(Invalid Parent Key)';
|
||||
last;
|
||||
}
|
||||
if (exists $offsets_seen{$key->get_offset}) { # found loop
|
||||
unshift @key_names, '(Invalid Parent Key)';
|
||||
last;
|
||||
}
|
||||
unshift @key_names, $key->get_name;
|
||||
}
|
||||
|
||||
my $key_path = join('\\', @key_names);
|
||||
$self->{_key_path} = $key_path;
|
||||
return $key_path;
|
||||
}
|
||||
|
||||
sub get_value_data {
|
||||
my $self = shift;
|
||||
my $value_name = shift;
|
||||
|
||||
croak "Usage: get_value_data('value name')" if !defined $value_name;
|
||||
|
||||
if (my $value = $self->get_value($value_name)) {
|
||||
return $value->get_data;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
sub get_mru_list_of_values {
|
||||
my $self = shift;
|
||||
|
||||
my @values = ();
|
||||
|
||||
if (my $mrulist = $self->get_value('MRUList')) {
|
||||
foreach my $ch (split(//, $mrulist->get_data)) {
|
||||
if (my $value = $self->get_value($ch)) {
|
||||
push @values, $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
elsif (my $mrulistex = $self->get_value('MRUListEx')) {
|
||||
foreach my $item (unpack('V*', $mrulistex->get_data)) {
|
||||
last if $item == 0xffffffff;
|
||||
if (my $value = $self->get_value($item)) {
|
||||
push @values, $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return @values;
|
||||
}
|
||||
|
||||
sub get_list_of_subkeys {
|
||||
my $self = shift;
|
||||
|
||||
my $subkey_iter = $self->get_subkey_iterator;
|
||||
my @subkeys;
|
||||
while (my $subkey = $subkey_iter->()) {
|
||||
push @subkeys, $subkey;
|
||||
}
|
||||
return @subkeys;
|
||||
}
|
||||
|
||||
sub get_list_of_values {
|
||||
my $self = shift;
|
||||
|
||||
my $value_iter = $self->get_value_iterator;
|
||||
my @values;
|
||||
while (my $value = $value_iter->()) {
|
||||
push @values, $value;
|
||||
}
|
||||
return @values;
|
||||
}
|
||||
|
||||
sub get_subtree_iterator {
|
||||
my $self = shift;
|
||||
|
||||
my @start_keys = ($self);
|
||||
push my (@subkey_iters), Parse::Win32Registry::Iterator->new(sub {
|
||||
return shift @start_keys;
|
||||
});
|
||||
my $value_iter;
|
||||
my $key; # used to remember key while iterating values
|
||||
|
||||
return Parse::Win32Registry::Iterator->new(sub {
|
||||
if (defined $value_iter && wantarray) {
|
||||
my $value = $value_iter->();
|
||||
if (defined $value) {
|
||||
return ($key, $value);
|
||||
}
|
||||
# $value_iter finished, so fetch a new one
|
||||
# from the (current) $subkey_iter[-1]
|
||||
}
|
||||
while (@subkey_iters > 0) {
|
||||
$key = $subkey_iters[-1]->(); # depth-first
|
||||
if (defined $key) {
|
||||
push @subkey_iters, $key->get_subkey_iterator;
|
||||
$value_iter = $key->get_value_iterator;
|
||||
return $key;
|
||||
}
|
||||
pop @subkey_iters; # $subkey_iter finished, so remove it
|
||||
}
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
sub walk {
|
||||
my $self = shift;
|
||||
my $key_enter_func = shift;
|
||||
my $value_func = shift;
|
||||
my $key_leave_func = shift;
|
||||
|
||||
if (!defined $key_enter_func &&
|
||||
!defined $value_func &&
|
||||
!defined $key_leave_func) {
|
||||
$key_enter_func = sub { print "+ ", $_[0]->get_path, "\n"; };
|
||||
$value_func = sub { print " '", $_[0]->get_name, "'\n"; };
|
||||
$key_leave_func = sub { print "- ", $_[0]->get_path, "\n"; };
|
||||
}
|
||||
|
||||
$key_enter_func->($self) if ref $key_enter_func eq 'CODE';
|
||||
|
||||
foreach my $value ($self->get_list_of_values) {
|
||||
$value_func->($value) if ref $value_func eq 'CODE';
|
||||
}
|
||||
|
||||
foreach my $subkey ($self->get_list_of_subkeys) {
|
||||
$subkey->walk($key_enter_func, $value_func, $key_leave_func);
|
||||
}
|
||||
|
||||
$key_leave_func->($self) if ref $key_leave_func eq 'CODE';
|
||||
}
|
||||
|
||||
1;
|
101
thirdparty/rr/Parse/Win32Registry/Value.pm
vendored
Normal file
101
thirdparty/rr/Parse/Win32Registry/Value.pm
vendored
Normal file
@ -0,0 +1,101 @@
|
||||
package Parse::Win32Registry::Value;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base qw(Parse::Win32Registry::Entry);
|
||||
|
||||
use Carp;
|
||||
use Parse::Win32Registry::Base qw(:all);
|
||||
|
||||
sub get_name {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_name};
|
||||
}
|
||||
|
||||
sub get_type {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_type};
|
||||
}
|
||||
|
||||
our @Types = qw(
|
||||
REG_NONE
|
||||
REG_SZ
|
||||
REG_EXPAND_SZ
|
||||
REG_BINARY
|
||||
REG_DWORD
|
||||
REG_DWORD_BIG_ENDIAN
|
||||
REG_LINK
|
||||
REG_MULTI_SZ
|
||||
REG_RESOURCE_LIST
|
||||
REG_FULL_RESOURCE_DESCRIPTOR
|
||||
REG_RESOURCE_REQUIREMENTS_LIST
|
||||
REG_QWORD
|
||||
);
|
||||
|
||||
sub get_type_as_string {
|
||||
my $self = shift;
|
||||
|
||||
my $type = $self->get_type;
|
||||
if (exists $Types[$type]) {
|
||||
return $Types[$type];
|
||||
}
|
||||
else {
|
||||
# Return unrecognised types as REG_<number>
|
||||
# REGEDIT displays them as formatted hex numbers, e.g. 0x1f4
|
||||
return "REG_$type";
|
||||
}
|
||||
}
|
||||
|
||||
sub get_data_as_string {
|
||||
my $self = shift;
|
||||
|
||||
my $type = $self->get_type;
|
||||
my $data = $self->get_data;
|
||||
if (!defined($data)) {
|
||||
return '(invalid data)';
|
||||
}
|
||||
elsif (length($data) == 0) {
|
||||
return '(no data)';
|
||||
}
|
||||
elsif ($type == REG_SZ || $type == REG_EXPAND_SZ) {
|
||||
return $data;
|
||||
}
|
||||
elsif ($type == REG_MULTI_SZ) {
|
||||
my @data = $self->get_data;
|
||||
my $i = 0;
|
||||
return join(' ', map { "[" . $i++ . "] $_" } @data);
|
||||
}
|
||||
elsif ($type == REG_DWORD || $type == REG_DWORD_BIG_ENDIAN) {
|
||||
return sprintf '0x%08x (%u)', $data, $data;
|
||||
}
|
||||
else {
|
||||
return join(' ', unpack('(H2)*', $data));
|
||||
}
|
||||
}
|
||||
|
||||
sub get_raw_data {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_data};
|
||||
}
|
||||
|
||||
sub as_string {
|
||||
my $self = shift;
|
||||
|
||||
my $name = $self->get_name;
|
||||
$name = '(Default)' if $name eq '';
|
||||
my $type_as_string = $self->get_type_as_string;
|
||||
my $data_as_string = $self->get_data_as_string;
|
||||
return "$name ($type_as_string) = $data_as_string";
|
||||
}
|
||||
|
||||
sub print_summary {
|
||||
my $self = shift;
|
||||
|
||||
print $self->as_string, "\n";
|
||||
}
|
||||
|
||||
1;
|
540
thirdparty/rr/Parse/Win32Registry/Win95/File.pm
vendored
Normal file
540
thirdparty/rr/Parse/Win32Registry/Win95/File.pm
vendored
Normal file
@ -0,0 +1,540 @@
|
||||
package Parse::Win32Registry::Win95::File;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base qw(Parse::Win32Registry::File);
|
||||
|
||||
use Carp;
|
||||
use File::Basename;
|
||||
use Parse::Win32Registry::Base qw(:all);
|
||||
use Parse::Win32Registry::Win95::Key;
|
||||
|
||||
use constant CREG_HEADER_LENGTH => 0x20;
|
||||
use constant OFFSET_TO_RGKN_BLOCK => 0x20;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $filename = shift or croak 'No filename specified';
|
||||
|
||||
open my $fh, '<', $filename or croak "Unable to open '$filename': $!";
|
||||
|
||||
# CREG Header
|
||||
# 0x00 dword = 'CREG' signature
|
||||
# 0x04
|
||||
# 0x08 dword = offset to first rgdb block
|
||||
# 0x0c
|
||||
# 0x10 word = number of rgdb blocks
|
||||
|
||||
my $bytes_read = sysread($fh, my $creg_header, CREG_HEADER_LENGTH);
|
||||
if ($bytes_read != CREG_HEADER_LENGTH) {
|
||||
warnf('Could not read registry file header');
|
||||
return;
|
||||
}
|
||||
|
||||
my ($creg_sig,
|
||||
$offset_to_first_rgdb_block,
|
||||
$num_rgdb_blocks) = unpack('a4x4Vx4v', $creg_header);
|
||||
|
||||
if ($creg_sig ne 'CREG') {
|
||||
warnf('Invalid registry file signature');
|
||||
return;
|
||||
}
|
||||
|
||||
my $self = {};
|
||||
$self->{_filehandle} = $fh;
|
||||
$self->{_filename} = $filename;
|
||||
$self->{_length} = (stat $fh)[7];
|
||||
$self->{_offset_to_first_rgdb_block} = $offset_to_first_rgdb_block;
|
||||
$self->{_num_rgdb_blocks} = $num_rgdb_blocks;
|
||||
bless $self, $class;
|
||||
|
||||
# get_rgkn will cache the rgkn block for subsequent calls
|
||||
my $rgkn_block = $self->get_rgkn;
|
||||
return if !defined $rgkn_block; # warning will already have been made
|
||||
|
||||
# Index the rgdb entries by id for faster look up
|
||||
$self->_index_rgdb_entries;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub get_timestamp {
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub get_timestamp_as_string {
|
||||
return iso8601(undef);
|
||||
}
|
||||
|
||||
sub get_embedded_filename {
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub get_root_key {
|
||||
my $self = shift;
|
||||
|
||||
return $self->get_rgkn->get_root_key;
|
||||
}
|
||||
|
||||
sub get_virtual_root_key {
|
||||
my $self = shift;
|
||||
my $fake_root = shift;
|
||||
|
||||
my $root_key = $self->get_root_key;
|
||||
return if !defined $root_key;
|
||||
|
||||
if (!defined $fake_root) {
|
||||
# guess virtual root from filename
|
||||
my $filename = basename $self->{_filename};
|
||||
|
||||
if ($filename =~ /USER/i) {
|
||||
$fake_root = 'HKEY_USERS';
|
||||
}
|
||||
elsif ($filename =~ /SYSTEM/i) {
|
||||
$fake_root = 'HKEY_LOCAL_MACHINE';
|
||||
}
|
||||
else {
|
||||
$fake_root = 'HKEY_UNKNOWN';
|
||||
}
|
||||
}
|
||||
|
||||
$root_key->{_name} = $fake_root;
|
||||
$root_key->{_key_path} = $fake_root;
|
||||
|
||||
return $root_key;
|
||||
}
|
||||
|
||||
sub _index_rgdb_entries {
|
||||
my $self = shift;
|
||||
|
||||
my %index = ();
|
||||
|
||||
# Build index of rgdb key entries
|
||||
# Entries are only included if $key_block_num matches $rgdb_block_num
|
||||
my $rgdb_block_num = 0;
|
||||
my $rgdb_iter = $self->get_rgdb_iterator;
|
||||
while (my $rgdb = $rgdb_iter->()) {
|
||||
my $rgdb_key_iter = $rgdb->get_key_iterator;
|
||||
while (my $rgdb_key = $rgdb_key_iter->()) {
|
||||
my $key_id = $rgdb_key->{_id};
|
||||
my $key_block_num = $key_id >> 16;
|
||||
if ($rgdb_block_num == $key_block_num) {
|
||||
$index{$key_id} = $rgdb_key;
|
||||
}
|
||||
}
|
||||
$rgdb_block_num++;
|
||||
}
|
||||
|
||||
$self->{_rgdb_index} = \%index;
|
||||
}
|
||||
|
||||
sub _dump_rgdb_index {
|
||||
my $self = shift;
|
||||
|
||||
my $rgdb_index = $self->{_rgdb_index};
|
||||
|
||||
foreach my $key_id (sort { $a <=> $b } keys %$rgdb_index) {
|
||||
my $rgdb_key = $rgdb_index->{$key_id};
|
||||
printf qq{id=0x%x 0x%x,%d/%d "%s" vals=%d\n},
|
||||
$key_id,
|
||||
$rgdb_key->{_offset},
|
||||
$rgdb_key->{_length_used},
|
||||
$rgdb_key->{_length},
|
||||
$rgdb_key->{_name},
|
||||
$rgdb_key->{_num_values};
|
||||
}
|
||||
}
|
||||
|
||||
sub get_rgkn {
|
||||
my $self = shift;
|
||||
|
||||
# Return cached rgkn block if present
|
||||
if (defined $self->{_rgkn}) {
|
||||
return $self->{_rgkn};
|
||||
}
|
||||
|
||||
my $offset = OFFSET_TO_RGKN_BLOCK;
|
||||
my $rgkn_block = Parse::Win32Registry::Win95::RGKN->new($self, $offset);
|
||||
$self->{_rgkn} = $rgkn_block;
|
||||
return $rgkn_block;
|
||||
}
|
||||
|
||||
sub get_rgdb_iterator {
|
||||
my $self = shift;
|
||||
|
||||
my $offset_to_next_rgdb_block = $self->{_offset_to_first_rgdb_block};
|
||||
my $num_rgdb_blocks = $self->{_num_rgdb_blocks};
|
||||
|
||||
my $end_of_file = $self->{_length};
|
||||
|
||||
my $rgdb_block_num = 0;
|
||||
|
||||
return Parse::Win32Registry::Iterator->new(sub {
|
||||
if ($offset_to_next_rgdb_block > $end_of_file) {
|
||||
return; # no more rgdb blocks
|
||||
}
|
||||
if ($rgdb_block_num >= $num_rgdb_blocks) {
|
||||
return; # no more rgdb blocks
|
||||
}
|
||||
$rgdb_block_num++;
|
||||
if (my $rgdb_block = Parse::Win32Registry::Win95::RGDB->new($self,
|
||||
$offset_to_next_rgdb_block))
|
||||
{
|
||||
return unless $rgdb_block->get_length > 0;
|
||||
$offset_to_next_rgdb_block += $rgdb_block->get_length;
|
||||
return $rgdb_block;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
sub get_block_iterator {
|
||||
my $self = shift;
|
||||
|
||||
my $rgdb_iter;
|
||||
|
||||
return Parse::Win32Registry::Iterator->new(sub {
|
||||
if (!defined $rgdb_iter) {
|
||||
$rgdb_iter = $self->get_rgdb_iterator;
|
||||
return $self->get_rgkn;
|
||||
}
|
||||
return $rgdb_iter->();
|
||||
});
|
||||
}
|
||||
|
||||
*get_hbin_iterator = \&get_block_iterator;
|
||||
|
||||
|
||||
package Parse::Win32Registry::Win95::RGKN;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base qw(Parse::Win32Registry::Entry);
|
||||
|
||||
use Carp;
|
||||
use Parse::Win32Registry::Base qw(:all);
|
||||
|
||||
use constant RGKN_HEADER_LENGTH => 0x20;
|
||||
use constant OFFSET_TO_RGKN_BLOCK => 0x20;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $regfile = shift;
|
||||
my $offset = shift || OFFSET_TO_RGKN_BLOCK;
|
||||
|
||||
croak 'Missing registry file' if !defined $regfile;
|
||||
croak 'Missing offset' if !defined $offset;
|
||||
|
||||
my $fh = $regfile->get_filehandle;
|
||||
|
||||
# RGKN Block Header
|
||||
# 0x0 dword = 'RGKN' signature
|
||||
# 0x4 dword = length of rgkn block
|
||||
# 0x8 dword = offset to root key entry (relative to start of rgkn block)
|
||||
|
||||
sysseek($fh, $offset, 0);
|
||||
my $bytes_read = sysread($fh, my $rgkn_header, RGKN_HEADER_LENGTH);
|
||||
if ($bytes_read != RGKN_HEADER_LENGTH) {
|
||||
warnf('Could not read RGKN header at 0x%x', $offset);
|
||||
return;
|
||||
}
|
||||
|
||||
my ($sig,
|
||||
$rgkn_block_length,
|
||||
$offset_to_root_key) = unpack('a4VV', $rgkn_header);
|
||||
|
||||
if ($sig ne 'RGKN') {
|
||||
warnf('Invalid RGKN block signature at 0x%x', $offset);
|
||||
return;
|
||||
}
|
||||
|
||||
$offset_to_root_key += $offset;
|
||||
|
||||
my $self = {};
|
||||
$self->{_regfile} = $regfile;
|
||||
$self->{_offset} = $offset;
|
||||
$self->{_length} = $rgkn_block_length;
|
||||
$self->{_header_length} = RGKN_HEADER_LENGTH;
|
||||
$self->{_allocated} = 1;
|
||||
$self->{_tag} = 'rgkn block';
|
||||
$self->{_offset_to_root_key} = $offset_to_root_key;
|
||||
bless $self, $class;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub get_root_key {
|
||||
my $self = shift;
|
||||
|
||||
my $regfile = $self->{_regfile};
|
||||
my $offset_to_root_key = $self->{_offset_to_root_key};
|
||||
|
||||
my $root_key = Parse::Win32Registry::Win95::Key->new($regfile,
|
||||
$offset_to_root_key);
|
||||
return $root_key;
|
||||
}
|
||||
|
||||
sub get_entry_iterator {
|
||||
my $self = shift;
|
||||
|
||||
my $root_key = $self->get_root_key;
|
||||
|
||||
# In the unlikely event there is no root key, return an empty iterator
|
||||
if (defined $root_key) {
|
||||
return $root_key->get_subtree_iterator;
|
||||
}
|
||||
else {
|
||||
return Parse::Win32Registry::Iterator->new(sub {});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
package Parse::Win32Registry::Win95::RGDB;
|
||||
|
||||
use base qw(Parse::Win32Registry::Entry);
|
||||
|
||||
use Carp;
|
||||
use Parse::Win32Registry::Base qw(:all);
|
||||
|
||||
use constant RGDB_HEADER_LENGTH => 0x20;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $regfile = shift;
|
||||
my $offset = shift;
|
||||
|
||||
croak 'Missing registry file' if !defined $regfile;
|
||||
croak 'Missing offset' if !defined $offset;
|
||||
|
||||
my $fh = $regfile->get_filehandle;
|
||||
|
||||
# RGDB Block Header
|
||||
# 0x0 dword = 'RDGB' signature
|
||||
# 0x4 dword = length of rgdb block
|
||||
|
||||
sysseek($fh, $offset, 0);
|
||||
my $bytes_read = sysread($fh, my $rgdb_header, RGDB_HEADER_LENGTH);
|
||||
if ($bytes_read != RGDB_HEADER_LENGTH) {
|
||||
return;
|
||||
}
|
||||
|
||||
my ($sig,
|
||||
$rgdb_block_length) = unpack('a4V', $rgdb_header);
|
||||
|
||||
if ($sig ne 'RGDB') {
|
||||
return;
|
||||
}
|
||||
|
||||
my $self = {};
|
||||
$self->{_regfile} = $regfile;
|
||||
$self->{_offset} = $offset;
|
||||
$self->{_length} = $rgdb_block_length;
|
||||
$self->{_header_length} = RGDB_HEADER_LENGTH;
|
||||
$self->{_allocated} = 1;
|
||||
$self->{_tag} = 'rgdb block';
|
||||
bless $self, $class;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub get_key_iterator {
|
||||
my $self = shift;
|
||||
|
||||
my $regfile = $self->{_regfile};
|
||||
my $offset = $self->{_offset};
|
||||
my $length = $self->{_length};
|
||||
|
||||
my $offset_to_next_rgdb_key = $offset + RGDB_HEADER_LENGTH;
|
||||
my $end_of_rgdb_block = $offset + $length;
|
||||
|
||||
return Parse::Win32Registry::Iterator->new(sub {
|
||||
if ($offset_to_next_rgdb_key >= $end_of_rgdb_block) {
|
||||
return;
|
||||
}
|
||||
if (my $rgdb_key = Parse::Win32Registry::Win95::RGDBKey->new($regfile,
|
||||
$offset_to_next_rgdb_key))
|
||||
{
|
||||
return unless $rgdb_key->get_length > 0;
|
||||
$offset_to_next_rgdb_key += $rgdb_key->get_length;
|
||||
|
||||
# Check rgdb key has not run past end of rgdb block
|
||||
if ($offset_to_next_rgdb_key > $end_of_rgdb_block) {
|
||||
return;
|
||||
}
|
||||
return $rgdb_key;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
sub get_entry_iterator {
|
||||
my $self = shift;
|
||||
|
||||
my $value_iter;
|
||||
my $key_iter = $self->get_key_iterator;
|
||||
|
||||
return Parse::Win32Registry::Iterator->new(sub {
|
||||
if (defined $value_iter) {
|
||||
my $value = $value_iter->();
|
||||
if (defined $value) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
my $key = $key_iter->();
|
||||
if (!defined $key) {
|
||||
return; # key iterator finished
|
||||
}
|
||||
|
||||
$value_iter = $key->get_value_iterator;
|
||||
return $key;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
package Parse::Win32Registry::Win95::RGDBKey;
|
||||
|
||||
use base qw(Parse::Win32Registry::Entry);
|
||||
|
||||
use Carp;
|
||||
use Encode;
|
||||
use Parse::Win32Registry::Base qw(:all);
|
||||
|
||||
use constant RGDB_ENTRY_HEADER_LENGTH => 0x14;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $regfile = shift;
|
||||
my $offset = shift;
|
||||
|
||||
croak 'Missing registry file' if !defined $regfile;
|
||||
croak 'Missing offset' if !defined $offset;
|
||||
|
||||
my $fh = $regfile->get_filehandle;
|
||||
|
||||
# RGDB Key Entry
|
||||
# 0x00 dword = length of rgdb entry / offset to next rgdb entry
|
||||
# (this length includes any following value entries)
|
||||
# 0x04 dword = id (top word = block num, bottom word = id)
|
||||
# 0x08 dword = bytes used (unpacked, but not used)
|
||||
# 0x0c word = key name length
|
||||
# 0x0e word = number of values
|
||||
# 0x10 dword
|
||||
# 0x14 = key name [for key name length bytes]
|
||||
# followed immediately by any RGDB Value Entries belonging to this key
|
||||
|
||||
sysseek($fh, $offset, 0);
|
||||
my $bytes_read = sysread($fh, my $rgdb_key_entry, RGDB_ENTRY_HEADER_LENGTH);
|
||||
if ($bytes_read != RGDB_ENTRY_HEADER_LENGTH) {
|
||||
return;
|
||||
}
|
||||
|
||||
my ($length,
|
||||
$key_id,
|
||||
$length_used,
|
||||
$name_length,
|
||||
$num_values) = unpack('VVVvv', $rgdb_key_entry);
|
||||
|
||||
$bytes_read = sysread($fh, my $name, $name_length);
|
||||
if ($bytes_read != $name_length) {
|
||||
return;
|
||||
}
|
||||
$name = decode($Parse::Win32Registry::Base::CODEPAGE, $name);
|
||||
|
||||
# Calculate the length of the entry's key header
|
||||
my $header_length = RGDB_ENTRY_HEADER_LENGTH + $name_length;
|
||||
|
||||
# Check for invalid/unused entries
|
||||
if ($key_id == 0xffffffff || $length_used == 0xffffffff
|
||||
|| $header_length > $length)
|
||||
{
|
||||
$name = '';
|
||||
$header_length = RGDB_ENTRY_HEADER_LENGTH;
|
||||
}
|
||||
|
||||
my $self = {};
|
||||
$self->{_regfile} = $regfile;
|
||||
$self->{_offset} = $offset;
|
||||
$self->{_length} = $length;
|
||||
$self->{_length_used} = $length_used;
|
||||
$self->{_header_length} = $header_length;
|
||||
$self->{_allocated} = 1;
|
||||
$self->{_tag} = 'rgdb key';
|
||||
$self->{_id} = $key_id;
|
||||
$self->{_name} = $name;
|
||||
$self->{_name_length} = $name_length;
|
||||
$self->{_num_values} = $num_values;
|
||||
bless $self, $class;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub get_name {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_name};
|
||||
}
|
||||
|
||||
sub parse_info {
|
||||
my $self = shift;
|
||||
|
||||
my $info = sprintf '0x%x rgdb key len=0x%x/0x%x "%s" id=0x%x vals=%d',
|
||||
$self->{_offset},
|
||||
$self->{_length_used},
|
||||
$self->{_length},
|
||||
$self->{_name},
|
||||
$self->{_id},
|
||||
$self->{_num_values};
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
sub get_value_iterator {
|
||||
my $self = shift;
|
||||
|
||||
my $regfile = $self->{_regfile};
|
||||
|
||||
my $num_values_remaining = $self->{_num_values};
|
||||
|
||||
my $offset = $self->{_offset};
|
||||
|
||||
# offset_to_next_rgdb_value can only be set to a valid offset
|
||||
# if num_values_remaining > 0
|
||||
my $offset_to_next_rgdb_value = 0xffffffff;
|
||||
if ($num_values_remaining > 0) {
|
||||
$offset_to_next_rgdb_value = $offset
|
||||
+ $self->{_header_length};
|
||||
}
|
||||
|
||||
my $end_of_rgdb_key = $offset + $self->{_length};
|
||||
|
||||
# don't attempt to return values if id is invalid...
|
||||
if ($self->{_id} == 0xffffffff) {
|
||||
$num_values_remaining = 0;
|
||||
}
|
||||
|
||||
return Parse::Win32Registry::Iterator->new(sub {
|
||||
if ($num_values_remaining-- <= 0) {
|
||||
return;
|
||||
}
|
||||
if ($offset_to_next_rgdb_value == 0xffffffff) {
|
||||
return;
|
||||
}
|
||||
if ($offset_to_next_rgdb_value > $end_of_rgdb_key) {
|
||||
return;
|
||||
}
|
||||
if (my $value = Parse::Win32Registry::Win95::Value->new($regfile,
|
||||
$offset_to_next_rgdb_value))
|
||||
{
|
||||
return unless $value->get_length > 0;
|
||||
$offset_to_next_rgdb_value += $value->get_length;
|
||||
return $value;
|
||||
}
|
||||
else {
|
||||
return; # no more values
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
1;
|
||||
|
207
thirdparty/rr/Parse/Win32Registry/Win95/Key.pm
vendored
Normal file
207
thirdparty/rr/Parse/Win32Registry/Win95/Key.pm
vendored
Normal file
@ -0,0 +1,207 @@
|
||||
package Parse::Win32Registry::Win95::Key;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base qw(Parse::Win32Registry::Key);
|
||||
|
||||
use Carp;
|
||||
use Parse::Win32Registry::Base qw(:all);
|
||||
use Parse::Win32Registry::Win95::Value;
|
||||
|
||||
use constant RGKN_ENTRY_LENGTH => 0x1c;
|
||||
use constant OFFSET_TO_RGKN_BLOCK => 0x20;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $regfile = shift;
|
||||
my $offset = shift; # offset to RGKN key entry relative to start of RGKN
|
||||
my $parent_key_path = shift; # parent key path (optional)
|
||||
|
||||
croak 'Missing registry file' if !defined $regfile;
|
||||
croak 'Missing offset' if !defined $offset;
|
||||
|
||||
my $fh = $regfile->get_filehandle;
|
||||
|
||||
# RGKN Key Entry
|
||||
# 0x00 dword
|
||||
# 0x04 dword
|
||||
# 0x08 dword
|
||||
# 0x0c dword = offset to parent RGKN entry
|
||||
# 0x10 dword = offset to first child RGKN entry
|
||||
# 0x14 dword = offset to next sibling RGKN entry
|
||||
# 0x18 dword = entry id of RGDB entry
|
||||
|
||||
# Extracted offsets are relative to the start of the RGKN block
|
||||
|
||||
# Any offset of 0xffffffff marks the end of a list.
|
||||
# An entry id of 0xffffffff means the RGKN entry has no RGDB entry.
|
||||
# This occurs for the root key of the registry file.
|
||||
|
||||
sysseek($fh, $offset, 0);
|
||||
my $bytes_read = sysread($fh, my $rgkn_entry, RGKN_ENTRY_LENGTH);
|
||||
if ($bytes_read != RGKN_ENTRY_LENGTH) {
|
||||
warnf('Could not read RGKN key at 0x%x', $offset);
|
||||
return;
|
||||
}
|
||||
|
||||
my ($offset_to_parent,
|
||||
$offset_to_first_child,
|
||||
$offset_to_next_sibling,
|
||||
$key_id) = unpack('x12VVVV', $rgkn_entry);
|
||||
|
||||
$offset_to_parent += OFFSET_TO_RGKN_BLOCK
|
||||
if $offset_to_parent != 0xffffffff;
|
||||
$offset_to_first_child += OFFSET_TO_RGKN_BLOCK
|
||||
if $offset_to_first_child != 0xffffffff;
|
||||
$offset_to_next_sibling += OFFSET_TO_RGKN_BLOCK
|
||||
if $offset_to_next_sibling != 0xffffffff;
|
||||
|
||||
my $self = {};
|
||||
$self->{_regfile} = $regfile;
|
||||
$self->{_offset} = $offset;
|
||||
$self->{_length} = RGKN_ENTRY_LENGTH;
|
||||
$self->{_allocated} = 1;
|
||||
$self->{_tag} = 'rgkn key';
|
||||
$self->{_offset_to_parent} = $offset_to_parent;
|
||||
$self->{_offset_to_first_child} = $offset_to_first_child;
|
||||
$self->{_offset_to_next_sibling} = $offset_to_next_sibling;
|
||||
$self->{_id} = $key_id;
|
||||
bless $self, $class;
|
||||
|
||||
# Look up corresponding rgdb entry
|
||||
my $index = $regfile->{_rgdb_index};
|
||||
croak 'Missing rgdb index' if !defined $index;
|
||||
if (exists $index->{$key_id}) {
|
||||
my $rgdb_key = $index->{$key_id};
|
||||
$self->{_rgdb_key} = $rgdb_key;
|
||||
$self->{_name} = $rgdb_key->get_name;
|
||||
}
|
||||
else {
|
||||
$self->{_name} = '';
|
||||
# Only the root key should have no matching RGDB entry
|
||||
if (!$self->is_root) {
|
||||
warnf('Could not find RGDB entry for RGKN key at 0x%x', $offset);
|
||||
}
|
||||
}
|
||||
|
||||
my $name = $self->{_name};
|
||||
$self->{_key_path} = defined($parent_key_path)
|
||||
? "$parent_key_path\\$name"
|
||||
: $name;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub get_timestamp {
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub get_timestamp_as_string {
|
||||
return iso8601(undef);
|
||||
}
|
||||
|
||||
sub get_class_name {
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub is_root {
|
||||
my $self = shift;
|
||||
|
||||
my $offset = $self->{_offset};
|
||||
my $regfile = $self->{_regfile};
|
||||
|
||||
my $rgkn_block = $regfile->get_rgkn;
|
||||
my $offset_to_root_key = $rgkn_block->{_offset_to_root_key};
|
||||
|
||||
# This gives better results than checking id == 0xffffffff
|
||||
return $offset == $offset_to_root_key;
|
||||
}
|
||||
|
||||
sub get_parent {
|
||||
my $self = shift;
|
||||
|
||||
my $regfile = $self->{_regfile};
|
||||
my $offset_to_parent = $self->{_offset_to_parent};
|
||||
my $key_path = $self->{_key_path};
|
||||
|
||||
return if $self->is_root;
|
||||
|
||||
my $grandparent_key_path;
|
||||
my @keys = split(/\\/, $key_path, -1);
|
||||
if (@keys > 2) {
|
||||
$grandparent_key_path = join("\\", @keys[0..$#keys-2]);
|
||||
}
|
||||
|
||||
return Parse::Win32Registry::Win95::Key->new($regfile,
|
||||
$offset_to_parent,
|
||||
$grandparent_key_path);
|
||||
}
|
||||
|
||||
sub get_security {
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub as_string {
|
||||
my $self = shift;
|
||||
|
||||
return $self->get_path;
|
||||
}
|
||||
|
||||
sub parse_info {
|
||||
my $self = shift;
|
||||
|
||||
my $info = sprintf '0x%x rgkn key len=0x%x par=0x%x,child=0x%x,next=0x%x id=0x%x',
|
||||
$self->{_offset},
|
||||
$self->{_length},
|
||||
$self->{_offset_to_parent},
|
||||
$self->{_offset_to_first_child},
|
||||
$self->{_offset_to_next_sibling},
|
||||
$self->{_id};
|
||||
return $info;
|
||||
}
|
||||
|
||||
sub get_subkey_iterator {
|
||||
my $self = shift;
|
||||
|
||||
my $regfile = $self->{_regfile};
|
||||
my $key_path = $self->{_key_path};
|
||||
|
||||
my $offset_to_next_key = $self->{_offset_to_first_child};
|
||||
|
||||
my $end_of_file = $regfile->get_length;
|
||||
my $rgkn_block = $regfile->get_rgkn;
|
||||
my $end_of_rgkn_block = $rgkn_block->get_offset + $rgkn_block->get_length;
|
||||
|
||||
return Parse::Win32Registry::Iterator->new(sub {
|
||||
if ($offset_to_next_key == 0xffffffff) {
|
||||
return; # no more subkeys
|
||||
}
|
||||
if ($offset_to_next_key > $end_of_rgkn_block) {
|
||||
return;
|
||||
}
|
||||
if (my $key = Parse::Win32Registry::Win95::Key->new($regfile,
|
||||
$offset_to_next_key, $key_path))
|
||||
{
|
||||
$offset_to_next_key = $key->{_offset_to_next_sibling};
|
||||
return $key;
|
||||
}
|
||||
else {
|
||||
return; # no more subkeys
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
sub get_value_iterator {
|
||||
my $self = shift;
|
||||
|
||||
my $rgdb_key = $self->{_rgdb_key};
|
||||
if (defined $rgdb_key) {
|
||||
return $rgdb_key->get_value_iterator;
|
||||
}
|
||||
else {
|
||||
return Parse::Win32Registry::Iterator->new(sub {});
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
177
thirdparty/rr/Parse/Win32Registry/Win95/Value.pm
vendored
Normal file
177
thirdparty/rr/Parse/Win32Registry/Win95/Value.pm
vendored
Normal file
@ -0,0 +1,177 @@
|
||||
package Parse::Win32Registry::Win95::Value;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base qw(Parse::Win32Registry::Value);
|
||||
|
||||
use Carp;
|
||||
use Encode;
|
||||
use Parse::Win32Registry::Base qw(:all);
|
||||
|
||||
use constant RGDB_VALUE_HEADER_LENGTH => 0xc;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $regfile = shift;
|
||||
my $offset = shift; # offset to RGDB value entry
|
||||
|
||||
croak 'Missing registry file' if !defined $regfile;
|
||||
croak 'Missing offset' if !defined $offset;
|
||||
|
||||
my $fh = $regfile->get_filehandle;
|
||||
|
||||
# RGDB Value Entry
|
||||
# 0x00 dword = value type
|
||||
# 0x04
|
||||
# 0x08 word = value name length
|
||||
# 0x0a word = value data length
|
||||
# 0x0c = value name [for name length bytes]
|
||||
# + value data [for data length bytes]
|
||||
# Value type may just be a word, not a dword;
|
||||
# following word always appears to be zero.
|
||||
|
||||
sysseek($fh, $offset, 0);
|
||||
my $bytes_read = sysread($fh, my $rgdb_value_entry,
|
||||
RGDB_VALUE_HEADER_LENGTH);
|
||||
if ($bytes_read != RGDB_VALUE_HEADER_LENGTH) {
|
||||
warnf('Could not read RGDB value at 0x%x', $offset);
|
||||
return;
|
||||
}
|
||||
|
||||
my ($type,
|
||||
$name_length,
|
||||
$data_length) = unpack('Vx4vv', $rgdb_value_entry);
|
||||
|
||||
$bytes_read = sysread($fh, my $name, $name_length);
|
||||
if ($bytes_read != $name_length) {
|
||||
warnf('Could not read name for RGDB value at 0x%x', $offset);
|
||||
return;
|
||||
}
|
||||
$name = decode($Parse::Win32Registry::Base::CODEPAGE, $name);
|
||||
|
||||
$bytes_read = sysread($fh, my $data, $data_length);
|
||||
if ($bytes_read != $data_length) {
|
||||
warnf('Could not read data for RGDB value at 0x%x', $offset);
|
||||
return;
|
||||
}
|
||||
|
||||
my $self = {};
|
||||
$self->{_regfile} = $regfile;
|
||||
$self->{_offset} = $offset;
|
||||
$self->{_length} = RGDB_VALUE_HEADER_LENGTH + $name_length + $data_length;
|
||||
$self->{_allocated} = 1;
|
||||
$self->{_tag} = 'rgdb value';
|
||||
$self->{_name} = $name;
|
||||
$self->{_name_length} = $name_length;
|
||||
$self->{_type} = $type;
|
||||
$self->{_data} = $data;
|
||||
$self->{_data_length} = $data_length;
|
||||
bless $self, $class;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub get_data {
|
||||
my $self = shift;
|
||||
|
||||
my $type = $self->get_type;
|
||||
|
||||
my $data = $self->{_data};
|
||||
return if !defined $data; # actually, Win95 value data is always defined
|
||||
|
||||
# apply decoding to appropriate data types
|
||||
if ($type == REG_DWORD) {
|
||||
if (length($data) == 4) {
|
||||
$data = unpack('V', $data);
|
||||
}
|
||||
else {
|
||||
# incorrect length for dword data
|
||||
$data = undef;
|
||||
}
|
||||
}
|
||||
elsif ($type == REG_DWORD_BIG_ENDIAN) {
|
||||
if (length($data) == 4) {
|
||||
$data = unpack('N', $data);
|
||||
}
|
||||
else {
|
||||
# incorrect length for dword data
|
||||
$data = undef;
|
||||
}
|
||||
}
|
||||
elsif ($type == REG_SZ || $type == REG_EXPAND_SZ) {
|
||||
# Snip off any terminating null.
|
||||
# Typically, REG_SZ values will not have a terminating null,
|
||||
# while REG_EXPAND_SZ values will have a terminating null
|
||||
chop $data if substr($data, -1, 1) eq "\0";
|
||||
}
|
||||
elsif ($type == REG_MULTI_SZ) {
|
||||
# Snip off any terminating nulls
|
||||
chop $data if substr($data, -1, 1) eq "\0";
|
||||
chop $data if substr($data, -1, 1) eq "\0";
|
||||
my @multi_sz = split("\0", $data, -1);
|
||||
# Make sure there is at least one empty string
|
||||
@multi_sz = ('') if @multi_sz == 0;
|
||||
return wantarray ? @multi_sz : join($", @multi_sz);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
sub as_regedit_export {
|
||||
my $self = shift;
|
||||
my $version = shift || 5;
|
||||
|
||||
my $name = $self->get_name;
|
||||
my $export = $name eq '' ? '@=' : '"' . $name . '"=';
|
||||
|
||||
my $type = $self->get_type;
|
||||
|
||||
# XXX
|
||||
# if (!defined $self->{_data}) {
|
||||
# $name = $name eq '' ? '@' : qq{"$name"};
|
||||
# return qq{; $name=(invalid data)\n};
|
||||
# }
|
||||
|
||||
if ($type == REG_SZ) {
|
||||
$export .= '"' . $self->get_data . '"';
|
||||
$export .= "\n";
|
||||
}
|
||||
elsif ($type == REG_BINARY) {
|
||||
$export .= 'hex:';
|
||||
$export .= format_octets($self->{_data}, length($export));
|
||||
}
|
||||
elsif ($type == REG_DWORD) {
|
||||
my $data = $self->get_data;
|
||||
$export .= defined($data)
|
||||
? sprintf("dword:%08x", $data)
|
||||
: "dword:";
|
||||
$export .= "\n";
|
||||
}
|
||||
elsif ($type == REG_EXPAND_SZ || $type == REG_MULTI_SZ) {
|
||||
my $data = $version == 4
|
||||
? $self->{_data} # raw data
|
||||
: encode("UCS-2LE", $self->{_data}); # ansi->unicode
|
||||
$export .= sprintf("hex(%x):", $type);
|
||||
$export .= format_octets($data, length($export));
|
||||
}
|
||||
else {
|
||||
$export .= sprintf("hex(%x):", $type);
|
||||
$export .= format_octets($self->{_data}, length($export));
|
||||
}
|
||||
return $export;
|
||||
}
|
||||
|
||||
sub parse_info {
|
||||
my $self = shift;
|
||||
|
||||
my $info = sprintf '0x%x rgdb value len=0x%x "%s" type=%d data,len=0x%x',
|
||||
$self->{_offset},
|
||||
$self->{_length},
|
||||
$self->{_name},
|
||||
$self->{_type},
|
||||
$self->{_data_length};
|
||||
return $info;
|
||||
}
|
||||
|
||||
1;
|
109
thirdparty/rr/Parse/Win32Registry/WinNT/Entry.pm
vendored
Normal file
109
thirdparty/rr/Parse/Win32Registry/WinNT/Entry.pm
vendored
Normal file
@ -0,0 +1,109 @@
|
||||
package Parse::Win32Registry::WinNT::Entry;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base qw(Parse::Win32Registry::Entry);
|
||||
|
||||
use Carp;
|
||||
use Parse::Win32Registry::Base qw(:all);
|
||||
use Parse::Win32Registry::WinNT::Key;
|
||||
use Parse::Win32Registry::WinNT::Value;
|
||||
use Parse::Win32Registry::WinNT::Security;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $regfile = shift;
|
||||
my $offset = shift;
|
||||
|
||||
croak 'Missing registry file' if !defined $regfile;
|
||||
croak 'Missing offset' if !defined $offset;
|
||||
|
||||
my $fh = $regfile->get_filehandle;
|
||||
|
||||
sysseek($fh, $offset, 0);
|
||||
my $bytes_read = sysread($fh, my $entry_header, 8);
|
||||
if ($bytes_read != 8) {
|
||||
return;
|
||||
}
|
||||
|
||||
my ($length,
|
||||
$tag) = unpack('Va2', $entry_header);
|
||||
|
||||
my $allocated = 0;
|
||||
if ($length > 0x7fffffff) {
|
||||
$allocated = 1;
|
||||
$length = (0xffffffff - $length) + 1;
|
||||
}
|
||||
|
||||
$tag = '' if $tag !~ /(nk|vk|lh|lf|li|ri|sk)/;
|
||||
|
||||
if ($tag eq 'nk') {
|
||||
if (my $key = Parse::Win32Registry::WinNT::Key->new($regfile,
|
||||
$offset))
|
||||
{
|
||||
$key->regenerate_path;
|
||||
return $key;
|
||||
}
|
||||
}
|
||||
elsif ($tag eq 'vk') {
|
||||
if (my $value = Parse::Win32Registry::WinNT::Value->new($regfile,
|
||||
$offset))
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
elsif ($tag eq 'sk') {
|
||||
if (my $value = Parse::Win32Registry::WinNT::Security->new($regfile,
|
||||
$offset))
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
my $self = {};
|
||||
$self->{_regfile} = $regfile,
|
||||
$self->{_offset} = $offset,
|
||||
$self->{_length} = $length,
|
||||
$self->{_tag} = $tag,
|
||||
$self->{_allocated} = $allocated,
|
||||
bless $self, $class;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub as_string {
|
||||
my $self = shift;
|
||||
|
||||
my $tag = $self->{_tag};
|
||||
if ($tag eq 'nk') {
|
||||
return '(key entry)';
|
||||
}
|
||||
elsif ($tag eq 'vk') {
|
||||
return '(value entry)';
|
||||
}
|
||||
elsif ($tag eq 'sk') {
|
||||
return '(security entry)';
|
||||
}
|
||||
elsif ($tag =~ /(lh|lf|li|ri)/) {
|
||||
return '(subkey list entry)';
|
||||
}
|
||||
return '(unidentified entry)';
|
||||
}
|
||||
|
||||
sub parse_info {
|
||||
my $self = shift;
|
||||
|
||||
my $tag = $self->{_tag};
|
||||
$tag = defined($tag) && $tag ne ''
|
||||
? $tag . ' '
|
||||
: '.. ';
|
||||
my $info = sprintf '0x%x %slen=0x%x alloc=%d',
|
||||
$self->{_offset},
|
||||
$tag,
|
||||
$self->{_length},
|
||||
$self->{_allocated};
|
||||
return $info;
|
||||
}
|
||||
|
||||
1;
|
297
thirdparty/rr/Parse/Win32Registry/WinNT/File.pm
vendored
Normal file
297
thirdparty/rr/Parse/Win32Registry/WinNT/File.pm
vendored
Normal file
@ -0,0 +1,297 @@
|
||||
package Parse::Win32Registry::WinNT::File;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base qw(Parse::Win32Registry::File);
|
||||
|
||||
use Carp;
|
||||
use Encode;
|
||||
use File::Basename;
|
||||
use Parse::Win32Registry::Base qw(:all);
|
||||
use Parse::Win32Registry::WinNT::Key;
|
||||
|
||||
use constant REGF_HEADER_LENGTH => 0x200;
|
||||
use constant OFFSET_TO_FIRST_HBIN => 0x1000;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $filename = shift or croak "No filename specified";
|
||||
|
||||
open my $fh, '<', $filename or croak "Unable to open '$filename': $!";
|
||||
|
||||
# 0x00 dword = 'regf' signature
|
||||
# 0x04 dword = seq1
|
||||
# 0x08 dword = seq2
|
||||
# 0x0c qword = timestamp
|
||||
# 0x14 dword = major version
|
||||
# 0x18 dword = minor version
|
||||
# 0x1c dword = type (0 = registry file, 1 = log file)
|
||||
# 0x20 dword = (1)
|
||||
# 0x24 dword = offset to root key
|
||||
# 0x28 dword = total length of all hbins (excludes header)
|
||||
# 0x2c dword = (1)
|
||||
# 0x30 = embedded filename
|
||||
|
||||
# Extracted offsets are always relative to first hbin
|
||||
|
||||
my $bytes_read = sysread($fh, my $regf_header, REGF_HEADER_LENGTH);
|
||||
if ($bytes_read != REGF_HEADER_LENGTH) {
|
||||
warnf('Could not read registry file header');
|
||||
return;
|
||||
}
|
||||
|
||||
my ($regf_sig,
|
||||
$seq1,
|
||||
$seq2,
|
||||
$timestamp,
|
||||
$major_version,
|
||||
$minor_version,
|
||||
$type,
|
||||
$offset_to_root_key,
|
||||
$total_hbin_length,
|
||||
$embedded_filename,
|
||||
) = unpack('a4VVa8VVVx4VVx4a64', $regf_header);
|
||||
|
||||
$offset_to_root_key += OFFSET_TO_FIRST_HBIN;
|
||||
|
||||
if ($regf_sig ne 'regf') {
|
||||
warnf('Invalid registry file signature');
|
||||
return;
|
||||
}
|
||||
|
||||
$embedded_filename = unpack('Z*', decode('UCS-2LE', $embedded_filename));
|
||||
|
||||
# The header checksum is the xor of the first 127 dwords.
|
||||
# The checksum is stored in the 128th dword, at offset 0x1fc (508).
|
||||
my $checksum = 0;
|
||||
foreach my $x (unpack('V127', $regf_header)) {
|
||||
$checksum ^= $x;
|
||||
}
|
||||
my $embedded_checksum = unpack('x508V', $regf_header);
|
||||
if ($checksum != $embedded_checksum) {
|
||||
warnf('Invalid checksum for registry file header');
|
||||
}
|
||||
|
||||
my $self = {};
|
||||
$self->{_filehandle} = $fh;
|
||||
$self->{_filename} = $filename;
|
||||
$self->{_length} = (stat $fh)[7];
|
||||
$self->{_offset_to_root_key} = $offset_to_root_key;
|
||||
$self->{_timestamp} = unpack_windows_time($timestamp);
|
||||
$self->{_embedded_filename} = $embedded_filename;
|
||||
$self->{_seq1} = $seq1;
|
||||
$self->{_seq2} = $seq2;
|
||||
$self->{_version} = "$major_version.$minor_version";
|
||||
$self->{_type} = $type;
|
||||
$self->{_total_hbin_length} = $total_hbin_length;
|
||||
$self->{_embedded_checksum} = $embedded_checksum;
|
||||
$self->{_security_cache} = {}; # comment out to disable cache
|
||||
bless $self, $class;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub get_root_key {
|
||||
my $self = shift;
|
||||
|
||||
my $offset_to_root_key = $self->{_offset_to_root_key};
|
||||
|
||||
my $root_key = Parse::Win32Registry::WinNT::Key->new($self,
|
||||
$offset_to_root_key);
|
||||
return $root_key;
|
||||
}
|
||||
|
||||
sub get_virtual_root_key {
|
||||
my $self = shift;
|
||||
my $fake_root = shift;
|
||||
|
||||
my $root_key = $self->get_root_key;
|
||||
return if !defined $root_key;
|
||||
|
||||
if (!defined $fake_root) {
|
||||
# guess virtual root from filename
|
||||
my $filename = basename $self->{_filename};
|
||||
|
||||
if ($filename =~ /NTUSER/i) {
|
||||
$fake_root = 'HKEY_CURRENT_USER';
|
||||
}
|
||||
elsif ($filename =~ /USRCLASS/i) {
|
||||
$fake_root = 'HKEY_CLASSES_ROOT';
|
||||
}
|
||||
elsif ($filename =~ /SOFTWARE/i) {
|
||||
$fake_root = 'HKEY_LOCAL_MACHINE\SOFTWARE';
|
||||
}
|
||||
elsif ($filename =~ /SYSTEM/i) {
|
||||
$fake_root = 'HKEY_LOCAL_MACHINE\SYSTEM';
|
||||
}
|
||||
elsif ($filename =~ /SAM/i) {
|
||||
$fake_root = 'HKEY_LOCAL_MACHINE\SAM';
|
||||
}
|
||||
elsif ($filename =~ /SECURITY/i) {
|
||||
$fake_root = 'HKEY_LOCAL_MACHINE\SECURITY';
|
||||
}
|
||||
else {
|
||||
$fake_root = 'HKEY_UNKNOWN';
|
||||
}
|
||||
}
|
||||
|
||||
$root_key->{_name} = $fake_root;
|
||||
$root_key->{_key_path} = $fake_root;
|
||||
|
||||
return $root_key;
|
||||
}
|
||||
|
||||
sub get_timestamp {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_timestamp};
|
||||
}
|
||||
|
||||
sub get_timestamp_as_string {
|
||||
my $self = shift;
|
||||
|
||||
return iso8601($self->{_timestamp});
|
||||
}
|
||||
|
||||
sub get_embedded_filename {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_embedded_filename};
|
||||
}
|
||||
|
||||
sub get_block_iterator {
|
||||
my $self = shift;
|
||||
|
||||
my $offset_to_next_hbin = OFFSET_TO_FIRST_HBIN;
|
||||
my $end_of_file = $self->{_length};
|
||||
|
||||
return Parse::Win32Registry::Iterator->new(sub {
|
||||
if ($offset_to_next_hbin > $end_of_file) {
|
||||
return; # no more hbins
|
||||
}
|
||||
if (my $hbin = Parse::Win32Registry::WinNT::Hbin->new($self,
|
||||
$offset_to_next_hbin))
|
||||
{
|
||||
return unless $hbin->get_length > 0;
|
||||
$offset_to_next_hbin += $hbin->get_length;
|
||||
return $hbin;
|
||||
}
|
||||
else {
|
||||
return; # no more hbins
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
*get_hbin_iterator = \&get_block_iterator;
|
||||
|
||||
sub _dump_security_cache {
|
||||
my $self = shift;
|
||||
|
||||
if (defined(my $cache = $self->{_security_cache})) {
|
||||
foreach my $offset (sort { $a <=> $b } keys %$cache) {
|
||||
my $security = $cache->{$offset};
|
||||
printf '0x%x %s\n', $offset, $security->as_string;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
package Parse::Win32Registry::WinNT::Hbin;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base qw(Parse::Win32Registry::Entry);
|
||||
|
||||
use Carp;
|
||||
use Parse::Win32Registry::Base qw(:all);
|
||||
use Parse::Win32Registry::WinNT::Entry;
|
||||
|
||||
use constant HBIN_HEADER_LENGTH => 0x20;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $regfile = shift;
|
||||
my $offset = shift;
|
||||
|
||||
croak 'Missing registry file' if !defined $regfile;
|
||||
croak 'Missing offset' if !defined $offset;
|
||||
|
||||
my $fh = $regfile->get_filehandle;
|
||||
|
||||
# 0x00 dword = 'hbin' signature
|
||||
# 0x04 dword = offset from first hbin to this hbin
|
||||
# 0x08 dword = length of this hbin / relative offset to next hbin
|
||||
# 0x14 qword = timestamp (first hbin only)
|
||||
|
||||
# Extracted offsets are always relative to first hbin
|
||||
|
||||
sysseek($fh, $offset, 0);
|
||||
my $bytes_read = sysread($fh, my $hbin_header, HBIN_HEADER_LENGTH);
|
||||
if ($bytes_read != HBIN_HEADER_LENGTH) {
|
||||
return;
|
||||
}
|
||||
|
||||
my ($sig,
|
||||
$offset_to_hbin,
|
||||
$length,
|
||||
$timestamp) = unpack('a4VVx8a8x4', $hbin_header);
|
||||
|
||||
if ($sig ne 'hbin') {
|
||||
return;
|
||||
}
|
||||
|
||||
my $self = {};
|
||||
$self->{_regfile} = $regfile;
|
||||
$self->{_offset} = $offset;
|
||||
$self->{_length} = $length;
|
||||
$self->{_header_length} = HBIN_HEADER_LENGTH;
|
||||
$self->{_allocated} = 1;
|
||||
$self->{_tag} = $sig;
|
||||
$self->{_timestamp} = unpack_windows_time($timestamp);
|
||||
bless $self, $class;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub get_timestamp {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_timestamp};
|
||||
}
|
||||
|
||||
sub get_timestamp_as_string {
|
||||
my $self = shift;
|
||||
|
||||
return iso8601($self->{_timestamp});
|
||||
}
|
||||
|
||||
sub get_entry_iterator {
|
||||
my $self = shift;
|
||||
|
||||
my $regfile = $self->{_regfile};
|
||||
my $offset = $self->{_offset};
|
||||
my $length = $self->{_length};
|
||||
|
||||
my $offset_to_next_entry = $offset + HBIN_HEADER_LENGTH;
|
||||
my $end_of_hbin = $offset + $length;
|
||||
|
||||
return Parse::Win32Registry::Iterator->new(sub {
|
||||
if ($offset_to_next_entry >= $end_of_hbin) {
|
||||
return; # no more entries
|
||||
}
|
||||
if (my $entry = Parse::Win32Registry::WinNT::Entry->new($regfile,
|
||||
$offset_to_next_entry))
|
||||
{
|
||||
return unless $entry->get_length > 0;
|
||||
$offset_to_next_entry += $entry->get_length;
|
||||
return $entry;
|
||||
}
|
||||
else {
|
||||
return; # no more entries
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
1;
|
444
thirdparty/rr/Parse/Win32Registry/WinNT/Key.pm
vendored
Normal file
444
thirdparty/rr/Parse/Win32Registry/WinNT/Key.pm
vendored
Normal file
@ -0,0 +1,444 @@
|
||||
package Parse::Win32Registry::WinNT::Key;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base qw(Parse::Win32Registry::Key);
|
||||
|
||||
use Carp;
|
||||
use Encode;
|
||||
use Parse::Win32Registry::Base qw(:all);
|
||||
use Parse::Win32Registry::WinNT::Value;
|
||||
use Parse::Win32Registry::WinNT::Security;
|
||||
|
||||
use constant NK_HEADER_LENGTH => 0x50;
|
||||
use constant OFFSET_TO_FIRST_HBIN => 0x1000;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $regfile = shift;
|
||||
my $offset = shift; # offset to nk record relative to start of file
|
||||
my $parent_key_path = shift; # parent key path (optional)
|
||||
|
||||
croak 'Missing registry file' if !defined $regfile;
|
||||
croak 'Missing offset' if !defined $offset;
|
||||
|
||||
my $fh = $regfile->get_filehandle;
|
||||
|
||||
# 0x00 dword = key length (negative = allocated)
|
||||
# 0x04 word = 'nk' signature
|
||||
# 0x06 word = flags
|
||||
# 0x08 qword = timestamp
|
||||
# 0x10
|
||||
# 0x14 dword = offset to parent
|
||||
# 0x18 dword = number of subkeys
|
||||
# 0x1c
|
||||
# 0x20 dword = offset to subkey list (lf, lh, ri, li)
|
||||
# 0x24
|
||||
# 0x28 dword = number of values
|
||||
# 0x2c dword = offset to value list
|
||||
# 0x30 dword = offset to security
|
||||
# 0x34 dword = offset to class name
|
||||
# 0x38 dword = max subkey name length
|
||||
# 0x3c dword = max class name length
|
||||
# 0x40 dword = max value name length
|
||||
# 0x44 dword = max value data length
|
||||
# 0x48
|
||||
# 0x4c word = key name length
|
||||
# 0x4e word = class name length
|
||||
# 0x50 = key name [for key name length bytes]
|
||||
|
||||
# Extracted offsets are always relative to first hbin
|
||||
|
||||
sysseek($fh, $offset, 0);
|
||||
my $bytes_read = sysread($fh, my $nk_header, NK_HEADER_LENGTH);
|
||||
if ($bytes_read != NK_HEADER_LENGTH) {
|
||||
warnf('Could not read key at 0x%x', $offset);
|
||||
return;
|
||||
}
|
||||
|
||||
my ($length,
|
||||
$sig,
|
||||
$flags,
|
||||
$timestamp,
|
||||
$offset_to_parent,
|
||||
$num_subkeys,
|
||||
$offset_to_subkey_list,
|
||||
$num_values,
|
||||
$offset_to_value_list,
|
||||
$offset_to_security,
|
||||
$offset_to_class_name,
|
||||
$name_length,
|
||||
$class_name_length,
|
||||
) = unpack('Va2va8x4VVx4Vx4VVVVx20vv', $nk_header);
|
||||
|
||||
$offset_to_parent += OFFSET_TO_FIRST_HBIN
|
||||
if $offset_to_parent != 0xffffffff;
|
||||
$offset_to_subkey_list += OFFSET_TO_FIRST_HBIN
|
||||
if $offset_to_subkey_list != 0xffffffff;
|
||||
$offset_to_value_list += OFFSET_TO_FIRST_HBIN
|
||||
if $offset_to_value_list != 0xffffffff;
|
||||
$offset_to_security += OFFSET_TO_FIRST_HBIN
|
||||
if $offset_to_security != 0xffffffff;
|
||||
$offset_to_class_name += OFFSET_TO_FIRST_HBIN
|
||||
if $offset_to_class_name != 0xffffffff;
|
||||
|
||||
my $allocated = 0;
|
||||
if ($length > 0x7fffffff) {
|
||||
$allocated = 1;
|
||||
$length = (0xffffffff - $length) + 1;
|
||||
}
|
||||
# allocated should be true
|
||||
|
||||
if ($length < NK_HEADER_LENGTH) {
|
||||
warnf('Invalid value entry length at 0x%x', $offset);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($sig ne 'nk') {
|
||||
warnf('Invalid signature for key at 0x%x', $offset);
|
||||
return;
|
||||
}
|
||||
|
||||
$bytes_read = sysread($fh, my $name, $name_length);
|
||||
if ($bytes_read != $name_length) {
|
||||
warnf('Could not read name for key at 0x%x', $offset);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($flags & 0x20) {
|
||||
$name = decode($Parse::Win32Registry::Base::CODEPAGE, $name);
|
||||
}
|
||||
else {
|
||||
$name = decode('UCS-2LE', $name);
|
||||
}
|
||||
|
||||
my $key_path = (defined $parent_key_path)
|
||||
? "$parent_key_path\\$name"
|
||||
: "$name";
|
||||
|
||||
my $class_name;
|
||||
if ($offset_to_class_name != 0xffffffff) {
|
||||
sysseek($fh, $offset_to_class_name + 4, 0);
|
||||
$bytes_read = sysread($fh, $class_name, $class_name_length);
|
||||
if ($bytes_read != $class_name_length) {
|
||||
warnf('Could not read class name at 0x%x', $offset_to_class_name);
|
||||
$class_name = undef;
|
||||
}
|
||||
else {
|
||||
$class_name = decode('UCS-2LE', $class_name);
|
||||
}
|
||||
}
|
||||
|
||||
my $self = {};
|
||||
$self->{_regfile} = $regfile;
|
||||
$self->{_offset} = $offset;
|
||||
$self->{_length} = $length;
|
||||
$self->{_allocated} = $allocated;
|
||||
$self->{_tag} = $sig;
|
||||
$self->{_name} = $name;
|
||||
$self->{_name_length} = $name_length;
|
||||
$self->{_key_path} = $key_path;
|
||||
$self->{_flags} = $flags;
|
||||
$self->{_offset_to_parent} = $offset_to_parent;
|
||||
$self->{_num_subkeys} = $num_subkeys;
|
||||
$self->{_offset_to_subkey_list} = $offset_to_subkey_list;
|
||||
$self->{_num_values} = $num_values;
|
||||
$self->{_offset_to_value_list} = $offset_to_value_list;
|
||||
$self->{_timestamp} = unpack_windows_time($timestamp);
|
||||
$self->{_offset_to_security} = $offset_to_security;
|
||||
$self->{_offset_to_class_name} = $offset_to_class_name;
|
||||
$self->{_class_name_length} = $class_name_length;
|
||||
$self->{_class_name} = $class_name;
|
||||
bless $self, $class;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub get_timestamp {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_timestamp};
|
||||
}
|
||||
|
||||
sub get_timestamp_as_string {
|
||||
my $self = shift;
|
||||
|
||||
return iso8601($self->get_timestamp);
|
||||
}
|
||||
|
||||
sub get_class_name {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_class_name};
|
||||
}
|
||||
|
||||
sub is_root {
|
||||
my $self = shift;
|
||||
|
||||
my $flags = $self->{_flags};
|
||||
return $flags & 4 || $flags & 8;
|
||||
}
|
||||
|
||||
sub get_parent {
|
||||
my $self = shift;
|
||||
|
||||
my $regfile = $self->{_regfile};
|
||||
my $offset_to_parent = $self->{_offset_to_parent};
|
||||
my $key_path = $self->{_key_path};
|
||||
|
||||
return if $self->is_root;
|
||||
|
||||
my $grandparent_key_path;
|
||||
my @keys = split /\\/, $key_path, -1;
|
||||
if (@keys > 2) {
|
||||
$grandparent_key_path = join('\\', @keys[0..$#keys-2]);
|
||||
}
|
||||
|
||||
return Parse::Win32Registry::WinNT::Key->new($regfile,
|
||||
$offset_to_parent,
|
||||
$grandparent_key_path);
|
||||
}
|
||||
|
||||
sub get_security {
|
||||
my $self = shift;
|
||||
|
||||
my $regfile = $self->{_regfile};
|
||||
my $offset_to_security = $self->{_offset_to_security};
|
||||
my $key_path = $self->{_key_path};
|
||||
|
||||
if ($offset_to_security == 0xffffffff) {
|
||||
return;
|
||||
}
|
||||
|
||||
return Parse::Win32Registry::WinNT::Security->new($regfile,
|
||||
$offset_to_security,
|
||||
$key_path);
|
||||
}
|
||||
|
||||
sub as_string {
|
||||
my $self = shift;
|
||||
|
||||
my $string = $self->get_path . ' [' . $self->get_timestamp_as_string . ']';
|
||||
return $string;
|
||||
}
|
||||
|
||||
sub parse_info {
|
||||
my $self = shift;
|
||||
|
||||
my $info = sprintf '0x%x nk len=0x%x alloc=%d "%s" par=0x%x keys=%d,0x%x vals=%d,0x%x sec=0x%x class=0x%x',
|
||||
$self->{_offset},
|
||||
$self->{_length},
|
||||
$self->{_allocated},
|
||||
$self->{_name},
|
||||
$self->{_offset_to_parent},
|
||||
$self->{_num_subkeys}, $self->{_offset_to_subkey_list},
|
||||
$self->{_num_values}, $self->{_offset_to_value_list},
|
||||
$self->{_offset_to_security},
|
||||
$self->{_offset_to_class_name};
|
||||
if (defined $self->{_class_name}) {
|
||||
$info .= sprintf ',len=0x%x', $self->{_class_name_length};
|
||||
}
|
||||
return $info;
|
||||
}
|
||||
|
||||
sub _get_offsets_to_subkeys {
|
||||
my $self = shift;
|
||||
|
||||
# Offset is passed as a parameter for recursive lists such as 'ri'
|
||||
my $offset_to_subkey_list = shift || $self->{_offset_to_subkey_list};
|
||||
|
||||
my $regfile = $self->{_regfile};
|
||||
my $fh = $regfile->get_filehandle;
|
||||
|
||||
return if $offset_to_subkey_list == 0xffffffff
|
||||
|| $self->{_num_subkeys} == 0;
|
||||
|
||||
sysseek($fh, $offset_to_subkey_list, 0);
|
||||
my $bytes_read = sysread($fh, my $subkey_list_header, 8);
|
||||
if ($bytes_read != 8) {
|
||||
warnf('Could not read subkey list header at 0x%x',
|
||||
$offset_to_subkey_list);
|
||||
return;
|
||||
}
|
||||
|
||||
# 0x00 dword = subkey list length (negative = allocated)
|
||||
# 0x04 word = 'lf' signature
|
||||
# 0x06 word = number of entries
|
||||
# 0x08 dword = offset to 1st subkey
|
||||
# 0x0c dword = first four characters of the key name
|
||||
# 0x10 dword = offset to 2nd subkey
|
||||
# 0x14 dword = first four characters of the key name
|
||||
# ...
|
||||
|
||||
# 0x00 dword = subkey list length (negative = allocated)
|
||||
# 0x04 word = 'lh' signature
|
||||
# 0x06 word = number of entries
|
||||
# 0x08 dword = offset to 1st subkey
|
||||
# 0x0c dword = hash of the key name
|
||||
# 0x10 dword = offset to 2nd subkey
|
||||
# 0x14 dword = hash of the key name
|
||||
# ...
|
||||
|
||||
# 0x00 dword = subkey list length (negative = allocated)
|
||||
# 0x04 word = 'ri' signature
|
||||
# 0x06 word = number of entries in ri list
|
||||
# 0x08 dword = offset to 1st lf/lh/li list
|
||||
# 0x0c dword = offset to 2nd lf/lh/li list
|
||||
# 0x10 dword = offset to 3rd lf/lh/li list
|
||||
# ...
|
||||
|
||||
# 0x00 dword = subkey list length (negative = allocated)
|
||||
# 0x04 word = 'li' signature
|
||||
# 0x06 word = number of entries in li list
|
||||
# 0x08 dword = offset to 1st subkey
|
||||
# 0x0c dword = offset to 2nd subkey
|
||||
# ...
|
||||
|
||||
# Extracted offsets are always relative to first hbin
|
||||
|
||||
my @offsets_to_subkeys = ();
|
||||
|
||||
my ($length,
|
||||
$sig,
|
||||
$num_entries,
|
||||
) = unpack('Va2v', $subkey_list_header);
|
||||
|
||||
my $subkey_list_length;
|
||||
if ($sig eq 'lf' || $sig eq 'lh') {
|
||||
$subkey_list_length = 2 * 4 * $num_entries;
|
||||
}
|
||||
elsif ($sig eq 'ri' || $sig eq 'li') {
|
||||
$subkey_list_length = 4 * $num_entries;
|
||||
}
|
||||
else {
|
||||
warnf('Invalid signature for subkey list at 0x%x',
|
||||
$offset_to_subkey_list);
|
||||
return;
|
||||
}
|
||||
|
||||
$bytes_read = sysread($fh, my $subkey_list, $subkey_list_length);
|
||||
if ($bytes_read != $subkey_list_length) {
|
||||
warnf('Could not read subkey list at 0x%x',
|
||||
$offset_to_subkey_list);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($sig eq 'lf') {
|
||||
foreach my $offset (unpack("(Vx4)$num_entries", $subkey_list)) {
|
||||
push @offsets_to_subkeys, OFFSET_TO_FIRST_HBIN + $offset;
|
||||
}
|
||||
}
|
||||
elsif ($sig eq 'lh') {
|
||||
foreach my $offset (unpack("(Vx4)$num_entries", $subkey_list)) {
|
||||
push @offsets_to_subkeys, OFFSET_TO_FIRST_HBIN + $offset;
|
||||
}
|
||||
}
|
||||
elsif ($sig eq 'ri') {
|
||||
foreach my $offset (unpack("V$num_entries", $subkey_list)) {
|
||||
my $offsets_ref =
|
||||
$self->_get_offsets_to_subkeys(OFFSET_TO_FIRST_HBIN + $offset);
|
||||
if (defined $offsets_ref && ref $offsets_ref eq 'ARRAY') {
|
||||
push @offsets_to_subkeys, @{ $offsets_ref };
|
||||
}
|
||||
}
|
||||
}
|
||||
elsif ($sig eq 'li') {
|
||||
foreach my $offset (unpack("V$num_entries", $subkey_list)) {
|
||||
push @offsets_to_subkeys, OFFSET_TO_FIRST_HBIN + $offset;
|
||||
}
|
||||
}
|
||||
|
||||
return \@offsets_to_subkeys;
|
||||
}
|
||||
|
||||
sub get_subkey_iterator {
|
||||
my $self = shift;
|
||||
|
||||
my $regfile = $self->{_regfile};
|
||||
my $key_path = $self->{_key_path};
|
||||
|
||||
my @offsets_to_subkeys = ();
|
||||
if ($self->{_num_subkeys} > 0) {
|
||||
my $offsets_to_subkeys_ref = $self->_get_offsets_to_subkeys;
|
||||
if (defined $offsets_to_subkeys_ref) {
|
||||
@offsets_to_subkeys = @{$self->_get_offsets_to_subkeys};
|
||||
}
|
||||
}
|
||||
|
||||
return Parse::Win32Registry::Iterator->new(sub {
|
||||
while (defined(my $offset_to_subkey = shift @offsets_to_subkeys)) {
|
||||
my $subkey = Parse::Win32Registry::WinNT::Key->new($regfile,
|
||||
$offset_to_subkey, $key_path);
|
||||
if (defined $subkey) {
|
||||
return $subkey;
|
||||
}
|
||||
}
|
||||
return; # no more offsets to subkeys
|
||||
});
|
||||
}
|
||||
|
||||
sub _get_offsets_to_values {
|
||||
my $self = shift;
|
||||
|
||||
my $regfile = $self->{_regfile};
|
||||
my $fh = $regfile->get_filehandle;
|
||||
my $offset_to_value_list = $self->{_offset_to_value_list};
|
||||
|
||||
my $num_values = $self->{_num_values};
|
||||
return if $num_values == 0;
|
||||
# Actually, this could probably just fall through
|
||||
# as unpack("x4V0", ...) would return an empty array.
|
||||
|
||||
my @offsets_to_values = ();
|
||||
|
||||
# 0x00 dword = value list length (negative = allocated)
|
||||
# 0x04 dword = 1st offset
|
||||
# 0x08 dword = 2nd offset
|
||||
# ...
|
||||
|
||||
# Extracted offsets are always relative to first hbin
|
||||
|
||||
sysseek($fh, $offset_to_value_list, 0);
|
||||
my $value_list_length = 0x4 + $num_values * 4;
|
||||
my $bytes_read = sysread($fh, my $value_list, $value_list_length);
|
||||
if ($bytes_read != $value_list_length) {
|
||||
warnf("Could not read value list at 0x%x",
|
||||
$offset_to_value_list);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach my $offset (unpack("x4V$num_values", $value_list)) {
|
||||
push @offsets_to_values, OFFSET_TO_FIRST_HBIN + $offset;
|
||||
}
|
||||
|
||||
return \@offsets_to_values;
|
||||
}
|
||||
|
||||
sub get_value_iterator {
|
||||
my $self = shift;
|
||||
|
||||
my $regfile = $self->{_regfile};
|
||||
my $key_path = $self->{_key_path};
|
||||
|
||||
my @offsets_to_values = ();
|
||||
if ($self->{_num_values} > 0) {
|
||||
my $offsets_to_values_ref = $self->_get_offsets_to_values;
|
||||
if (defined $offsets_to_values_ref) {
|
||||
@offsets_to_values = @{$self->_get_offsets_to_values};
|
||||
}
|
||||
}
|
||||
|
||||
return Parse::Win32Registry::Iterator->new(sub {
|
||||
while (defined(my $offset_to_value = shift @offsets_to_values)) {
|
||||
my $value = Parse::Win32Registry::WinNT::Value->new($regfile,
|
||||
$offset_to_value);
|
||||
if (defined $value) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
return; # no more offsets to values
|
||||
});
|
||||
}
|
||||
|
||||
1;
|
157
thirdparty/rr/Parse/Win32Registry/WinNT/Security.pm
vendored
Normal file
157
thirdparty/rr/Parse/Win32Registry/WinNT/Security.pm
vendored
Normal file
@ -0,0 +1,157 @@
|
||||
package Parse::Win32Registry::WinNT::Security;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base qw(Parse::Win32Registry::Entry);
|
||||
|
||||
use Carp;
|
||||
use Parse::Win32Registry::Base qw(:all);
|
||||
|
||||
use constant SK_HEADER_LENGTH => 0x18;
|
||||
use constant OFFSET_TO_FIRST_HBIN => 0x1000;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $regfile = shift;
|
||||
my $offset = shift; # offset to sk record relative to start of file
|
||||
|
||||
croak 'Missing registry file' if !defined $regfile;
|
||||
croak 'Missing offset' if !defined $offset;
|
||||
|
||||
if (defined(my $cache = $regfile->{_security_cache})) {
|
||||
if (exists $cache->{$offset}) {
|
||||
return $cache->{$offset};
|
||||
}
|
||||
}
|
||||
|
||||
my $fh = $regfile->get_filehandle;
|
||||
|
||||
# 0x00 dword = security length (negative = allocated)
|
||||
# 0x04 word = 'sk' signature
|
||||
# 0x08 dword = offset to previous sk
|
||||
# 0x0c dword = offset to next sk
|
||||
# 0x10 dword = ref count
|
||||
# 0x14 dword = length of security descriptor
|
||||
# 0x18 = start of security descriptor
|
||||
|
||||
# Extracted offsets are always relative to first hbin
|
||||
|
||||
sysseek($fh, $offset, 0);
|
||||
my $bytes_read = sysread($fh, my $sk_header, SK_HEADER_LENGTH);
|
||||
if ($bytes_read != SK_HEADER_LENGTH) {
|
||||
warnf('Could not read security at 0x%x', $offset);
|
||||
return;
|
||||
}
|
||||
|
||||
my ($length,
|
||||
$sig,
|
||||
$offset_to_previous,
|
||||
$offset_to_next,
|
||||
$ref_count,
|
||||
$sd_length,
|
||||
) = unpack('Va2x2VVVV', $sk_header);
|
||||
|
||||
$offset_to_previous += OFFSET_TO_FIRST_HBIN
|
||||
if $offset_to_previous != 0xffffffff;
|
||||
$offset_to_next += OFFSET_TO_FIRST_HBIN
|
||||
if $offset_to_next != 0xffffffff;
|
||||
|
||||
my $allocated = 0;
|
||||
if ($length > 0x7fffffff) {
|
||||
$allocated = 1;
|
||||
$length = (0xffffffff - $length) + 1;
|
||||
}
|
||||
# allocated should be true
|
||||
|
||||
if ($sig ne 'sk') {
|
||||
warnf('Invalid signature for security at 0x%x', $offset);
|
||||
return;
|
||||
}
|
||||
|
||||
$bytes_read = sysread($fh, my $sd_data, $sd_length);
|
||||
if ($bytes_read != $sd_length) {
|
||||
warnf('Could not read security descriptor for security at 0x%x',
|
||||
$offset);
|
||||
return;
|
||||
}
|
||||
|
||||
my $sd = unpack_security_descriptor($sd_data);
|
||||
if (!defined $sd) {
|
||||
warnf('Invalid security descriptor for security at 0x%x',
|
||||
$offset);
|
||||
# Abandon security object if security descriptor is invalid
|
||||
return;
|
||||
}
|
||||
|
||||
my $self = {};
|
||||
$self->{_regfile} = $regfile;
|
||||
$self->{_offset} = $offset;
|
||||
$self->{_length} = $length;
|
||||
$self->{_allocated} = $allocated;
|
||||
$self->{_tag} = $sig;
|
||||
$self->{_offset_to_previous} = $offset_to_previous;
|
||||
$self->{_offset_to_next} = $offset_to_next;
|
||||
$self->{_ref_count} = $ref_count;
|
||||
$self->{_security_descriptor_length} = $sd_length;
|
||||
$self->{_security_descriptor} = $sd;
|
||||
bless $self, $class;
|
||||
|
||||
if (defined(my $cache = $regfile->{_security_cache})) {
|
||||
$cache->{$offset} = $self;
|
||||
}
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub get_previous {
|
||||
my $self = shift;
|
||||
my $regfile = $self->{_regfile};
|
||||
my $offset_to_previous = $self->{_offset_to_previous};
|
||||
|
||||
return Parse::Win32Registry::WinNT::Security->new($regfile,
|
||||
$offset_to_previous);
|
||||
}
|
||||
|
||||
sub get_next {
|
||||
my $self = shift;
|
||||
my $regfile = $self->{_regfile};
|
||||
my $offset_to_next = $self->{_offset_to_next};
|
||||
|
||||
return Parse::Win32Registry::WinNT::Security->new($regfile,
|
||||
$offset_to_next);
|
||||
}
|
||||
|
||||
sub get_reference_count {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_ref_count};
|
||||
}
|
||||
|
||||
sub get_security_descriptor {
|
||||
my $self = shift;
|
||||
|
||||
return $self->{_security_descriptor};
|
||||
}
|
||||
|
||||
sub as_string {
|
||||
my $self = shift;
|
||||
|
||||
return '(security entry)';
|
||||
}
|
||||
|
||||
sub parse_info {
|
||||
my $self = shift;
|
||||
|
||||
my $info = sprintf '0x%x sk len=0x%x alloc=%d prev=0x%x,next=0x%x refs=%d',
|
||||
$self->{_offset},
|
||||
$self->{_length},
|
||||
$self->{_allocated},
|
||||
$self->{_offset_to_previous},
|
||||
$self->{_offset_to_next},
|
||||
$self->{_ref_count};
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
1;
|
332
thirdparty/rr/Parse/Win32Registry/WinNT/Value.pm
vendored
Normal file
332
thirdparty/rr/Parse/Win32Registry/WinNT/Value.pm
vendored
Normal file
@ -0,0 +1,332 @@
|
||||
package Parse::Win32Registry::WinNT::Value;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base qw(Parse::Win32Registry::Value);
|
||||
|
||||
use Carp;
|
||||
use Encode;
|
||||
use Parse::Win32Registry::Base qw(:all);
|
||||
|
||||
use constant VK_HEADER_LENGTH => 0x18;
|
||||
use constant OFFSET_TO_FIRST_HBIN => 0x1000;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $regfile = shift;
|
||||
my $offset = shift; # offset to vk record relative to first hbin
|
||||
|
||||
croak 'Missing registry file' if !defined $regfile;
|
||||
croak 'Missing offset' if !defined $offset;
|
||||
|
||||
my $fh = $regfile->get_filehandle;
|
||||
|
||||
# 0x00 dword = value length (negative = allocated)
|
||||
# 0x04 word = 'vk' signature
|
||||
# 0x06 word = value name length
|
||||
# 0x08 dword = value data length (bit 31 set => data stored inline)
|
||||
# 0x0c dword = offset to data/inline data
|
||||
# 0x10 dword = value type
|
||||
# 0x14 word = flags (bit 1 set => compressed name)
|
||||
# 0x16 word
|
||||
# 0x18 = value name [for value name length bytes]
|
||||
|
||||
# Extracted offsets are always relative to first hbin
|
||||
|
||||
sysseek($fh, $offset, 0);
|
||||
my $bytes_read = sysread($fh, my $vk_header, VK_HEADER_LENGTH);
|
||||
if ($bytes_read != VK_HEADER_LENGTH) {
|
||||
warnf('Could not read value at 0x%x', $offset);
|
||||
return;
|
||||
}
|
||||
|
||||
my ($length,
|
||||
$sig,
|
||||
$name_length,
|
||||
$data_length,
|
||||
$offset_to_data,
|
||||
$type,
|
||||
$flags,
|
||||
) = unpack('Va2vVVVv', $vk_header);
|
||||
|
||||
my $allocated = 0;
|
||||
if ($length > 0x7fffffff) {
|
||||
$allocated = 1;
|
||||
$length = (0xffffffff - $length) + 1;
|
||||
}
|
||||
# allocated should be true
|
||||
|
||||
if ($length < VK_HEADER_LENGTH) {
|
||||
warnf('Invalid value entry length at 0x%x', $offset);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($sig ne 'vk') {
|
||||
warnf('Invalid signature for value at 0x%x', $offset);
|
||||
return;
|
||||
}
|
||||
|
||||
$bytes_read = sysread($fh, my $name, $name_length);
|
||||
if ($bytes_read != $name_length) {
|
||||
warnf('Could not read name for value at 0x%x', $offset);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($flags & 1) {
|
||||
$name = decode($Parse::Win32Registry::Base::CODEPAGE, $name);
|
||||
}
|
||||
else {
|
||||
$name = decode('UCS-2LE', $name);
|
||||
};
|
||||
|
||||
# If the top bit of the data_length is set, then
|
||||
# the value is inline and stored in the offset to data field (at 0xc).
|
||||
my $data;
|
||||
my $data_inline = $data_length >> 31;
|
||||
if ($data_inline) {
|
||||
# REG_DWORDs are always inline, but I've also seen
|
||||
# REG_SZ, REG_BINARY, REG_EXPAND_SZ, and REG_NONE inline
|
||||
$data_length &= 0x7fffffff;
|
||||
if ($data_length > 4) {
|
||||
warnf("Invalid inline data length for value '%s' at 0x%x",
|
||||
$name, $offset);
|
||||
$data = undef;
|
||||
}
|
||||
else {
|
||||
# unpack inline data from header
|
||||
$data = substr($vk_header, 0xc, $data_length);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ($offset_to_data != 0 && $offset_to_data != 0xffffffff) {
|
||||
$offset_to_data += OFFSET_TO_FIRST_HBIN;
|
||||
if ($offset_to_data < ($regfile->get_length - $data_length)) {
|
||||
$data = _extract_data($fh, $offset_to_data, $data_length);
|
||||
}
|
||||
else {
|
||||
warnf("Invalid offset to data for value '%s' at 0x%x",
|
||||
$name, $offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my $self = {};
|
||||
$self->{_regfile} = $regfile;
|
||||
$self->{_offset} = $offset;
|
||||
$self->{_length} = $length;
|
||||
$self->{_allocated} = $allocated;
|
||||
$self->{_tag} = $sig;
|
||||
$self->{_name} = $name;
|
||||
$self->{_name_length} = $name_length;
|
||||
$self->{_type} = $type;
|
||||
$self->{_data} = $data;
|
||||
$self->{_data_length} = $data_length;
|
||||
$self->{_data_inline} = $data_inline;
|
||||
$self->{_offset_to_data} = $offset_to_data;
|
||||
$self->{_flags} = $flags;
|
||||
bless $self, $class;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub _extract_data {
|
||||
my $fh = shift;
|
||||
my $offset_to_data = shift;
|
||||
my $data_length = shift;
|
||||
|
||||
if ($offset_to_data == 0 || $offset_to_data == 0xffffffff) {
|
||||
return undef;
|
||||
}
|
||||
|
||||
sysseek($fh, $offset_to_data, 0);
|
||||
my $bytes_read = sysread($fh, my $data_header, 4);
|
||||
if ($bytes_read != 4) {
|
||||
warnf('Could not read data at 0x%x', $offset_to_data);
|
||||
return undef;
|
||||
}
|
||||
|
||||
my ($max_data_length) = unpack('V', $data_header);
|
||||
|
||||
my $data_allocated = 0;
|
||||
if ($max_data_length > 0x7fffffff) {
|
||||
$data_allocated = 1;
|
||||
$max_data_length = (0xffffffff - $max_data_length) + 1;
|
||||
}
|
||||
# data_allocated should be true
|
||||
|
||||
my $data;
|
||||
|
||||
if ($data_length > $max_data_length) {
|
||||
$bytes_read = sysread($fh, my $db_entry, 8);
|
||||
if ($bytes_read != 8) {
|
||||
warnf('Could not read data at 0x%x', $offset_to_data);
|
||||
return undef;
|
||||
}
|
||||
|
||||
my ($sig, $num_data_blocks, $offset_to_data_block_list)
|
||||
= unpack('a2vV', $db_entry);
|
||||
if ($sig ne 'db') {
|
||||
warnf('Invalid signature for big data at 0x%x', $offset_to_data);
|
||||
return undef;
|
||||
}
|
||||
$offset_to_data_block_list += OFFSET_TO_FIRST_HBIN;
|
||||
|
||||
sysseek($fh, $offset_to_data_block_list + 4, 0);
|
||||
$bytes_read = sysread($fh, my $data_block_list, $num_data_blocks * 4);
|
||||
if ($bytes_read != $num_data_blocks * 4) {
|
||||
warnf('Could not read data block list at 0x%x',
|
||||
$offset_to_data_block_list);
|
||||
return undef;
|
||||
}
|
||||
|
||||
$data = "";
|
||||
my @offsets = map { OFFSET_TO_FIRST_HBIN + $_ }
|
||||
unpack("V$num_data_blocks", $data_block_list);
|
||||
foreach my $offset (@offsets) {
|
||||
sysseek($fh, $offset, 0);
|
||||
$bytes_read = sysread($fh, my $block_header, 4);
|
||||
if ($bytes_read != 4) {
|
||||
warnf('Could not read data block at 0x%x', $offset);
|
||||
return undef;
|
||||
}
|
||||
my ($block_length) = unpack('V', $block_header);
|
||||
if ($block_length > 0x7fffffff) {
|
||||
$block_length = (0xffffffff - $block_length) + 1;
|
||||
}
|
||||
$bytes_read = sysread($fh, my $block_data, $block_length - 8);
|
||||
if ($bytes_read != $block_length - 8) {
|
||||
warnf('Could not read data block at 0x%x', $offset);
|
||||
return undef;
|
||||
}
|
||||
$data .= $block_data;
|
||||
}
|
||||
if (length($data) < $data_length) {
|
||||
warnf("Insufficient data blocks for data at 0x%x", $offset_to_data);
|
||||
return undef;
|
||||
}
|
||||
$data = substr($data, 0, $data_length);
|
||||
return $data;
|
||||
}
|
||||
else {
|
||||
$bytes_read = sysread($fh, $data, $data_length);
|
||||
if ($bytes_read != $data_length) {
|
||||
warnf("Could not read data at 0x%x", $offset_to_data);
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
sub get_data {
|
||||
my $self = shift;
|
||||
|
||||
my $type = $self->get_type;
|
||||
|
||||
my $data = $self->{_data};
|
||||
return if !defined $data;
|
||||
|
||||
# apply decoding to appropriate data types
|
||||
if ($type == REG_DWORD) {
|
||||
if (length($data) == 4) {
|
||||
$data = unpack('V', $data);
|
||||
}
|
||||
else {
|
||||
# incorrect length for dword data
|
||||
$data = undef;
|
||||
}
|
||||
}
|
||||
elsif ($type == REG_DWORD_BIG_ENDIAN) {
|
||||
if (length($data) == 4) {
|
||||
$data = unpack('N', $data);
|
||||
}
|
||||
else {
|
||||
# incorrect length for dword data
|
||||
$data = undef;
|
||||
}
|
||||
}
|
||||
elsif ($type == REG_SZ || $type == REG_EXPAND_SZ) {
|
||||
$data = decode('UCS-2LE', $data);
|
||||
# snip off any terminating null
|
||||
chop $data if substr($data, -1, 1) eq "\0";
|
||||
}
|
||||
elsif ($type == REG_MULTI_SZ) {
|
||||
$data = decode('UCS-2LE', $data);
|
||||
# snip off any terminating nulls
|
||||
chop $data if substr($data, -1, 1) eq "\0";
|
||||
chop $data if substr($data, -1, 1) eq "\0";
|
||||
my @multi_sz = split("\0", $data, -1);
|
||||
# make sure there is at least one empty string
|
||||
@multi_sz = ('') if @multi_sz == 0;
|
||||
return wantarray ? @multi_sz : join($", @multi_sz);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
sub as_regedit_export {
|
||||
my $self = shift;
|
||||
my $version = shift || 5;
|
||||
|
||||
my $name = $self->get_name;
|
||||
my $export = $name eq '' ? '@=' : '"' . $name . '"=';
|
||||
|
||||
my $type = $self->get_type;
|
||||
|
||||
# XXX
|
||||
# if (!defined $self->{_data}) {
|
||||
# $name = $name eq '' ? '@' : qq{"$name"};
|
||||
# return qq{; $name=(invalid data)\n};
|
||||
# }
|
||||
|
||||
if ($type == REG_SZ) {
|
||||
$export .= '"' . $self->get_data . '"';
|
||||
$export .= "\n";
|
||||
}
|
||||
elsif ($type == REG_BINARY) {
|
||||
$export .= "hex:";
|
||||
$export .= format_octets($self->{_data}, length($export));
|
||||
}
|
||||
elsif ($type == REG_DWORD) {
|
||||
my $data = $self->get_data;
|
||||
$export .= defined($data)
|
||||
? sprintf("dword:%08x", $data)
|
||||
: "dword:";
|
||||
$export .= "\n";
|
||||
}
|
||||
elsif ($type == REG_EXPAND_SZ || $type == REG_MULTI_SZ) {
|
||||
my $data = $version == 4
|
||||
? encode("ascii", $self->{_data}) # unicode->ascii
|
||||
: $self->{_data}; # raw data
|
||||
$export .= sprintf("hex(%x):", $type);
|
||||
$export .= format_octets($data, length($export));
|
||||
}
|
||||
else {
|
||||
$export .= sprintf("hex(%x):", $type);
|
||||
$export .= format_octets($self->{_data}, length($export));
|
||||
}
|
||||
return $export;
|
||||
}
|
||||
|
||||
sub parse_info {
|
||||
my $self = shift;
|
||||
|
||||
my $info = sprintf '0x%x vk len=0x%x alloc=%d "%s" type=%d',
|
||||
$self->{_offset},
|
||||
$self->{_length},
|
||||
$self->{_allocated},
|
||||
$self->{_name},
|
||||
$self->{_type};
|
||||
if ($self->{_data_inline}) {
|
||||
$info .= sprintf ' data=inline,len=0x%x',
|
||||
$self->{_data_length};
|
||||
}
|
||||
else {
|
||||
$info .= sprintf ' data=0x%x,len=0x%x',
|
||||
$self->{_offset_to_data},
|
||||
$self->{_data_length};
|
||||
}
|
||||
return $info;
|
||||
}
|
||||
|
||||
1;
|
Loading…
x
Reference in New Issue
Block a user