Merge remote-tracking branch 'upstream/develop' into improved-case-deletion

This commit is contained in:
Richard Cordovano 2019-02-22 18:09:08 -05:00
commit 1bcca8ec70
17 changed files with 1222 additions and 49 deletions

View File

@ -2027,6 +2027,43 @@ public class Case {
throw ex;
}
}
/**
* Create an empty portable case from the current case
*
* @param caseName Case name
* @param portableCaseFolder Case folder - must not exist
*
* @return The portable case database
*
* @throws TskCoreException
*/
public SleuthkitCase createPortableCase(String caseName, File portableCaseFolder) throws TskCoreException {
if (portableCaseFolder.exists()) {
throw new TskCoreException("Portable case folder " + portableCaseFolder.toString() + " already exists");
}
if (! portableCaseFolder.mkdirs()) {
throw new TskCoreException("Error creating portable case folder " + portableCaseFolder.toString());
}
CaseDetails details = new CaseDetails(caseName, getNumber(), getExaminer(),
getExaminerPhone(), getExaminerEmail(), getCaseNotes());
try {
CaseMetadata portableCaseMetadata = new CaseMetadata(Case.CaseType.SINGLE_USER_CASE, portableCaseFolder.toString(),
caseName, details, metadata);
portableCaseMetadata.setCaseDatabaseName(SINGLE_USER_CASE_DB_NAME);
} catch (CaseMetadataException ex) {
throw new TskCoreException("Error creating case metadata", ex);
}
// Create the Sleuthkit case
SleuthkitCase portableSleuthkitCase;
String dbFilePath = Paths.get(portableCaseFolder.toString(), SINGLE_USER_CASE_DB_NAME).toString();
portableSleuthkitCase = SleuthkitCase.newCase(dbFilePath);
return portableSleuthkitCase;
}
/**
* Checks current thread for an interrupt. Usage: checking for user

View File

@ -65,7 +65,7 @@ public final class CaseDetails implements Serializable {
examinerPhone = exPhone;
examinerEmail = exEmail;
caseNotes = notes;
}
}
/**
* Get the case display name
@ -115,7 +115,7 @@ public final class CaseDetails implements Serializable {
/**
* Get the case notes
*
* @return notes - the note asssociated with the case
* @return notes - the note associated with the case
*/
public String getCaseNotes() {
return caseNotes;

View File

@ -95,13 +95,19 @@ public final class CaseMetadata {
private final static String EXAMINER_ELEMENT_EMAIL = "ExaminerEmail"; //NON-NLS
private final static String CASE_ELEMENT_NOTES = "CaseNotes"; //NON-NLS
/*
* Fields from schema version 5
*/
private static final String SCHEMA_VERSION_FIVE = "5.0";
private final static String ORIGINAL_CASE_ELEMENT_NAME = "OriginalCase"; //NON-NLS
/*
* Unread fields, regenerated on save.
*/
private final static String MODIFIED_DATE_ELEMENT_NAME = "ModifiedDate"; //NON-NLS
private final static String AUTOPSY_SAVED_BY_ELEMENT_NAME = "SavedByAutopsyVersion"; //NON-NLS
private final static String CURRENT_SCHEMA_VERSION = SCHEMA_VERSION_FOUR;
private final static String CURRENT_SCHEMA_VERSION = SCHEMA_VERSION_FIVE;
private final Path metadataFilePath;
private Case.CaseType caseType;
@ -112,6 +118,7 @@ public final class CaseMetadata {
private String textIndexName; // Legacy
private String createdDate;
private String createdByVersion;
private CaseMetadata originalMetadata = null; // For portable cases
/**
* Gets the file extension used for case metadata files.
@ -130,21 +137,33 @@ public final class CaseMetadata {
public static DateFormat getDateFormat() {
return new SimpleDateFormat(DATE_FORMAT_STRING, Locale.US);
}
/**
* Constructs a CaseMetadata object for a new case. The metadata is not
* persisted to the case metadata file until writeFile or a setX method is
* called.
*
* @param caseType The type of case.
* @param caseDirectory The case directory.
* @param caseName The immutable name of the case.
* @param caseDetails The details for the case
*/
CaseMetadata(Case.CaseType caseType, String caseDirectory, String caseName, CaseDetails caseDetails) {
this(caseType, caseDirectory, caseName, caseDetails, null);
}
/**
* Constructs a CaseMetadata object for a new case. The metadata is not
* persisted to the case metadata file until writeFile or a setX method is
* called.
*
* @param caseDirectory The case directory.
* @param caseType The type of case.
* @param caseDirectory The case directory.
* @param caseName The immutable name of the case.
* @param caseDisplayName The display name of the case, can be changed by a
* user.
* @param caseNumber The case number.
* @param examiner The name of the case examiner.
* @param caseDetails The details for the case
* @param originalMetadata The metadata object from the original case
*/
CaseMetadata(Case.CaseType caseType, String caseDirectory, String caseName, CaseDetails caseDetails) {
CaseMetadata(Case.CaseType caseType, String caseDirectory, String caseName, CaseDetails caseDetails, CaseMetadata originalMetadata) {
metadataFilePath = Paths.get(caseDirectory, caseDetails.getCaseDisplayName() + FILE_EXTENSION);
this.caseType = caseType;
this.caseName = caseName;
@ -154,6 +173,7 @@ public final class CaseMetadata {
textIndexName = "";
createdByVersion = Version.getVersion();
createdDate = CaseMetadata.DATE_FORMAT.format(new Date());
this.originalMetadata = originalMetadata;
}
/**
@ -417,17 +437,42 @@ public final class CaseMetadata {
/*
* Create the children of the case element.
*/
createChildElement(doc, caseElement, CASE_NAME_ELEMENT_NAME, caseName);
createChildElement(doc, caseElement, CASE_DISPLAY_NAME_ELEMENT_NAME, caseDetails.getCaseDisplayName());
createChildElement(doc, caseElement, CASE_NUMBER_ELEMENT_NAME, caseDetails.getCaseNumber());
createChildElement(doc, caseElement, EXAMINER_ELEMENT_NAME, caseDetails.getExaminerName());
createChildElement(doc, caseElement, EXAMINER_ELEMENT_PHONE, caseDetails.getExaminerPhone());
createChildElement(doc, caseElement, EXAMINER_ELEMENT_EMAIL, caseDetails.getExaminerEmail());
createChildElement(doc, caseElement, CASE_ELEMENT_NOTES, caseDetails.getCaseNotes());
createChildElement(doc, caseElement, CASE_TYPE_ELEMENT_NAME, caseType.toString());
createChildElement(doc, caseElement, CASE_DB_ABSOLUTE_PATH_ELEMENT_NAME, caseDatabasePath);
createChildElement(doc, caseElement, CASE_DB_NAME_RELATIVE_ELEMENT_NAME, caseDatabaseName);
createChildElement(doc, caseElement, TEXT_INDEX_ELEMENT, textIndexName);
createCaseElements(doc, caseElement, this);
/*
* Add original case element
*/
Element originalCaseElement = doc.createElement(ORIGINAL_CASE_ELEMENT_NAME);
rootElement.appendChild(originalCaseElement);
if (originalMetadata != null) {
createChildElement(doc, originalCaseElement, CREATED_DATE_ELEMENT_NAME, originalMetadata.createdDate);
Element originalCaseDetailsElement = doc.createElement(CASE_ELEMENT_NAME);
originalCaseElement.appendChild(originalCaseDetailsElement);
createCaseElements(doc, originalCaseDetailsElement, originalMetadata);
}
}
/**
* Write the case element children for the given metadata object
*
* @param doc The document.
* @param caseElement The case element parent
* @param metadataToWrite The CaseMetadata object to read from
*/
private void createCaseElements(Document doc, Element caseElement, CaseMetadata metadataToWrite) {
CaseDetails caseDetailsToWrite = metadataToWrite.caseDetails;
createChildElement(doc, caseElement, CASE_NAME_ELEMENT_NAME, metadataToWrite.caseName);
createChildElement(doc, caseElement, CASE_DISPLAY_NAME_ELEMENT_NAME, caseDetailsToWrite.getCaseDisplayName());
createChildElement(doc, caseElement, CASE_NUMBER_ELEMENT_NAME, caseDetailsToWrite.getCaseNumber());
createChildElement(doc, caseElement, EXAMINER_ELEMENT_NAME, caseDetailsToWrite.getExaminerName());
createChildElement(doc, caseElement, EXAMINER_ELEMENT_PHONE, caseDetailsToWrite.getExaminerPhone());
createChildElement(doc, caseElement, EXAMINER_ELEMENT_EMAIL, caseDetailsToWrite.getExaminerEmail());
createChildElement(doc, caseElement, CASE_ELEMENT_NOTES, caseDetailsToWrite.getCaseNotes());
createChildElement(doc, caseElement, CASE_TYPE_ELEMENT_NAME, metadataToWrite.caseType.toString());
createChildElement(doc, caseElement, CASE_DB_ABSOLUTE_PATH_ELEMENT_NAME, metadataToWrite.caseDatabasePath);
createChildElement(doc, caseElement, CASE_DB_NAME_RELATIVE_ELEMENT_NAME, metadataToWrite.caseDatabaseName);
createChildElement(doc, caseElement, TEXT_INDEX_ELEMENT, metadataToWrite.textIndexName);
}
/**
@ -505,7 +550,9 @@ public final class CaseMetadata {
examinerEmail = getElementTextContent(caseElement, EXAMINER_ELEMENT_EMAIL, false);
caseNotes = getElementTextContent(caseElement, CASE_ELEMENT_NOTES, false);
}
this.caseDetails = new CaseDetails(caseDisplayName, caseNumber, examinerName, examinerPhone, examinerEmail, caseNotes);
this.caseDetails = new CaseDetails(caseDisplayName, caseNumber, examinerName, examinerPhone, examinerEmail,
caseNotes);
this.caseType = Case.CaseType.fromString(getElementTextContent(caseElement, CASE_TYPE_ELEMENT_NAME, true));
if (null == this.caseType) {
throw new CaseMetadataException("Case metadata file corrupted");

View File

@ -26,6 +26,7 @@ import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.Directory;
import org.sleuthkit.datamodel.FileSystem;
import org.sleuthkit.datamodel.LocalDirectory;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.VolumeSystem;
@ -88,6 +89,13 @@ class ContentChildren extends AbstractContentChildren<Content> {
} else {
children.add(c);
}
} else if (c instanceof LocalDirectory) {
LocalDirectory localDir = (LocalDirectory)c;
if (localDir.isRoot()) {
children.addAll(getDisplayChildren(localDir));
} else {
children.add(c);
}
} else {
children.add(c);
}

View File

@ -46,6 +46,7 @@ import org.sleuthkit.autopsy.datamodel.DataSourcesNode;
import org.sleuthkit.autopsy.datamodel.DisplayableItemNode;
import org.sleuthkit.autopsy.datamodel.RootContentChildren;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentVisitor;
import org.sleuthkit.datamodel.DataSource;
@ -242,6 +243,17 @@ public class ViewContextAction extends AbstractAction {
*/
DisplayableItemNode undecoratedParentNode = (DisplayableItemNode) ((DirectoryTreeFilterNode) parentTreeViewNode).getOriginal();
undecoratedParentNode.setChildNodeSelectionInfo(new ContentNodeSelectionInfo(content));
if (content instanceof BlackboardArtifact) {
BlackboardArtifact artifact = ((BlackboardArtifact) content);
long associatedId = artifact.getObjectID();
try {
Content associatedFileContent = artifact.getSleuthkitCase().getContentById(associatedId);
undecoratedParentNode.setChildNodeSelectionInfo(new ContentNodeSelectionInfo(associatedFileContent));
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Could not find associated content from artifact with id %d", artifact.getId());
}
}
TreeView treeView = treeViewTopComponent.getTree();
treeView.expandNode(parentTreeViewNode);
if (treeViewTopComponent.getSelectedNode().equals(parentTreeViewNode)) {

View File

@ -109,9 +109,9 @@ final class DataSourceIngestPipeline {
this.job.updateDataSourceIngestProgressBarDisplayName(displayName);
this.job.switchDataSourceIngestProgressBarToIndeterminate();
DataSourceIngestPipeline.ingestManager.setIngestTaskProgress(task, module.getDisplayName());
logger.log(Level.INFO, "{0} analysis of {1} (jobId={2}) starting", new Object[]{module.getDisplayName(), this.job.getDataSource().getName(), this.job.getDataSource().getId()}); //NON-NLS
logger.log(Level.INFO, "{0} analysis of {1} (jobId={2}) starting", new Object[]{module.getDisplayName(), this.job.getDataSource().getName(), this.job.getId()}); //NON-NLS
module.process(dataSource, new DataSourceIngestModuleProgress(this.job));
logger.log(Level.INFO, "{0} analysis of {1} (jobId={2}) finished", new Object[]{module.getDisplayName(), this.job.getDataSource().getName(), this.job.getDataSource().getId()}); //NON-NLS
logger.log(Level.INFO, "{0} analysis of {1} (jobId={2}) finished", new Object[]{module.getDisplayName(), this.job.getDataSource().getName(), this.job.getId()}); //NON-NLS
} catch (Throwable ex) { // Catch-all exception firewall
errors.add(new IngestModuleError(module.getDisplayName(), ex));
String msg = ex.getMessage();

View File

@ -78,6 +78,12 @@ final class EncryptionDetectionDataSourceIngestModule implements DataSourceInges
try {
if (dataSource instanceof Image) {
if (((Image) dataSource).getPaths().length == 0) {
logger.log(Level.SEVERE, String.format("Unable to process data source '%s' - image has no paths", dataSource.getName()));
return IngestModule.ProcessResult.ERROR;
}
List<VolumeSystem> volumeSystems = ((Image) dataSource).getVolumeSystems();
for (VolumeSystem volumeSystem : volumeSystems) {
for (Volume volume : volumeSystem.getVolumes()) {

View File

@ -252,3 +252,9 @@ ReportHTMLConfigurationPanel.headerTextField.text=
ReportHTMLConfigurationPanel.footerTextField.text=
ReportHTMLConfigurationPanel.headerLabel.text=Header:
ReportHTMLConfigurationPanel.footerLabel.text=Footer:
CreatePortableCasePanel.selectAllButton.text=Select All
CreatePortableCasePanel.deselectAllButton.text=Deselect All
CreatePortableCasePanel.outputFolderTextField.text=jTextField1
CreatePortableCasePanel.chooseOutputFolderButton.text=Choose folder
CreatePortableCasePanel.jLabel1.text=Export files tagged as:
CreatePortableCasePanel.jLabel2.text=Select output folder:

View File

@ -0,0 +1,476 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 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.report;
import org.openide.util.lookup.ServiceProvider;
import javax.swing.JPanel;
import java.util.logging.Level;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.FileUtil;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.autopsy.datamodel.utils.FileTypeUtils.FileTypeCategory;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.FileSystem;
import org.sleuthkit.datamodel.Image;
import org.sleuthkit.datamodel.LocalFilesDataSource;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
import org.sleuthkit.datamodel.Volume;
import org.sleuthkit.datamodel.VolumeSystem;
/**
* Creates a portable case from tagged files
*/
@ServiceProvider(service = GeneralReportModule.class)
public class CreatePortableCaseModule implements GeneralReportModule {
private static final Logger logger = Logger.getLogger(CreatePortableCaseModule.class.getName());
private static final String FILE_FOLDER_NAME = "PortableCaseFiles";
private static final String UNKNOWN_FILE_TYPE_FOLDER = "Other";
private CreatePortableCasePanel configPanel;
// These are the types for the exported file subfolders
private static final List<FileTypeCategory> FILE_TYPE_CATEGORIES = Arrays.asList(FileTypeCategory.AUDIO, FileTypeCategory.DOCUMENTS,
FileTypeCategory.EXECUTABLE, FileTypeCategory.IMAGE, FileTypeCategory.VIDEO);
private Case currentCase = null;
private SleuthkitCase skCase = null;
private File caseFolder = null;
private File copiedFilesFolder = null;
// Maps old object ID from current case to new object in portable case
private final Map<Long, Content> oldIdToNewContent = new HashMap<>();
// Maps new object ID to the new object
private final Map<Long, Content> newIdToContent = new HashMap<>();
// Maps old TagName to new TagName
private final Map<TagName, TagName> oldTagNameToNewTagName = new HashMap<>();
public CreatePortableCaseModule() {
// Nothing to do here
}
@NbBundle.Messages({
"CreatePortableCaseModule.getName.name=Portable Case"
})
@Override
public String getName() {
return Bundle.CreatePortableCaseModule_getName_name();
}
@NbBundle.Messages({
"CreatePortableCaseModule.getDescription.description=Copies selected tagged items to a new single-user case that will work anywhere"
})
@Override
public String getDescription() {
return Bundle.CreatePortableCaseModule_getDescription_description();
}
@Override
public String getRelativeFilePath() {
return "";
}
/**
* Convenience method to avoid code duplication.
* Assumes that if an exception is supplied then the error is SEVERE. Otherwise
* it is logged as a WARNING.
*
* @param logWarning Warning to write to the log
* @param dialogWarning Warning to write to a pop-up window
* @param ex The exception (can be null)
* @param progressPanel The report progress panel
*/
private void handleError(String logWarning, String dialogWarning, Exception ex, ReportProgressPanel progressPanel) {
if (ex == null) {
logger.log(Level.WARNING, logWarning);
} else {
logger.log(Level.SEVERE, logWarning, ex);
}
MessageNotifyUtil.Message.error(dialogWarning);
progressPanel.setIndeterminate(false);
progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR);
cleanup();
}
@NbBundle.Messages({
"CreatePortableCaseModule.generateReport.verifying=Verifying selected parameters...",
"CreatePortableCaseModule.generateReport.creatingCase=Creating portable case database...",
"CreatePortableCaseModule.generateReport.copyingTags=Copying tags...",
"# {0} - tag name",
"CreatePortableCaseModule.generateReport.copyingFiles=Copying files tagged as {0}...",
"# {0} - output folder",
"CreatePortableCaseModule.generateReport.outputDirDoesNotExist=Output folder {0} does not exist",
"# {0} - output folder",
"CreatePortableCaseModule.generateReport.outputDirIsNotDir=Output folder {0} is not a folder",
"CreatePortableCaseModule.generateReport.noTagsSelected=No tags selected for export.",
"CreatePortableCaseModule.generateReport.caseClosed=Current case has been closed",
"CreatePortableCaseModule.generateReport.errorCopyingTags=Error copying tags",
"CreatePortableCaseModule.generateReport.errorCopyingFiles=Error copying tagged files"
})
@Override
public void generateReport(String reportPath, ReportProgressPanel progressPanel) {
progressPanel.setIndeterminate(true);
progressPanel.start();
progressPanel.updateStatusLabel(Bundle.CreatePortableCaseModule_generateReport_verifying());
// Clear out any old values
cleanup();
// Validate the input parameters
File outputDir = new File(configPanel.getOutputFolder());
if (! outputDir.exists()) {
handleError("Output folder " + outputDir.toString() + " does not exist",
Bundle.CreatePortableCaseModule_generateReport_outputDirDoesNotExist(outputDir.toString()), null, progressPanel);
return;
}
if (! outputDir.isDirectory()) {
handleError("Output folder " + outputDir.toString() + " is not a folder",
Bundle.CreatePortableCaseModule_generateReport_outputDirIsNotDir(outputDir.toString()), null, progressPanel);
return;
}
List<TagName> tagNames = configPanel.getSelectedTagNames();
if (tagNames.isEmpty()) {
handleError("No tags selected for export",
Bundle.CreatePortableCaseModule_generateReport_noTagsSelected(), null, progressPanel);
return;
}
// Save the current case object
try {
currentCase = Case.getCurrentCaseThrows();
} catch (NoCurrentCaseException ex) {
handleError("Current case has been closed",
Bundle.CreatePortableCaseModule_generateReport_caseClosed(), null, progressPanel);
return;
}
// Create the case.
// skCase and caseFolder will be set here.
progressPanel.updateStatusLabel(Bundle.CreatePortableCaseModule_generateReport_creatingCase());
createCase(outputDir, progressPanel);
if (skCase == null) {
// The error has already been handled
return;
}
// Check for cancellation
if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
cleanup();
return;
}
// Copy the selected tags
progressPanel.updateStatusLabel(Bundle.CreatePortableCaseModule_generateReport_copyingTags());
try {
for(TagName tagName:tagNames) {
TagName newTagName = skCase.addOrUpdateTagName(tagName.getDisplayName(), tagName.getDescription(), tagName.getColor(), tagName.getKnownStatus());
oldTagNameToNewTagName.put(tagName, newTagName);
}
} catch (TskCoreException ex) {
handleError("Error copying tags", Bundle.CreatePortableCaseModule_generateReport_errorCopyingTags(), ex, progressPanel);
return;
}
// Copy the tagged files
try {
for(TagName tagName:tagNames) {
// Check for cancellation
if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
return;
}
progressPanel.updateStatusLabel(Bundle.CreatePortableCaseModule_generateReport_copyingFiles(tagName.getDisplayName()));
addFilesToPortableCase(tagName, progressPanel);
}
} catch (TskCoreException ex) {
handleError("Error copying tagged files", Bundle.CreatePortableCaseModule_generateReport_errorCopyingFiles(), ex, progressPanel);
return;
}
// Close the case connections and clear out the maps
cleanup();
progressPanel.complete(ReportProgressPanel.ReportStatus.COMPLETE);
}
/**
* Create the case directory and case database.
* skCase will be set if this completes without error.
*
* @param outputDir The parent for the case folder
* @param progressPanel
*/
@NbBundle.Messages({
"# {0} - case folder",
"CreatePortableCaseModule.createCase.caseDirExists=Case folder {0} already exists",
"CreatePortableCaseModule.createCase.errorCreatingCase=Error creating case",
"# {0} - folder",
"CreatePortableCaseModule.createCase.errorCreatingFolder=Error creating folder {0}",
})
private void createCase(File outputDir, ReportProgressPanel progressPanel) {
// Create the case folder
String caseName = currentCase.getDisplayName() + " (Portable)";
caseFolder = Paths.get(outputDir.toString(), caseName).toFile();
if (caseFolder.exists()) {
handleError("Case folder " + caseFolder.toString() + " already exists",
Bundle.CreatePortableCaseModule_createCase_caseDirExists(caseFolder.toString()), null, progressPanel);
return;
}
// Create the case
try {
skCase = currentCase.createPortableCase(caseName, caseFolder);
} catch (TskCoreException ex) {
handleError("Error creating case " + caseName + " in folder " + caseFolder.toString(),
Bundle.CreatePortableCaseModule_createCase_errorCreatingCase(), ex, progressPanel);
return;
}
// Create the base folder for the copied files
copiedFilesFolder = Paths.get(caseFolder.toString(), FILE_FOLDER_NAME).toFile();
if (! copiedFilesFolder.mkdir()) {
handleError("Error creating folder " + copiedFilesFolder.toString(),
Bundle.CreatePortableCaseModule_createCase_errorCreatingFolder(copiedFilesFolder.toString()), null, progressPanel);
return;
}
// Create subfolders for the copied files
for (FileTypeCategory cat:FILE_TYPE_CATEGORIES) {
File subFolder = Paths.get(copiedFilesFolder.toString(), cat.getDisplayName()).toFile();
if (! subFolder.mkdir()) {
handleError("Error creating folder " + subFolder.toString(),
Bundle.CreatePortableCaseModule_createCase_errorCreatingFolder(subFolder.toString()), null, progressPanel);
return;
}
}
File unknownTypeFolder = Paths.get(copiedFilesFolder.toString(), UNKNOWN_FILE_TYPE_FOLDER).toFile();
if (! unknownTypeFolder.mkdir()) {
handleError("Error creating folder " + unknownTypeFolder.toString(),
Bundle.CreatePortableCaseModule_createCase_errorCreatingFolder(unknownTypeFolder.toString()), null, progressPanel);
return;
}
}
/**
* Add all files with a given tag to the portable case.
*
* @param oldTagName
* @param progressPanel
* @throws TskCoreException
*/
@NbBundle.Messages({
"# {0} - File name",
"CreatePortableCaseModule.addFilesToPortableCase.copyingFile=Copying file {0}",
})
private void addFilesToPortableCase(TagName oldTagName, ReportProgressPanel progressPanel) throws TskCoreException {
// Get all the tags in the current case
List<ContentTag> tags = currentCase.getServices().getTagsManager().getContentTagsByTagName(oldTagName);
// Copy the files into the portable case and tag
for (ContentTag tag : tags) {
// Check for cancellation
if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
return;
}
Content content = tag.getContent();
if (content instanceof AbstractFile) {
AbstractFile file = (AbstractFile) content;
String filePath = file.getParentPath() + file.getName();
progressPanel.updateStatusLabel(Bundle.CreatePortableCaseModule_addFilesToPortableCase_copyingFile(filePath));
long newFileId;
CaseDbTransaction trans = skCase.beginTransaction();
try {
newFileId = copyContent(file, trans);
trans.commit();
} catch (TskCoreException ex) {
trans.rollback();
throw(ex);
}
// Tag the file
if (! oldTagNameToNewTagName.containsKey(tag.getName())) {
throw new TskCoreException("TagName map is missing entry for ID " + tag.getName().getId() + " with display name " + tag.getName().getDisplayName());
}
skCase.addContentTag(newIdToContent.get(newFileId), oldTagNameToNewTagName.get(tag.getName()), tag.getComment(), tag.getBeginByteOffset(), tag.getEndByteOffset());
}
}
}
/**
* Returns the object ID for the given content object in the portable case.
*
* @param content The content object to copy into the portable case
* @param trans The current transaction
*
* @return the new object ID for this content
*
* @throws TskCoreException
*/
private long copyContent(Content content, CaseDbTransaction trans) throws TskCoreException {
// Check if we've already copied this content
if (oldIdToNewContent.containsKey(content.getId())) {
return oldIdToNewContent.get(content.getId()).getId();
}
// Otherwise:
// - Make parent of this object (if applicable)
// - Copy this content
long parentId = 0;
if (content.getParent() != null) {
parentId = copyContent(content.getParent(), trans);
}
Content newContent;
if (content instanceof Image) {
Image image = (Image)content;
newContent = skCase.addImage(image.getType(), image.getSsize(), image.getSize(), image.getName(),
new ArrayList<>(), image.getTimeZone(), image.getMd5(), image.getSha1(), image.getSha256(), image.getDeviceId(), trans);
} else if (content instanceof VolumeSystem) {
VolumeSystem vs = (VolumeSystem)content;
newContent = skCase.addVolumeSystem(parentId, vs.getType(), vs.getOffset(), vs.getBlockSize(), trans);
} else if (content instanceof Volume) {
Volume vs = (Volume)content;
newContent = skCase.addVolume(parentId, vs.getAddr(), vs.getStart(), vs.getLength(),
vs.getDescription(), vs.getFlags(), trans);
} else if (content instanceof FileSystem) {
FileSystem fs = (FileSystem)content;
newContent = skCase.addFileSystem(parentId, fs.getImageOffset(), fs.getFsType(), fs.getBlock_size(),
fs.getBlock_count(), fs.getRoot_inum(), fs.getFirst_inum(), fs.getLastInum(),
fs.getName(), trans);
} else if (content instanceof AbstractFile) {
AbstractFile abstractFile = (AbstractFile)content;
if (abstractFile instanceof LocalFilesDataSource) {
LocalFilesDataSource localFilesDS = (LocalFilesDataSource)abstractFile;
newContent = skCase.addLocalFilesDataSource(localFilesDS.getDeviceId(), localFilesDS.getName(), localFilesDS.getTimeZone(), trans);
} else {
if (abstractFile.isDir()) {
newContent = skCase.addLocalDirectory(parentId, abstractFile.getName(), trans);
} else {
try {
// Copy the file
String fileName = abstractFile.getId() + "-" + FileUtil.escapeFileName(abstractFile.getName());
String exportSubFolder = getExportSubfolder(abstractFile);
File exportFolder = Paths.get(copiedFilesFolder.toString(), exportSubFolder).toFile();
File localFile = new File(exportFolder, fileName);
ContentUtils.writeToFile(abstractFile, localFile);
// Get the new parent object in the portable case database
Content oldParent = abstractFile.getParent();
if (! oldIdToNewContent.containsKey(oldParent.getId())) {
throw new TskCoreException("Parent of file with ID " + abstractFile.getId() + " has not been created");
}
Content newParent = oldIdToNewContent.get(oldParent.getId());
// Construct the relative path to the copied file
String relativePath = FILE_FOLDER_NAME + File.separator + exportSubFolder + File.separator + fileName;
newContent = skCase.addLocalFile(abstractFile.getName(), relativePath, abstractFile.getSize(),
abstractFile.getCtime(), abstractFile.getCrtime(), abstractFile.getAtime(), abstractFile.getMtime(),
abstractFile.getMd5Hash(), abstractFile.getKnown(), abstractFile.getMIMEType(),
true, TskData.EncodingType.NONE,
newParent, trans);
} catch (IOException ex) {
throw new TskCoreException("Error copying file " + abstractFile.getName() + " with original obj ID "
+ abstractFile.getId(), ex);
}
}
}
} else {
throw new TskCoreException("Trying to copy unexpected Content type " + content.getClass().getName());
}
// Save the new object
oldIdToNewContent.put(content.getId(), newContent);
newIdToContent.put(newContent.getId(), newContent);
return oldIdToNewContent.get(content.getId()).getId();
}
/**
* Return the subfolder name for this file based on MIME type
*
* @param abstractFile the file
*
* @return the name of the appropriate subfolder for this file type
*/
private String getExportSubfolder(AbstractFile abstractFile) {
if (abstractFile.getMIMEType() == null || abstractFile.getMIMEType().isEmpty()) {
return UNKNOWN_FILE_TYPE_FOLDER;
}
for (FileTypeCategory cat:FILE_TYPE_CATEGORIES) {
if (cat.getMediaTypes().contains(abstractFile.getMIMEType())) {
return cat.getDisplayName();
}
}
return UNKNOWN_FILE_TYPE_FOLDER;
}
/**
* Clear out the maps and other fields and close the database connections.
*/
private void cleanup() {
oldIdToNewContent.clear();
newIdToContent.clear();
oldTagNameToNewTagName.clear();
currentCase = null;
if (skCase != null) {
// We want to close the database connections here but it is currently not possible. JIRA-4736
skCase = null;
}
caseFolder = null;
copiedFilesFolder = null;
}
@Override
public JPanel getConfigurationPanel() {
configPanel = new CreatePortableCasePanel();
return configPanel;
}
}

View File

@ -0,0 +1,159 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<Component id="jLabel1" min="-2" max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
</Group>
<Group type="102" alignment="1" attributes="0">
<Group type="103" groupAlignment="1" attributes="0">
<Group type="102" alignment="1" attributes="0">
<Component id="jLabel2" min="-2" max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
</Group>
<Component id="jScrollPane1" max="32767" attributes="0"/>
</Group>
<EmptySpace min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" max="-2" attributes="0">
<Component id="deselectAllButton" linkSize="1" max="32767" attributes="0"/>
<Component id="selectAllButton" linkSize="1" max="32767" attributes="0"/>
</Group>
</Group>
<Group type="102" alignment="0" attributes="0">
<Component id="outputFolderTextField" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="chooseOutputFolderButton" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
<EmptySpace min="-2" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Component id="jLabel1" min="-2" max="-2" attributes="0"/>
<EmptySpace min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<Component id="selectAllButton" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="deselectAllButton" min="-2" max="-2" attributes="0"/>
</Group>
<Component id="jScrollPane1" pref="132" max="32767" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="1" attributes="0">
<Group type="102" attributes="0">
<Component id="jLabel2" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="outputFolderTextField" min="-2" max="-2" attributes="0"/>
</Group>
<Component id="chooseOutputFolderButton" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Component class="javax.swing.JButton" name="selectAllButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/report/Bundle.properties" key="CreatePortableCasePanel.selectAllButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[99, 23]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[99, 23]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[99, 23]"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="selectAllButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JButton" name="deselectAllButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/report/Bundle.properties" key="CreatePortableCasePanel.deselectAllButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="deselectAllButtonActionPerformed"/>
</Events>
</Component>
<Container class="javax.swing.JScrollPane" name="jScrollPane1">
<AuxValues>
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Component class="javax.swing.JList" name="tagNamesListBox">
<Properties>
<Property name="model" type="javax.swing.ListModel" editor="org.netbeans.modules.form.editors2.ListModelEditor">
<StringArray count="0"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;String&gt;"/>
</AuxValues>
</Component>
</SubComponents>
</Container>
<Component class="javax.swing.JTextField" name="outputFolderTextField">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/report/Bundle.properties" key="CreatePortableCasePanel.outputFolderTextField.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JButton" name="chooseOutputFolderButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/report/Bundle.properties" key="CreatePortableCasePanel.chooseOutputFolderButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="chooseOutputFolderButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JLabel" name="jLabel1">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/report/Bundle.properties" key="CreatePortableCasePanel.jLabel1.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="jLabel2">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/report/Bundle.properties" key="CreatePortableCasePanel.jLabel2.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
</SubComponents>
</Form>

View File

@ -0,0 +1,329 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2019 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.report;
import java.awt.Component;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import javax.swing.JCheckBox;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.ListCellRenderer;
import javax.swing.ListModel;
import javax.swing.event.ListDataListener;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException;
/**
*
*/
@NbBundle.Messages({
"CreatePortableCasePanel.error.errorTitle=Error getting tag names for case",
"CreatePortableCasePanel.error.noOpenCase=There is no case open",
"CreatePortableCasePanel.error.errorLoadingTags=Error loading tags",
})
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
class CreatePortableCasePanel extends javax.swing.JPanel {
private static final long serialVersionUID = 1L;
private List<TagName> tagNames;
private final Map<String, Boolean> tagNameSelections = new LinkedHashMap<>();
private final TagNamesListModel tagsNamesListModel = new TagNamesListModel();
private final TagsNamesListCellRenderer tagsNamesRenderer = new TagsNamesListCellRenderer();
/**
* Creates new form CreatePortableCasePanel
*/
public CreatePortableCasePanel() {
initComponents();
customizeComponents();
}
private void customizeComponents() {
populateTagNameComponents();
try {
outputFolderTextField.setText(Case.getCurrentCaseThrows().getReportDirectory());
} catch (NoCurrentCaseException ex) {
Logger.getLogger(CreatePortableCasePanel.class.getName()).log(Level.SEVERE, "Exception while getting open case.", ex);
JOptionPane.showMessageDialog(this, Bundle.CreatePortableCasePanel_error_noOpenCase(), Bundle.CreatePortableCasePanel_error_errorTitle(), JOptionPane.ERROR_MESSAGE);
}
}
private void populateTagNameComponents() {
// Get the tag names in use for the current case.
try {
tagNames = Case.getCurrentCaseThrows().getServices().getTagsManager().getTagNamesInUse();
} catch (TskCoreException ex) {
Logger.getLogger(CreatePortableCasePanel.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex);
JOptionPane.showMessageDialog(this, Bundle.CreatePortableCasePanel_error_errorLoadingTags(), Bundle.CreatePortableCasePanel_error_errorTitle(), JOptionPane.ERROR_MESSAGE);
} catch (NoCurrentCaseException ex) {
Logger.getLogger(CreatePortableCasePanel.class.getName()).log(Level.SEVERE, "Exception while getting open case.", ex);
JOptionPane.showMessageDialog(this, Bundle.CreatePortableCasePanel_error_noOpenCase(), Bundle.CreatePortableCasePanel_error_errorTitle(), JOptionPane.ERROR_MESSAGE);
}
// Mark the tag names as unselected. Note that tagNameSelections is a
// LinkedHashMap so that order is preserved and the tagNames and tagNameSelections
// containers are "parallel" containers.
for (TagName tagName : tagNames) {
tagNameSelections.put(tagName.getDisplayName(), Boolean.FALSE);
}
// Set up the tag names JList component to be a collection of check boxes
// for selecting tag names. The mouse click listener updates tagNameSelections
// to reflect user choices.
tagNamesListBox.setModel(tagsNamesListModel);
tagNamesListBox.setCellRenderer(tagsNamesRenderer);
tagNamesListBox.setVisibleRowCount(-1);
tagNamesListBox.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent evt) {
JList<?> list = (JList) evt.getSource();
int index = list.locationToIndex(evt.getPoint());
if (index > -1) {
String value = tagsNamesListModel.getElementAt(index);
tagNameSelections.put(value, !tagNameSelections.get(value));
list.repaint();
}
}
});
}
/**
* Gets the subset of the tag names in use selected by the user.
*
* @return A list, possibly empty, of TagName data transfer objects (DTOs).
*/
List<TagName> getSelectedTagNames() {
List<TagName> selectedTagNames = new ArrayList<>();
for (TagName tagName : tagNames) {
if (tagNameSelections.get(tagName.getDisplayName())) {
selectedTagNames.add(tagName);
}
}
return selectedTagNames;
}
/**
* Get the output folder
*
* @return The output folder for the portable case
*/
String getOutputFolder() {
return outputFolderTextField.getText();
}
// This class is a list model for the tag names JList component.
private class TagNamesListModel implements ListModel<String> {
@Override
public int getSize() {
return tagNames.size();
}
@Override
public String getElementAt(int index) {
return tagNames.get(index).getDisplayName();
}
@Override
public void addListDataListener(ListDataListener l) {
}
@Override
public void removeListDataListener(ListDataListener l) {
}
}
// This class renders the items in the tag names JList component as JCheckbox components.
private class TagsNamesListCellRenderer extends JCheckBox implements ListCellRenderer<String> {
private static final long serialVersionUID = 1L;
@Override
public Component getListCellRendererComponent(JList<? extends String> list, String value, int index, boolean isSelected, boolean cellHasFocus) {
if (value != null) {
setEnabled(list.isEnabled());
setSelected(tagNameSelections.get(value));
setFont(list.getFont());
setBackground(list.getBackground());
setForeground(list.getForeground());
setText(value);
return this;
}
return new JLabel();
}
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
selectAllButton = new javax.swing.JButton();
deselectAllButton = new javax.swing.JButton();
jScrollPane1 = new javax.swing.JScrollPane();
tagNamesListBox = new javax.swing.JList<>();
outputFolderTextField = new javax.swing.JTextField();
chooseOutputFolderButton = new javax.swing.JButton();
jLabel1 = new javax.swing.JLabel();
jLabel2 = new javax.swing.JLabel();
org.openide.awt.Mnemonics.setLocalizedText(selectAllButton, org.openide.util.NbBundle.getMessage(CreatePortableCasePanel.class, "CreatePortableCasePanel.selectAllButton.text")); // NOI18N
selectAllButton.setMaximumSize(new java.awt.Dimension(99, 23));
selectAllButton.setMinimumSize(new java.awt.Dimension(99, 23));
selectAllButton.setPreferredSize(new java.awt.Dimension(99, 23));
selectAllButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
selectAllButtonActionPerformed(evt);
}
});
org.openide.awt.Mnemonics.setLocalizedText(deselectAllButton, org.openide.util.NbBundle.getMessage(CreatePortableCasePanel.class, "CreatePortableCasePanel.deselectAllButton.text")); // NOI18N
deselectAllButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
deselectAllButtonActionPerformed(evt);
}
});
jScrollPane1.setViewportView(tagNamesListBox);
outputFolderTextField.setText(org.openide.util.NbBundle.getMessage(CreatePortableCasePanel.class, "CreatePortableCasePanel.outputFolderTextField.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(chooseOutputFolderButton, org.openide.util.NbBundle.getMessage(CreatePortableCasePanel.class, "CreatePortableCasePanel.chooseOutputFolderButton.text")); // NOI18N
chooseOutputFolderButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
chooseOutputFolderButtonActionPerformed(evt);
}
});
org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(CreatePortableCasePanel.class, "CreatePortableCasePanel.jLabel1.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(jLabel2, org.openide.util.NbBundle.getMessage(CreatePortableCasePanel.class, "CreatePortableCasePanel.jLabel2.text")); // NOI18N
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(jLabel1)
.addGap(0, 0, Short.MAX_VALUE))
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
.addGroup(layout.createSequentialGroup()
.addComponent(jLabel2)
.addGap(0, 0, Short.MAX_VALUE))
.addComponent(jScrollPane1))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
.addComponent(deselectAllButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(selectAllButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))
.addGroup(layout.createSequentialGroup()
.addComponent(outputFolderTextField)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(chooseOutputFolderButton)))
.addContainerGap())
);
layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {deselectAllButton, selectAllButton});
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(jLabel1)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(selectAllButton, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(deselectAllButton))
.addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 132, Short.MAX_VALUE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
.addGroup(layout.createSequentialGroup()
.addComponent(jLabel2)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(outputFolderTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addComponent(chooseOutputFolderButton))
.addContainerGap())
);
}// </editor-fold>//GEN-END:initComponents
private void selectAllButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_selectAllButtonActionPerformed
for (TagName tagName : tagNames) {
tagNameSelections.put(tagName.getDisplayName(), Boolean.TRUE);
}
tagNamesListBox.repaint();
}//GEN-LAST:event_selectAllButtonActionPerformed
private void deselectAllButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deselectAllButtonActionPerformed
for (TagName tagName : tagNames) {
tagNameSelections.put(tagName.getDisplayName(), Boolean.FALSE);
}
tagNamesListBox.repaint();
}//GEN-LAST:event_deselectAllButtonActionPerformed
private void chooseOutputFolderButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_chooseOutputFolderButtonActionPerformed
JFileChooser fileChooser = new JFileChooser();
fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
File currentSelection = new File(outputFolderTextField.getText());
if (currentSelection.exists()) {
fileChooser.setCurrentDirectory(currentSelection);
}
int result = fileChooser.showOpenDialog(this);
if (result == JFileChooser.APPROVE_OPTION) {
String outputDir = fileChooser.getSelectedFile().getAbsolutePath();
outputFolderTextField.setText(outputDir);
}
}//GEN-LAST:event_chooseOutputFolderButtonActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton chooseOutputFolderButton;
private javax.swing.JButton deselectAllButton;
private javax.swing.JLabel jLabel1;
private javax.swing.JLabel jLabel2;
private javax.swing.JScrollPane jScrollPane1;
private javax.swing.JTextField outputFolderTextField;
private javax.swing.JButton selectAllButton;
private javax.swing.JList<String> tagNamesListBox;
// End of variables declaration//GEN-END:variables
}

View File

@ -63,7 +63,7 @@ public class ImageGalleryDatabaseUpdateService implements AutopsyService {
return;
}
ProgressIndicator progress = context.getProgressIndicator();
progress.start(Bundle.ImageGalleryDatabaseUpdateService_openCaseResources_progressMessage_start());
progress.progress(Bundle.ImageGalleryDatabaseUpdateService_openCaseResources_progressMessage_start());
try {
ImageGalleryModule.createController(context.getCase());
} catch (TskCoreException ex) {

View File

@ -1577,11 +1577,20 @@ public final class DrawableDB {
return map;
}
/**
* Get the build status for the given data source.
* Will return UNKNOWN if the data source is not yet in the database.
*
* @param dataSourceId
*
* @return The status of the data source or UKNOWN if it is not found.
*
* @throws TskCoreException
*/
public DrawableDbBuildStatusEnum getDataSourceDbBuildStatus(Long dataSourceId) throws TskCoreException {
Map<Long, DrawableDbBuildStatusEnum> statusMap = getDataSourceDbBuildStatus();
if (statusMap.containsKey(dataSourceId) == false) {
throw new TskCoreException("Data Source ID not found: " + dataSourceId);
return DrawableDbBuildStatusEnum.UNKNOWN;
}
return statusMap.get(dataSourceId);
}

View File

@ -1,5 +1,5 @@
<hr/>
<p><i>Copyright &#169; 2012-2018 Basis Technology. Generated on $date<br/>
<p><i>Copyright &#169; 2012-2019 Basis Technology. Generated on $date<br/>
This work is licensed under a
<a rel="license" href="http://creativecommons.org/licenses/by-sa/3.0/us/">Creative Commons Attribution-Share Alike 3.0 United States License</a>.
</i></p>

View File

@ -65,6 +65,7 @@ The following topics are available here:
- \subpage live_triage_page
- \subpage advanced_page
- \subpage experimental_page
- \subpage translations_page
If the topic you need is not listed, refer to the <a href="http://wiki.sleuthkit.org/index.php?title=Autopsy_User%27s_Guide">Autopsy Wiki</a> or join the <a href="https://lists.sourceforge.net/lists/listinfo/sleuthkit-users">SleuthKit User List</a> at SourceForge.

View File

@ -0,0 +1,75 @@
/*! \page translations_page Translating This Document
The Autopsy user base is global. You can help out by translating the UI and this documentation.
\section translations_doc Translating Documentation
This section outlines how to translate this user documentation. To translate, you will need:
- A git account
- Basic familiarity with git
- Text editor
The Autopsy documentation is created by [Doxygen](http://www.doxygen.nl/) from ".dox" text files in the [docs/doxygen-user](https://github.com/sleuthkit/autopsy/tree/develop/docs/doxygen-user) folder in the Github repository.
The first step is to fork the [Autopsy Repository](https://github.com/sleuthkit/autopsy) into your Git account and make a clone of it into your environment so that you can make edits to the files.
As you are editing, you can review your documentation by installing Doxygen and running 'doxygen' from within the translations folder. It will save the HTML to the 'user-docs' folder.
\subsection translations_doc_start Translating To a New Language
If there is not already documentation in a language, then you need to make a copy of the entire English 'doxygen-user' folder and name it 'doxygen-user_AB' where AB is replaced by the 2 character [country code] (http://www.lingoes.net/en/translator/langcode.htm). For example, 'doxygen-user_fr' for French and 'doxygen-user_ja' for Japanese.
Edit the Doxyfile to update the OUTPUT_LANGUAGE field. For English it has:
\code
OUTPUT_LANGUAGE = English
\endcode
Now, simply start translating the English documents.
\subsection translations_doc_update Updating The Documentation
When new releases are made and the English documentation is updated, the other languages should be updated as well. To determine what has changed:
- First, determine when the last time the documentation was changed. From a command line, you can change into the translated documentation folder and type:
\code
$ cd docs/doxygen-user_fr
$ git log -n 1 .
commit 94e4b1042af47908dd4a0b2959b3f6c3d4af1111
Author: John Doe <jdoe@sleuthkit.org>
Date: Tue Jan 1 22:56:09 2019 -0500
update to quick start
\endcode
This shows you that commit 94e4b1042af47908dd4a0b2959b3f6c3d4af1111 was the last translation update to occur for the French version.
- Next, determine what changed in the English version since then:
\code
$ git diff 94e4b1042af47908dd4a0b2959b3f6c3d4af1111 ../doxygen-user
diff --git a/docs/doxygen-user/central_repo.dox b/docs/doxygen-user/central_repo.dox
index 83d3407e8..e8cd01c1b 100644
--- a/docs/doxygen-user/central_repo.dox
+++ b/docs/doxygen-user/central_repo.dox
@@ -79,6 +79,16 @@ Descriptions of the property types:
- Phone numbers are currently only extracted from call logs, contact lists and message, which come from the Android Analyzer module.
- <b>USB Devices</b>
- USB device properties come from the registry parsing in the Recent Activity Module.
+- <b>Wireless Networks</b>
+ - Wireless networks are correlated on SSIDs, and come from the registry par
\endcode
- Update the translated documentation accordingly based on what changed in the English version.
- If you do not get to complete all of the changes, you should create a TODO.txt file that lists what was not updated so that other people know that not everything was updated.
\subsection translations_doc_commit Committing the Documentation
You should submit a Github Pull Request when:
- You complete a language.
- You don't have time to do more work, but want to submit what you did.
To get the code committed, send a [pull request](https://help.github.com/articles/about-pull-requests/) to the main Autopsy repository.
*/

View File

@ -39,17 +39,26 @@ def gitSleuthkitCheckout(branch, branchOwner):
# passed is a global variable that gets set to non-zero integer
# When an error occurs
global passed
if (branchOwner==ORIGIN_OWNER):
cmd = ['git','checkout', branch]
else:
if branch in getSleuthkitBranchList(branchOwner):
#add the remotes
#if the branch owner was origin substitute in the name of that owner
if (branchOwner==ORIGIN_OWNER):
gitHubUser="sleuthkit"
else:
gitHubUser=branchOwner
checkout=['git','checkout','-b',branchOwner+'-'+branch]
print("Command run:" + " ".join(checkout))
passed = subprocess.call(checkout, stdout=sys.stdout,cwd=TSK_HOME)
cmd = ['git','pull', "/".join(["https://github.com", branchOwner, "sleuthkit.git"]), branch]
cmd = ['git','pull', "/".join(["https://github.com", gitHubUser, "sleuthkit.git"]), branch]
if passed != 0: #0 would be success
#unable to create new branch return instead of pulling
return
passed = subprocess.call(cmd,stdout=sys.stdout,cwd=TSK_HOME)
print("Command run:" + " ".join(cmd))
passed = subprocess.call(cmd,stdout=sys.stdout,cwd=TSK_HOME)
if (passed == 0):
sys.exit() #exit if successful
else:
print("Branch: " + branch + " does not exist for owner: " + branchOwner)
def parseXML(xmlFile):
'''
@ -70,40 +79,39 @@ def main():
if not TSK_HOME:
sys.exit(1)
print('Please set TSK_HOME env variable')
# Get the Autopsy branch being used. Travis and Appveyor
# will tell us where a pull request is directed
TRAVIS=os.getenv("TRAVIS",False)
APPVEYOR=os.getenv("APPVEYOR",False)
if TRAVIS == "true":
CURRENT_BRANCH=os.getenv("TRAVIS_PULL_REQUEST_BRANCH",False)
BRANCH_OWNER=os.getenv("TRAVIS_PULL_REQUEST_SLUG", False).split('/')[0]
CURRENT_BRANCH=os.getenv("TRAVIS_PULL_REQUEST_BRANCH","") #make default empty string which is same vaule used when not a PR
if (CURRENT_BRANCH != ""): #if it is a PR
BRANCH_OWNER=os.getenv("TRAVIS_PULL_REQUEST_SLUG", ORIGIN_OWNER+"/"+CURRENT_BRANCH).split('/')[0] #default owner is ORIGIN_OWNER
gitSleuthkitCheckout(CURRENT_BRANCH, BRANCH_OWNER)
TARGET_BRANCH=os.getenv("TRAVIS_BRANCH",DEVELOP_BRANCH)
elif APPVEYOR:
CURRENT_BRANCH=os.getenv("APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH",False)
BRANCH_OWNER=os.getenv("APPVEYOR_PULL_REQUEST_HEAD_REPO_NAME", False).split('/')[0]
CURRENT_BRANCH=os.getenv("APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH","") #make default same as value used by travis for readability of code
if (CURRENT_BRANCH != ""): #if it is a PR
BRANCH_OWNER=os.getenv("APPVEYOR_PULL_REQUEST_HEAD_REPO_NAME", ORIGIN_OWNER+"/"+CURRENT_BRANCH).split('/')[0] #default owner is ORIGIN_OWNER
gitSleuthkitCheckout(CURRENT_BRANCH, BRANCH_OWNER)
TARGET_BRANCH=os.getenv("APPVEYOR_REPO_BRANCH",DEVELOP_BRANCH)
else:
cmd=['git','rev-parse','--abbrev-ref','HEAD']
output = subprocess.check_output(cmd)
CURRENT_BRANCH=output.strip()
BRANCH_OWNER=ORIGIN_OWNER
TARGET_BRANCH=output.strip()
# If we are in an Autopsy release branch, then use the
# info in TSKVersion.xml to find the corresponding TSK
# release branch. For other branches, we don't always
# trust that TSKVersion has been updated.
if CURRENT_BRANCH.startswith('release'):
if TARGET_BRANCH.startswith('release'):
version = parseXML('TSKVersion.xml')
RELEASE_BRANCH = "release-"+version
gitSleuthkitCheckout(RELEASE_BRANCH, BRANCH_OWNER)
#If it failed try the origin release branch
if passed != 0:
gitSleuthkitCheckout(RELEASE_BRANCH, ORIGIN_OWNER)
# Check if the same branch exists in TSK (develop->develop, custom1->custom1, etc.)
#Check if the same user has a release branch which corresponds to this release branch
gitSleuthkitCheckout(RELEASE_BRANCH, ORIGIN_OWNER)
else:
gitSleuthkitCheckout(CURRENT_BRANCH, BRANCH_OWNER)
gitSleuthkitCheckout(TARGET_BRANCH, ORIGIN_OWNER)
# Otherwise, default to origin develop
if passed != 0:
gitSleuthkitCheckout(DEVELOP_BRANCH, ORIGIN_OWNER)
gitSleuthkitCheckout(DEVELOP_BRANCH, ORIGIN_OWNER)
if passed != 0:
print('Error checking out a Sleuth Kit branch')