diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddLocalFilesTask.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddLocalFilesTask.java index feae1ddb6b..185f696a97 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddLocalFilesTask.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddLocalFilesTask.java @@ -22,12 +22,12 @@ import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.casemodule.services.FileManager; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor; +import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; -import org.sleuthkit.autopsy.casemodule.services.FileManager; -import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.LocalFilesDataSource; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskDataException; diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties b/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties index 701a7b1261..4d0b858691 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties @@ -1,12 +1,10 @@ ContactDetailsPane.nameLabel.text=Placeholder SummaryViewer.countsPanel.border.title=Counts SummaryViewer.contactsLabel.text=Contacts: -SummaryViewer.attachmentsLabel.text=Media Attachments: OutlineViewPanel.messageLabel.text= SummaryViewer.messagesDataLabel.text=messages SummaryViewer.callLogsDataLabel.text=callLogs SummaryViewer.contactsDataLabel.text=contacts -SummaryViewer.attachmentsDataLabel.text=attachments SummaryViewer.messagesLabel.text=Messages: SummaryViewer.callLogsLabel.text=Call Logs: ThreadRootMessagePanel.showAllCheckBox.text=Show All Messages @@ -19,3 +17,7 @@ MessageViewer.showingMessagesLabel.text=Showing Messages for Thread: MessageViewer.backButton.AccessibleContext.accessibleDescription= MessageViewer.backButton.text=Threads MessageViewer.showAllButton.text=All Messages +SummaryViewer.thumbnailCntLabel.text=Media Attachments: +SummaryViewer.attachmentsLable.text=Total Attachments: +SummaryViewer.thumbnailsDataLabel.text=attachments +SummaryViewer.attachmentDataLabel.text=count diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED index f2aa0df8e1..f79bdaa464 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED @@ -37,12 +37,10 @@ MessageViewer_viewMessage_selected=Selected MessageViewer_viewMessage_unthreaded=Unthreaded SummaryViewer.countsPanel.border.title=Counts SummaryViewer.contactsLabel.text=Contacts: -SummaryViewer.attachmentsLabel.text=Media Attachments: OutlineViewPanel.messageLabel.text= SummaryViewer.messagesDataLabel.text=messages SummaryViewer.callLogsDataLabel.text=callLogs SummaryViewer.contactsDataLabel.text=contacts -SummaryViewer.attachmentsDataLabel.text=attachments SummaryViewer.messagesLabel.text=Messages: SummaryViewer.callLogsLabel.text=Call Logs: SummaryViewer_CaseRefNameColumn_Title=Case Name @@ -61,3 +59,7 @@ MessageViewer.showingMessagesLabel.text=Showing Messages for Thread: MessageViewer.backButton.AccessibleContext.accessibleDescription= MessageViewer.backButton.text=Threads MessageViewer.showAllButton.text=All Messages +SummaryViewer.thumbnailCntLabel.text=Media Attachments: +SummaryViewer.attachmentsLable.text=Total Attachments: +SummaryViewer.thumbnailsDataLabel.text=attachments +SummaryViewer.attachmentDataLabel.text=count diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/SelectionInfo.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/SelectionInfo.java index 45ece0a5be..1e83de831e 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/SelectionInfo.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/SelectionInfo.java @@ -24,6 +24,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.datamodel.Account; import org.sleuthkit.datamodel.AccountDeviceInstance; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -37,30 +38,30 @@ import org.sleuthkit.datamodel.TskCoreException; * VisualizationPane */ public final class SelectionInfo { - + private static final Logger logger = Logger.getLogger(SelectionInfo.class.getName()); private final Set selectedNodes; private final Set selectedEdges; private final CommunicationsFilter communicationFilter; private final Set accounts; - + private Set accountArtifacts = null; private SelectionSummary summary = null; /** * Wraps the details of the currently selected accounts. * - * @param selectedNodes Selected AccountDeviceInstances - * @param selectedEdges Selected pairs of AccountDeviceInstances - * @param communicationFilter Currently selected communications filters + * @param selectedNodes Selected AccountDeviceInstances + * @param selectedEdges Selected pairs of AccountDeviceInstances + * @param communicationFilter Currently selected communications filters */ - public SelectionInfo(Set selectedNodes, Set selectedEdges, + public SelectionInfo(Set selectedNodes, Set selectedEdges, CommunicationsFilter communicationFilter) { this.selectedNodes = selectedNodes; this.selectedEdges = selectedEdges; this.communicationFilter = communicationFilter; - + accounts = new HashSet<>(); selectedNodes.forEach((instance) -> { accounts.add(instance.getAccount()); @@ -75,10 +76,10 @@ public final class SelectionInfo { public Set getSelectedNodes() { return selectedNodes; } - + /** * Returns the currently selected edges - * + * * @return Set of GraphEdge objects */ public Set getSelectedEdges() { @@ -93,16 +94,17 @@ public final class SelectionInfo { public CommunicationsFilter getCommunicationsFilter() { return communicationFilter; } - + public Set getAccounts() { return accounts; } - + /** * Get the set of relationship sources from the case database - * + * * @return the relationship sources (may be empty) - * @throws TskCoreException + * + * @throws TskCoreException */ Set getRelationshipSources() throws TskCoreException { @@ -112,28 +114,28 @@ public final class SelectionInfo { } catch (NoCurrentCaseException ex) { throw new TskCoreException("Failed to get current case", ex); } - + Set relationshipSources = new HashSet<>(); try { // Add all nodes relationshipSources.addAll(communicationManager.getRelationshipSources(getSelectedNodes(), getCommunicationsFilter())); - + // Add all edges. For edges, the relationship has to include both endpoints for (SelectionInfo.GraphEdge edge : getSelectedEdges()) { - relationshipSources.addAll(communicationManager.getRelationshipSources(edge.getStartNode(), + relationshipSources.addAll(communicationManager.getRelationshipSources(edge.getStartNode(), edge.getEndNode(), getCommunicationsFilter())); } } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Failed to get relationships from case database.", ex); //NON-NLS - + } return relationshipSources; } - + public Set getArtifacts() { - if(accountArtifacts == null) { + if (accountArtifacts == null) { accountArtifacts = new HashSet<>(); - + try { final Set relationshipSources = getRelationshipSources(); relationshipSources.stream().filter((content) -> (content instanceof BlackboardArtifact)).forEachOrdered((content) -> { @@ -144,58 +146,67 @@ public final class SelectionInfo { return accountArtifacts; } } - + return accountArtifacts; } - + public SelectionSummary getSummary() { - if(summary == null) { + if (summary == null) { summary = new SelectionSummary(); } - + return summary; } - - final class SelectionSummary{ + + final class SelectionSummary { + int attachmentCnt; int messagesCnt; int emailCnt; int callLogCnt; int contactsCnt; - + int mediaCnt; + SelectionSummary() { getCounts(); } - - private void getCounts(){ - for(BlackboardArtifact artifact: getArtifacts()) { + + private void getCounts() { + for (BlackboardArtifact artifact : getArtifacts()) { BlackboardArtifact.ARTIFACT_TYPE fromID = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID()); - if(null != fromID) switch (fromID) { - case TSK_EMAIL_MSG: - emailCnt++; - break; - case TSK_CALLLOG: - callLogCnt++; - break; - case TSK_MESSAGE: - messagesCnt++; - break; - case TSK_CONTACT: - contactsCnt++; - break; - default: - break; + if (null != fromID) { + switch (fromID) { + case TSK_EMAIL_MSG: + emailCnt++; + break; + case TSK_CALLLOG: + callLogCnt++; + break; + case TSK_MESSAGE: + messagesCnt++; + break; + case TSK_CONTACT: + contactsCnt++; + break; + default: + break; + } } - try{ - attachmentCnt+= artifact.getChildrenCount(); + try { + attachmentCnt += artifact.getChildrenCount(); + for (Content childContent : artifact.getChildren()) { + if (ImageUtils.thumbnailSupported(childContent)) { + mediaCnt++; + } + } } catch (TskCoreException ex) { logger.log(Level.WARNING, String.format("Exception thrown " - + "from getChildrenCount artifactID: %d", + + "from getChildrenCount artifactID: %d", artifact.getArtifactID()), ex); //NON-NLS } } } - + public int getAttachmentCnt() { return attachmentCnt; } @@ -215,24 +226,29 @@ public final class SelectionInfo { public int getContactsCnt() { return contactsCnt; } + + public int getThumbnailCnt() { + return mediaCnt; + } } /** * Utility class to represent an edge from the graph visualization. */ public static class GraphEdge { + AccountDeviceInstance startNode; AccountDeviceInstance endNode; - + public GraphEdge(AccountDeviceInstance startNode, AccountDeviceInstance endNode) { this.startNode = startNode; this.endNode = endNode; } - + public AccountDeviceInstance getStartNode() { return startNode; } - + public AccountDeviceInstance getEndNode() { return endNode; } diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.form b/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.form index 85ddc8a2c8..73ef1a68ba 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.form +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.form @@ -41,16 +41,18 @@ - + + - + + - + @@ -74,10 +76,14 @@ - - + + + + + + @@ -104,17 +110,17 @@ - + - + - + - + @@ -139,6 +145,20 @@ + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.java index 5602152ba2..539c1eb2d3 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryViewer.java @@ -104,10 +104,11 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi } else { SelectionSummary summaryDetails = info.getSummary(); - attachmentsDataLabel.setText(Integer.toString(summaryDetails.getAttachmentCnt())); + thumbnailsDataLabel.setText(Integer.toString(summaryDetails.getThumbnailCnt())); callLogsDataLabel.setText(Integer.toString(summaryDetails.getCallLogCnt())); contactsDataLabel.setText(Integer.toString(summaryDetails.getContactsCnt())); messagesDataLabel.setText(Integer.toString(summaryDetails.getMessagesCnt() + summaryDetails.getEmailCnt())); + attachmentDataLabel.setText(Integer.toString(summaryDetails.getAttachmentCnt())); fileReferencesPanel.showOutlineView(); @@ -131,7 +132,7 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); - attachmentsLabel.setEnabled(enabled); + thumbnailCntLabel.setEnabled(enabled); callLogsLabel.setEnabled(enabled); contactsLabel.setEnabled(enabled); messagesLabel.setEnabled(enabled); @@ -144,10 +145,11 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi * Clears the text fields and OutlookViews. */ private void clearControls() { - attachmentsDataLabel.setText(""); + thumbnailsDataLabel.setText(""); callLogsDataLabel.setText(""); contactsDataLabel.setText(""); messagesDataLabel.setText(""); + attachmentDataLabel.setText(""); fileReferencesPanel.setNode(new AbstractNode(Children.LEAF)); caseReferencesPanel.setNode(new AbstractNode(Children.LEAF)); @@ -187,11 +189,13 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi contactsLabel = new javax.swing.JLabel(); messagesLabel = new javax.swing.JLabel(); callLogsLabel = new javax.swing.JLabel(); - attachmentsLabel = new javax.swing.JLabel(); - attachmentsDataLabel = new javax.swing.JLabel(); + thumbnailCntLabel = new javax.swing.JLabel(); + thumbnailsDataLabel = new javax.swing.JLabel(); messagesDataLabel = new javax.swing.JLabel(); callLogsDataLabel = new javax.swing.JLabel(); contactsDataLabel = new javax.swing.JLabel(); + attachmentsLable = new javax.swing.JLabel(); + attachmentDataLabel = new javax.swing.JLabel(); fileReferencesPanel = new org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel(); caseReferencesPanel = new org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel(); @@ -205,9 +209,9 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi org.openide.awt.Mnemonics.setLocalizedText(callLogsLabel, org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.callLogsLabel.text")); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(attachmentsLabel, org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.attachmentsLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(thumbnailCntLabel, org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.thumbnailCntLabel.text")); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(attachmentsDataLabel, org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.attachmentsDataLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(thumbnailsDataLabel, org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.thumbnailsDataLabel.text")); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(messagesDataLabel, org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.messagesDataLabel.text")); // NOI18N @@ -215,6 +219,10 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi org.openide.awt.Mnemonics.setLocalizedText(contactsDataLabel, org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.contactsDataLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(attachmentsLable, org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.attachmentsLable.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(attachmentDataLabel, org.openide.util.NbBundle.getMessage(SummaryViewer.class, "SummaryViewer.attachmentDataLabel.text")); // NOI18N + javax.swing.GroupLayout countsPanelLayout = new javax.swing.GroupLayout(countsPanel); countsPanel.setLayout(countsPanelLayout); countsPanelLayout.setHorizontalGroup( @@ -225,14 +233,16 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi .addComponent(messagesLabel) .addComponent(callLogsLabel) .addComponent(contactsLabel) - .addComponent(attachmentsLabel)) + .addComponent(thumbnailCntLabel) + .addComponent(attachmentsLable)) .addGap(18, 18, 18) .addGroup(countsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(attachmentsDataLabel) + .addComponent(attachmentDataLabel) + .addComponent(thumbnailsDataLabel) .addComponent(contactsDataLabel) .addComponent(callLogsDataLabel) .addComponent(messagesDataLabel)) - .addContainerGap(959, Short.MAX_VALUE)) + .addContainerGap(845, Short.MAX_VALUE)) ); countsPanelLayout.setVerticalGroup( countsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -251,9 +261,12 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi .addComponent(contactsDataLabel)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(countsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(attachmentsLabel) - .addComponent(attachmentsDataLabel)) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addComponent(thumbnailCntLabel) + .addComponent(thumbnailsDataLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(countsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(attachmentsLable) + .addComponent(attachmentDataLabel))) ); gridBagConstraints = new java.awt.GridBagConstraints(); @@ -287,8 +300,8 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JLabel attachmentsDataLabel; - private javax.swing.JLabel attachmentsLabel; + private javax.swing.JLabel attachmentDataLabel; + private javax.swing.JLabel attachmentsLable; private javax.swing.JLabel callLogsDataLabel; private javax.swing.JLabel callLogsLabel; private org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel caseReferencesPanel; @@ -298,6 +311,8 @@ public class SummaryViewer extends javax.swing.JPanel implements RelationshipsVi private org.sleuthkit.autopsy.communications.relationships.OutlineViewPanel fileReferencesPanel; private javax.swing.JLabel messagesDataLabel; private javax.swing.JLabel messagesLabel; + private javax.swing.JLabel thumbnailCntLabel; + private javax.swing.JLabel thumbnailsDataLabel; // End of variables declaration//GEN-END:variables } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java index eeacf491bf..e02a4ff776 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java @@ -336,7 +336,6 @@ public abstract class AbstractAbstractFileNode extends A backgroundTasksPool.submit(new GetSCOTask( new WeakReference<>(this), weakPcl)); - properties.add(new NodeProperty<>(LOCATION.toString(), LOCATION.toString(), NO_DESCR, getContentPath(content))); properties.add(new NodeProperty<>(MOD_TIME.toString(), MOD_TIME.toString(), NO_DESCR, ContentUtils.getStringTime(content.getMtime(), content))); properties.add(new NodeProperty<>(CHANGED_TIME.toString(), CHANGED_TIME.toString(), NO_DESCR, ContentUtils.getStringTime(content.getCtime(), content))); properties.add(new NodeProperty<>(ACCESS_TIME.toString(), ACCESS_TIME.toString(), NO_DESCR, ContentUtils.getStringTime(content.getAtime(), content))); @@ -345,6 +344,7 @@ public abstract class AbstractAbstractFileNode extends A properties.add(new NodeProperty<>(FLAGS_DIR.toString(), FLAGS_DIR.toString(), NO_DESCR, content.getDirFlagAsString())); properties.add(new NodeProperty<>(FLAGS_META.toString(), FLAGS_META.toString(), NO_DESCR, content.getMetaFlagsAsString())); properties.add(new NodeProperty<>(KNOWN.toString(), KNOWN.toString(), NO_DESCR, content.getKnown().getName())); + properties.add(new NodeProperty<>(LOCATION.toString(), LOCATION.toString(), NO_DESCR, getContentPath(content))); properties.add(new NodeProperty<>(MD5HASH.toString(), MD5HASH.toString(), NO_DESCR, StringUtils.defaultString(content.getMd5Hash()))); properties.add(new NodeProperty<>(MIMETYPE.toString(), MIMETYPE.toString(), NO_DESCR, StringUtils.defaultString(content.getMIMEType()))); properties.add(new NodeProperty<>(EXTENSION.toString(), EXTENSION.toString(), NO_DESCR, content.getNameExtension())); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/utils/LocalFileImporter.java b/Core/src/org/sleuthkit/autopsy/datamodel/utils/LocalFileImporter.java new file mode 100644 index 0000000000..95a47d5399 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/utils/LocalFileImporter.java @@ -0,0 +1,209 @@ +/* + * + * Autopsy Forensic Browser + * + * Copyright 2019 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datamodel.utils; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.SpecialDirectory; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData; + +/** + * Utility class for adding local files with specified paths in the data source. + * It is currently assumed that the data source is empty to start or that at + * least the paths to the files being added do not exist; no checks will be done + * to see if folders exist prior to creating them through addLocalFile(). + */ +public class LocalFileImporter { + private static final Logger logger = Logger.getLogger(LocalFileImporter.class.getName()); + + SleuthkitCase.CaseDbTransaction globalTrans = null; + boolean useSingleTransaction = true; + SleuthkitCase sleuthkitCase; + private final Map localFileDirMap = new HashMap<>(); + + /** + * Create a LocalFileImporter. + * + * @param sleuthkitCase The current SleuthkitCase + */ + public LocalFileImporter(SleuthkitCase sleuthkitCase) { + this.sleuthkitCase = sleuthkitCase; + this.useSingleTransaction = false; + } + + /** + * Create a LocalFileImporter. The caller is responsible for committing + * or rolling back the transaction. + * + * @param sleuthkitCase The current SleuthkitCase + * @param trans The open CaseDbTransaction + */ + public LocalFileImporter(SleuthkitCase sleuthkitCase, SleuthkitCase.CaseDbTransaction trans) { + this.sleuthkitCase = sleuthkitCase; + this.globalTrans = trans; + this.useSingleTransaction = true; + } + + /** + * Add a local file to the database with the specified parameters. Will create + * any necessary parent folders. + * + * Will not fail if the fileOnDisk does not exist. + * + * @param fileOnDisk The local file on disk + * @param name The name to use in the data source + * @param parentPath The path to use in the data source + * @param ctime Change time + * @param crtime Created time + * @param atime Access time + * @param mtime Modified time + * @param dataSource The data source to add the file to + * + * @return The AbstractFile that was just created + * + * @throws TskCoreException + */ + public AbstractFile addLocalFile(File fileOnDisk, String name, String parentPath, + Long ctime, Long crtime, Long atime, Long mtime, + DataSource dataSource) throws TskCoreException { + + // Get the parent folder, creating it and any of its parent folders if necessary + SpecialDirectory parentDir = getOrMakeDirInDataSource(new File(parentPath), dataSource); + + SleuthkitCase.CaseDbTransaction trans = null; + try { + if (useSingleTransaction) { + trans = globalTrans; + } else { + trans = sleuthkitCase.beginTransaction(); + } + + // Try to get the file size + long size = 0; + if (fileOnDisk.exists()) { + size = fileOnDisk.length(); + } + + // Create the new file + AbstractFile file = sleuthkitCase.addLocalFile(name, fileOnDisk.getAbsolutePath(), size, + ctime, crtime, atime, mtime, + true, TskData.EncodingType.NONE, parentDir, trans); + + if (! useSingleTransaction) { + trans.commit(); + } + return file; + } catch (TskCoreException ex) { + if ((!useSingleTransaction) && (null != trans)) { + try { + trans.rollback(); + } catch (TskCoreException ex2) { + logger.log(Level.SEVERE, String.format("Failed to rollback transaction after exception: %s", ex.getMessage()), ex2); + } + } + throw ex; + } + } + + /** + * Returns the SpecialDirectory object corresponding to the given directory, creating + * it and its parents as needed. + * + * @param directory The file to get the SpecialDirectory for + * @param dataSource The data source + * + * @return The SpecialDirectory object corresponding to the given file + * + * @throws TskCoreException + */ + private SpecialDirectory getOrMakeDirInDataSource(File directory, Content dataSource) throws TskCoreException { + if ((directory == null) || directory.getPath().isEmpty()) { + throw new TskCoreException("Can not create directory from null path"); + } + + // Check if we've already created it + if (localFileDirMap.containsKey(directory.toString())) { + return localFileDirMap.get(directory.toString()); + } + + File parent = directory.getParentFile(); + if (parent == null) { + // This is the root of the path and it isn't in the map, so create it + SpecialDirectory dir = createLocalFilesDir(dataSource.getId(), directory.getName()); + localFileDirMap.put(directory.getName(), dir); + return dir; + + } else { + // Create everything above this in the tree, and then add the parent folder + SpecialDirectory parentDir = getOrMakeDirInDataSource(parent, dataSource); + SpecialDirectory dir = createLocalFilesDir(parentDir.getId(), directory.getName()); + localFileDirMap.put(directory.getPath(), dir); + return dir; + } + } + + /** + * Create a new LocalDirectory + * + * @param parentId The object ID for parent + * @param name The name of the new local directory + * + * @return The new LocalDirectory + * + * @throws TskCoreException + */ + private SpecialDirectory createLocalFilesDir(long parentId, String name) throws TskCoreException { + SleuthkitCase.CaseDbTransaction trans = null; + + try { + if (useSingleTransaction) { + trans = globalTrans; + } else { + trans = sleuthkitCase.beginTransaction(); + } + SpecialDirectory dir; + + dir = sleuthkitCase.addLocalDirectory(parentId, name, trans); + + if (! useSingleTransaction) { + trans.commit(); + } + return dir; + } catch (TskCoreException ex) { + if (( !useSingleTransaction) && (null != trans)) { + try { + trans.rollback(); + } catch (TskCoreException ex2) { + logger.log(Level.SEVERE, String.format("Failed to rollback transaction after exception: %s", ex.getMessage()), ex2); + } + } + throw ex; + } + } + +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java index 3c206049d6..14aceaf14f 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java @@ -179,9 +179,7 @@ public class DataResultFilterNode extends FilterNode { newPs.setShortDescription(ps.getShortDescription()); newPs.put(ps.getProperties()); - if (newPs.remove(AbstractFsContentNode.HIDE_PARENT) != null) { - newPs.remove(AbstractFilePropertyType.LOCATION.toString()); - } + newPs.remove(AbstractFsContentNode.HIDE_PARENT); propertySets[i] = newPs; } } diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/Bundle.properties b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/Bundle.properties index 65fa3dd72d..5c9cf30524 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/Bundle.properties @@ -105,7 +105,7 @@ EditNonFullPathsRulePanel.minSizeCheckbox.text=Minimum size: NewRulePanel.chooseLabel.text=Choose the type of rule ConfigVisualPanel1.configureDriveRadioButton.text_1=Configure selected external drive: ConfigVisualPanel1.configureFolderRadioButton.text_1=Configure in a folder: -ConfigVisualPanel1.descriptionTextArea.text=Select a location for the Logical Imager. This location will contain the imaging program and a configuration file. If that location already contains a configuration file, it will be loaded to edit. Imaging results will be saved to this location, so ensure it has enough free space. +ConfigVisualPanel1.descriptionTextArea.text=Select a location for the Logical Imager. This location will contain the imaging program and a configuration file. If that location already contains a configuration file, it will be loaded to edit. Imaging results will be saved to this location, so ensure it has enough free space. Drives with FAT format are not supported. ConfigVisualPanel1.refreshButton.text=Refresh ConfigVisualPanel3.saveButton.text=Save ConfigVisualPanel3.configLabel.text=Logical Imager config file save status: @@ -122,3 +122,4 @@ EditNonFullPathsRulePanel.fileNamesInfoLabel.text=File names are case insensitiv EditNonFullPathsRulePanel.extensionsInfoLabel.text=Extensions are case insensitive. ConfigVisualPanel2.promptBeforeExit.text=Prompt before exiting imager ConfigVisualPanel2.promptBeforeExit.actionCommand= +ConfigVisualPanel2.createVHDCheckBox.text=Create VHD diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/Bundle.properties-MERGED index 3e6aad34de..ef1ca31571 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/Bundle.properties-MERGED @@ -26,10 +26,16 @@ ConfigVisualPanel1.chooseFileTitle=Select a Logical Imager configuration # {0} - filename ConfigVisualPanel1.configFileIsEmpty=Configuration file {0} is empty ConfigVisualPanel1.configurationError=Configuration error +# {0} - root +# {1} - description +# {2} - size with unit +# {3} - file system +ConfigVisualPanel1.driveListItem={0} ({1}) ({2}) - File system: {3} ConfigVisualPanel1.fileNameExtensionFilter=Configuration JSON File ConfigVisualPanel1.invalidConfigJson=Invalid config JSON: ConfigVisualPanel1.messageLabel.noExternalDriveFound=No drive found ConfigVisualPanel1.selectConfigurationFile=Select location +ConfigVisualPanel1.unknown=Unknown ConfigVisualPanel2.cancel=Cancel ConfigVisualPanel2.deleteRuleSet=Delete rule ConfigVisualPanel2.deleteRuleSetConfirmation=Delete rule confirmation @@ -174,7 +180,7 @@ LogicalImagerConfigDeserializer.unsupportedKeyException=Unsupported key: {0} NewRulePanel.chooseLabel.text=Choose the type of rule ConfigVisualPanel1.configureDriveRadioButton.text_1=Configure selected external drive: ConfigVisualPanel1.configureFolderRadioButton.text_1=Configure in a folder: -ConfigVisualPanel1.descriptionTextArea.text=Select a location for the Logical Imager. This location will contain the imaging program and a configuration file. If that location already contains a configuration file, it will be loaded to edit. Imaging results will be saved to this location, so ensure it has enough free space. +ConfigVisualPanel1.descriptionTextArea.text=Select a location for the Logical Imager. This location will contain the imaging program and a configuration file. If that location already contains a configuration file, it will be loaded to edit. Imaging results will be saved to this location, so ensure it has enough free space. Drives with FAT format are not supported. ConfigVisualPanel1.refreshButton.text=Refresh ConfigVisualPanel3.saveButton.text=Save ConfigVisualPanel3.configLabel.text=Logical Imager config file save status: @@ -191,6 +197,7 @@ EditNonFullPathsRulePanel.fileNamesInfoLabel.text=File names are case insensitiv EditNonFullPathsRulePanel.extensionsInfoLabel.text=Extensions are case insensitive. ConfigVisualPanel2.promptBeforeExit.text=Prompt before exiting imager ConfigVisualPanel2.promptBeforeExit.actionCommand= +ConfigVisualPanel2.createVHDCheckBox.text=Create VHD NewRuleSetPanel.attributeRule.description=Search for files based on one or more attributes or metadata fields. NewRuleSetPanel.attributeRule.name=Attribute NewRuleSetPanel.fullPathRule.description=Search for files based on full exact match path. diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel1.java b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel1.java index 2765bb1856..f6336771bd 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel1.java +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel1.java @@ -29,7 +29,11 @@ import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.nio.file.FileStore; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.spi.FileSystemProvider; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; @@ -241,10 +245,31 @@ final class ConfigVisualPanel1 extends JPanel { firePropertyChange(UPDATE_UI_EVENT_NAME, false, true); // NON-NLS }//GEN-LAST:event_driveListMouseReleasedSelection + /* + * Return the Windows file system name of the drive + * @param drive File system drive, should be of the form "C:\" + * + */ + @Messages({"ConfigVisualPanel1.unknown=Unknown"}) + private String getFileSystemName(String drive) { + FileSystem fileSystem = FileSystems.getDefault(); + FileSystemProvider provider = fileSystem.provider(); + try { + FileStore fileStore = provider.getFileStore(Paths.get(drive)); + return fileStore.type(); + } catch (IOException ex) { + return Bundle.ConfigVisualPanel1_unknown(); + } + } + /** * Refresh the list of local drives on the current machine */ - @Messages({"ConfigVisualPanel1.messageLabel.noExternalDriveFound=No drive found"}) + @NbBundle.Messages({ + "ConfigVisualPanel1.messageLabel.noExternalDriveFound=No drive found", + "# {0} - root", "# {1} - description", "# {2} - size with unit", "# {3} - file system", + "ConfigVisualPanel1.driveListItem={0} ({1}) ({2}) - File system: {3}" + }) private void refreshDriveList() { List listData = new ArrayList<>(); File[] roots = File.listRoots(); @@ -257,7 +282,8 @@ final class ConfigVisualPanel1 extends JPanel { String description = FileSystemView.getFileSystemView().getSystemTypeDescription(root); long spaceInBytes = root.getTotalSpace(); String sizeWithUnit = DriveListUtils.humanReadableByteCount(spaceInBytes, false); - listData.add(root + " (" + description + ") (" + sizeWithUnit + ")"); + String fileSystem = getFileSystemName(root.toString()); + listData.add(Bundle.ConfigVisualPanel1_driveListItem(root, description, sizeWithUnit, fileSystem)); if (firstRemovableDrive == -1) { try { FileStore fileStore = Files.getFileStore(root.toPath()); @@ -266,7 +292,7 @@ final class ConfigVisualPanel1 extends JPanel { } } catch (IOException ignored) { //unable to get this removable drive for default selection will try and select next removable drive by default - logger.log(Level.INFO, "Unable to select first removable drive found", ignored); + logger.log(Level.INFO, String.format("Unable to select first removable drive found %s", root.toString())); // NON-NLS } } i++; @@ -431,8 +457,7 @@ final class ConfigVisualPanel1 extends JPanel { return UPDATE_UI_EVENT_NAME; } - void setConfigFilename(String filename - ) { + void setConfigFilename(String filename) { configFileTextField.setText(filename); } @@ -442,9 +467,11 @@ final class ConfigVisualPanel1 extends JPanel { * @return true if panel has valid settings selected, false otherwise */ boolean isPanelValid() { - return !StringUtils.isBlank(getConfigPath()) && ((configureDriveRadioButton.isSelected() && !StringUtils.isBlank(driveList.getSelectedValue())) - || (configureFolderRadioButton.isSelected() && (!configFileTextField.getText().isEmpty()))); - + return !StringUtils.isBlank(getConfigPath()) + && !(getFileSystemName(getConfigPath().substring(0, 3)).equals("FAT") // NON-NLS + || getFileSystemName(getConfigPath().substring(0, 3)).equals("FAT32")) // NON-NLS + && ((configureDriveRadioButton.isSelected() && !StringUtils.isBlank(driveList.getSelectedValue())) + || (configureFolderRadioButton.isSelected() && (!configFileTextField.getText().isEmpty()))); } /** diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel2.form b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel2.form index b5725173df..5628bdb709 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel2.form +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel2.form @@ -103,6 +103,7 @@ + @@ -193,7 +194,8 @@ - + + @@ -582,5 +584,15 @@ + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel2.java b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel2.java index 65fe93fcd3..cadd5e7cca 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel2.java +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/ConfigVisualPanel2.java @@ -111,6 +111,7 @@ final class ConfigVisualPanel2 extends JPanel { maxSizeLabel = new javax.swing.JLabel(); maxSizeTextField = new javax.swing.JFormattedTextField(); promptBeforeExit = new javax.swing.JCheckBox(); + createVHDCheckBox = new javax.swing.JCheckBox(); org.openide.awt.Mnemonics.setLocalizedText(modifiedDateLabel, org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.modifiedDateLabel.text")); // NOI18N @@ -264,6 +265,13 @@ final class ConfigVisualPanel2 extends JPanel { } }); + org.openide.awt.Mnemonics.setLocalizedText(createVHDCheckBox, org.openide.util.NbBundle.getMessage(ConfigVisualPanel2.class, "ConfigVisualPanel2.createVHDCheckBox.text")); // NOI18N + createVHDCheckBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + createVHDCheckBoxActionPerformed(evt); + } + }); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( @@ -338,7 +346,8 @@ final class ConfigVisualPanel2 extends JPanel { .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(flagEncryptionProgramsCheckBox) .addComponent(finalizeImageWriter) - .addComponent(promptBeforeExit)) + .addComponent(promptBeforeExit) + .addComponent(createVHDCheckBox)) .addGap(0, 0, Short.MAX_VALUE)) .addComponent(jSeparator1))))) ); @@ -412,7 +421,8 @@ final class ConfigVisualPanel2 extends JPanel { .addComponent(finalizeImageWriter) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(promptBeforeExit) - .addGap(21, 21, 21)))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(createVHDCheckBox)))) ); }// //GEN-END:initComponents @@ -546,6 +556,10 @@ final class ConfigVisualPanel2 extends JPanel { config.setPromptBeforeExit(promptBeforeExit.isSelected()); }//GEN-LAST:event_promptBeforeExitActionPerformed + private void createVHDCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_createVHDCheckBoxActionPerformed + config.setCreateVHD(createVHDCheckBox.isSelected()); + }//GEN-LAST:event_createVHDCheckBoxActionPerformed + /** * Set the whether the a rule for detecting encryption programs will be * added to the rules in this config @@ -588,6 +602,7 @@ final class ConfigVisualPanel2 extends JPanel { // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JTextField configFileTextField; + private javax.swing.JCheckBox createVHDCheckBox; private javax.swing.JLabel daysIncludedLabel; private javax.swing.JButton deleteRuleButton; private javax.swing.JTextField descriptionEditTextField; @@ -638,13 +653,14 @@ final class ConfigVisualPanel2 extends JPanel { * Update the panel to reflect the rules in the current config * * @param configFilePath path of the config file being modified - * @param config contents of the config file being modifed + * @param config contents of the config file being modified * @param rowSelectionkey the name of the rule to select by default */ private void updatePanel(String configFilePath, LogicalImagerConfig config, String rowSelectionkey) { configFileTextField.setText(configFilePath); finalizeImageWriter.setSelected(config.isFinalizeImageWriter()); promptBeforeExit.setSelected(config.isPromptBeforeExit()); + createVHDCheckBox.setSelected(config.isCreateVHD()); LogicalImagerRuleSet ruleSet = getRuleSetFromCurrentConfig(); flagEncryptionProgramsCheckBox.setSelected(ruleSet.find(EncryptionProgramsRule.getName()) != null); RulesTableModel rulesTableModel = new RulesTableModel(); diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/LogicalImagerConfig.java b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/LogicalImagerConfig.java index 9d1c175de3..d896c15001 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/LogicalImagerConfig.java +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/LogicalImagerConfig.java @@ -42,6 +42,10 @@ class LogicalImagerConfig { @Expose(serialize = true) private boolean promptBeforeExit; + @SerializedName("create-VHD") + @Expose(serialize = true) + private boolean createVHD; + @SerializedName("rule-sets") @Expose(serialize = true) private List ruleSets; @@ -50,6 +54,7 @@ class LogicalImagerConfig { this.version = CURRENT_VERSION; this.finalizeImageWriter = false; this.promptBeforeExit = true; + this.createVHD = false; this.ruleSets = new ArrayList<>(); } @@ -60,6 +65,7 @@ class LogicalImagerConfig { this.version = CURRENT_VERSION; this.finalizeImageWriter = finalizeImageWriter; this.promptBeforeExit = true; + this.createVHD = false; this.ruleSets = ruleSets; } @@ -71,6 +77,7 @@ class LogicalImagerConfig { this.version = version; this.finalizeImageWriter = finalizeImageWriter; this.promptBeforeExit = true; + this.createVHD = false; this.ruleSets = ruleSets; } @@ -78,11 +85,13 @@ class LogicalImagerConfig { String version, boolean finalizeImageWriter, boolean promptBeforeExit, + boolean createVHD, List ruleSets ) { this.version = version; this.finalizeImageWriter = finalizeImageWriter; this.promptBeforeExit = promptBeforeExit; + this.createVHD = createVHD; this.ruleSets = ruleSets; } @@ -114,6 +123,14 @@ class LogicalImagerConfig { this.promptBeforeExit = promptBeforeExit; } + boolean isCreateVHD() { + return createVHD; + } + + void setCreateVHD(boolean createVHD) { + this.createVHD = createVHD; + } + List getRuleSets() { return ruleSets; } diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/LogicalImagerConfigDeserializer.java b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/LogicalImagerConfigDeserializer.java index dd433b68cf..d14fa31ccf 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/LogicalImagerConfigDeserializer.java +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/configuration/LogicalImagerConfigDeserializer.java @@ -46,6 +46,7 @@ class LogicalImagerConfigDeserializer implements JsonDeserializer parseRules(JsonArray asJsonArray) { diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/AddLogicalImageTask.java b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/AddLogicalImageTask.java index f259b6bfb9..7b3fe30dd8 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/AddLogicalImageTask.java +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/AddLogicalImageTask.java @@ -32,19 +32,21 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; +import org.apache.commons.io.FileUtils; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; -import org.sleuthkit.autopsy.casemodule.services.Blackboard; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.ingest.IngestServices; -import org.sleuthkit.autopsy.ingest.ModuleDataEvent; +import org.sleuthkit.autopsy.datamodel.utils.LocalFileImporter; import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Blackboard; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.LocalFilesDataSource; +import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; /** @@ -52,33 +54,42 @@ import org.sleuthkit.datamodel.TskCoreException; * SearchResults.txt and users.txt files to report - add an image data source to the * case database. */ -final class AddLogicalImageTask extends AddMultipleImageTask { +final class AddLogicalImageTask implements Runnable { private final static Logger LOGGER = Logger.getLogger(AddLogicalImageTask.class.getName()); private final static String SEARCH_RESULTS_TXT = "SearchResults.txt"; //NON-NLS private final static String USERS_TXT = "users.txt"; //NON-NLS private final static String MODULE_NAME = "Logical Imager"; //NON-NLS + private final static String ROOT_STR = "root"; // NON-NLS + private final static String VHD_EXTENSION = ".vhd"; // NON-NLS + private final String deviceId; + private final String timeZone; private final File src; private final File dest; private final DataSourceProcessorCallback callback; private final DataSourceProcessorProgressMonitor progressMonitor; private final Blackboard blackboard; private final Case currentCase; + private Map> imagePaths; + private Map imagePathToObjIdMap; + private long totalFiles; + + private volatile boolean cancelled; AddLogicalImageTask(String deviceId, - List imagePaths, String timeZone, File src, File dest, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback ) throws NoCurrentCaseException { - super(deviceId, imagePaths, timeZone, progressMonitor, callback); + this.deviceId = deviceId; + this.timeZone = timeZone; this.src = src; this.dest = dest; this.progressMonitor = progressMonitor; this.callback = callback; this.currentCase = Case.getCurrentCase(); - this.blackboard = this.currentCase.getServices().getBlackboard(); + this.blackboard = this.currentCase.getServices().getArtifactsBlackboard(); } /** @@ -91,16 +102,33 @@ final class AddLogicalImageTask extends AddMultipleImageTask { "# {0} - src", "# {1} - dest", "AddLogicalImageTask.failedToCopyDirectory=Failed to copy directory {0} to {1}", "# {0} - file", "AddLogicalImageTask.addingToReport=Adding {0} to report", "# {0} - file", "AddLogicalImageTask.doneAddingToReport=Done adding {0} to report", + "AddLogicalImageTask.ingestionCancelled=Ingestion cancelled", + "# {0} - file", "AddLogicalImageTask.failToGetCanonicalPath=Fail to get canonical path for {0}", + "# {0} - sparseImageDirectory", "AddLogicalImageTask.directoryDoesNotContainSparseImage=Directory {0} does not contain any images", + "AddLogicalImageTask.noCurrentCase=No current case", "AddLogicalImageTask.addingInterestingFiles=Adding search results as interesting files", "AddLogicalImageTask.doneAddingInterestingFiles=Done adding search results as interesting files", "# {0} - SearchResults.txt", "# {1} - directory", "AddLogicalImageTask.cannotFindFiles=Cannot find {0} in {1}", - "# {0} - reason", "AddLogicalImageTask.failedToAddInterestingFiles=Failed to add interesting files: {0}" + "# {0} - reason", "AddLogicalImageTask.failedToAddInterestingFiles=Failed to add interesting files: {0}", + "AddLogicalImageTask.addingExtractedFiles=Adding extracted files", + "AddLogicalImageTask.doneAddingExtractedFiles=Done adding extracted files", + "# {0} - reason", "AddLogicalImageTask.failedToGetTotalFilesCount=Failed to get total files count: {0}" }) @Override public void run() { List errorList = new ArrayList<>(); List emptyDataSources = new ArrayList<>(); + try { + progressMonitor.setProgressText(Bundle.AddLogicalImageTask_copyingImageFromTo(src.toString(), dest.toString())); + FileUtils.copyDirectory(src, dest); + progressMonitor.setProgressText(Bundle.AddLogicalImageTask_doneCopying()); + } catch (IOException ex) { + // Copy directory failed + String msg = Bundle.AddLogicalImageTask_failedToCopyDirectory(src.toString(), dest.toString()); + errorList.add(msg); + } + // Add the SearchResults.txt and users.txt to the case report String resultsFilename; if (Paths.get(dest.toString(), SEARCH_RESULTS_TXT).toFile().exists()) { @@ -108,8 +136,13 @@ final class AddLogicalImageTask extends AddMultipleImageTask { } else { errorList.add(Bundle.AddLogicalImageTask_cannotFindFiles(SEARCH_RESULTS_TXT, dest.toString())); callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources); - return; + return; } + + if (cancelled) { + return; + } + progressMonitor.setProgressText(Bundle.AddLogicalImageTask_addingToReport(resultsFilename)); String status = addReport(Paths.get(dest.toString(), resultsFilename), resultsFilename + " " + src.getName()); if (status != null) { @@ -128,17 +161,84 @@ final class AddLogicalImageTask extends AddMultipleImageTask { } progressMonitor.setProgressText(Bundle.AddLogicalImageTask_doneAddingToReport(USERS_TXT)); - super.run(); - if (super.getResult() == DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS) { - callback.done(super.getResult(), super.getErrorMessages(), super.getNewDataSources()); + // Get all VHD files in the dest directory + List imagePaths = new ArrayList<>(); + for (File f : dest.listFiles()) { + if (f.getName().endsWith(VHD_EXTENSION)) { + try { + imagePaths.add(f.getCanonicalPath()); + } catch (IOException ioe) { + String msg = Bundle.AddLogicalImageTask_failToGetCanonicalPath(f.getName()); + errorList.add(msg); + callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources); + return; + } + } + } + + AddMultipleImageTask addMultipleImageTask = null; + List newDataSources = new ArrayList<>(); + boolean createVHD; + + Path resultsPath = Paths.get(dest.toString(), resultsFilename); + try { + totalFiles = Files.lines(resultsPath).count() - 1; // skip the header line + } catch (IOException ex) { + errorList.add(Bundle.AddLogicalImageTask_failedToGetTotalFilesCount(ex.getMessage())); + callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources); return; } - + + if (imagePaths.isEmpty()) { + createVHD = false; + // No VHD in src directory, try ingest the root directory using Logical File Set + File root = Paths.get(dest.toString(), ROOT_STR).toFile(); + if (root.exists() && root.isDirectory()) { + imagePaths.add(root.getAbsolutePath()); + } else { + String msg = Bundle.AddLogicalImageTask_directoryDoesNotContainSparseImage(dest); + errorList.add(msg); + callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources); + return; + } + + try { + progressMonitor.setProgressText(Bundle.AddLogicalImageTask_addingExtractedFiles()); + addExtractedFiles(dest, resultsPath, newDataSources); + progressMonitor.setProgressText(Bundle.AddLogicalImageTask_doneAddingExtractedFiles()); + } catch (IOException | TskCoreException ex) { + errorList.add(ex.getMessage()); + LOGGER.log(Level.SEVERE, String.format("Failed to add datasource: %s", ex.getMessage()), ex); // NON-NLS + callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources); + return; + } + } else { + createVHD = true; + // ingest the VHDs + try { + addMultipleImageTask = new AddMultipleImageTask(deviceId, imagePaths, timeZone , progressMonitor, callback); + addMultipleImageTask.run(); + if (addMultipleImageTask.getResult() == DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS) { + callback.done(addMultipleImageTask.getResult(), addMultipleImageTask.getErrorMessages(), addMultipleImageTask.getNewDataSources()); + return; + } + } catch (NoCurrentCaseException ex) { + String msg = Bundle.AddLogicalImageTask_noCurrentCase(); + errorList.add(msg); + callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources); + return; + } + } + try { progressMonitor.setProgressText(Bundle.AddLogicalImageTask_addingInterestingFiles()); - addInterestingFiles(dest, Paths.get(dest.toString(), resultsFilename)); + addInterestingFiles(Paths.get(dest.toString(), resultsFilename), createVHD); progressMonitor.setProgressText(Bundle.AddLogicalImageTask_doneAddingInterestingFiles()); - callback.done(super.getResult(), super.getErrorMessages(), super.getNewDataSources()); + if (addMultipleImageTask != null) { + callback.done(addMultipleImageTask.getResult(), addMultipleImageTask.getErrorMessages(), addMultipleImageTask.getNewDataSources()); + } else { + callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.NO_ERRORS, errorList, newDataSources); + } } catch (IOException | TskCoreException ex) { errorList.add(Bundle.AddLogicalImageTask_failedToAddInterestingFiles(ex.getMessage())); LOGGER.log(Level.SEVERE, "Failed to add interesting files", ex); // NON-NLS @@ -172,6 +272,15 @@ final class AddLogicalImageTask extends AddMultipleImageTask { } } + /** + * Attempts to cancel the processing of the input image files. May result in + * partial processing of the input. + */ + void cancelTask() { + LOGGER.log(Level.WARNING, "AddLogicalImageTask cancelled, processing may be incomplete"); // NON-NLS + cancelled = true; + } + private Map imagePathsToDataSourceObjId(Map> imagePaths) { Map imagePathToObjIdMap = new HashMap<>(); for (Map.Entry> entry : imagePaths.entrySet()) { @@ -183,34 +292,31 @@ final class AddLogicalImageTask extends AddMultipleImageTask { } return imagePathToObjIdMap; } - + @Messages({ "# {0} - line number", "# {1} - fields length", "# {2} - expected length", "AddLogicalImageTask.notEnoughFields=File does not contain enough fields at line {0}, got {1}, expecting {2}", - "# {0} - target image path", "AddLogicalImageTask.cannotFindDataSourceObjId=Cannot find obj_id in tsk_image_names for {0}" + "# {0} - target image path", "AddLogicalImageTask.cannotFindDataSourceObjId=Cannot find obj_id in tsk_image_names for {0}", + "# {0} - file number", "# {1} - total files", "AddLogicalImageTask.addingInterestingFile=Adding interesting file {0} of {1}" }) - private void addInterestingFiles(File src, Path resultsPath) throws IOException, TskCoreException { - Map> imagePaths = currentCase.getSleuthkitCase().getImagePaths(); - Map imagePathToObjIdMap = imagePathsToDataSourceObjId(imagePaths); - long totalFiles = Files.lines(resultsPath).count() - 1; // skip the header line - + private void addInterestingFiles(Path resultsPath, boolean createVHD) throws IOException, TskCoreException { + imagePaths = currentCase.getSleuthkitCase().getImagePaths(); + imagePathToObjIdMap = imagePathsToDataSourceObjId(imagePaths); + try (BufferedReader br = new BufferedReader(new InputStreamReader( new FileInputStream(resultsPath.toFile()), "UTF8"))) { // NON-NLS + List artifacts = new ArrayList<>(); String line; br.readLine(); // skip the header line int lineNumber = 2; while ((line = br.readLine()) != null) { - String[] fields = line.split("\t", -1); // NON-NLS -// if (fields.length != 9) { -// throw new IOException(Bundle.AddLogicalImageTask_notEnoughFields(lineNumber, fields.length, 9)); -// } - String vhdFilename = fields[0]; - - String targetImagePath = Paths.get(src.toString(), vhdFilename).toString(); - Long dataSourceObjId = imagePathToObjIdMap.get(targetImagePath); - if (dataSourceObjId == null) { - throw new TskCoreException(Bundle.AddLogicalImageTask_cannotFindDataSourceObjId(targetImagePath)); + if (cancelled) { + return; } - + String[] fields = line.split("\t", -1); // NON-NLS + if (fields.length != 14) { + throw new IOException(Bundle.AddLogicalImageTask_notEnoughFields(lineNumber, fields.length, 14)); + } + String vhdFilename = fields[0]; // String fileSystemOffsetStr = fields[1]; String fileMetaAddressStr = fields[2]; // String extractStatusStr = fields[3]; @@ -218,40 +324,141 @@ final class AddLogicalImageTask extends AddMultipleImageTask { String ruleName = fields[5]; // String description = fields[6]; String filename = fields[7]; -// String parentPath = fields[8]; - + String parentPath = fields[8]; + if (lineNumber % 100 == 0) { - progressMonitor.setProgressText(String.format("Adding interesting file %d of %d", lineNumber, totalFiles)); + progressMonitor.setProgressText(Bundle.AddLogicalImageTask_addingInterestingFile(lineNumber, totalFiles)); } - String query = String.format("data_source_obj_id = '%s' AND meta_addr = '%s' AND name = '%s'", // NON-NLS - dataSourceObjId.toString(), fileMetaAddressStr, filename); + String query = makeQuery(createVHD, vhdFilename, fileMetaAddressStr, parentPath, filename); + + // TODO - findAllFilesWhere should SQL-escape the query List matchedFiles = Case.getCurrentCase().getSleuthkitCase().findAllFilesWhere(query); for (AbstractFile file : matchedFiles) { - addInterestingFile(file, ruleSetName, ruleName); + addInterestingFileToArtifacts(file, ruleSetName, ruleName, artifacts); } - lineNumber++; + lineNumber++; + } // end reading file + + try { + // index the artifact for keyword search + blackboard.postArtifacts(artifacts, MODULE_NAME); + } catch (Blackboard.BlackboardException ex) { + LOGGER.log(Level.SEVERE, "Unable to post artifacts to blackboard", ex); //NON-NLS } } - IngestServices.getInstance().fireModuleDataEvent(new ModuleDataEvent(MODULE_NAME, - BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT)); } - private void addInterestingFile(AbstractFile file, String ruleSetName, String ruleName) throws TskCoreException { + private void addInterestingFileToArtifacts(AbstractFile file, String ruleSetName, String ruleName, List artifacts) throws TskCoreException { Collection attributes = new ArrayList<>(); BlackboardAttribute setNameAttribute = new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME, MODULE_NAME, ruleSetName); attributes.add(setNameAttribute); BlackboardAttribute ruleNameAttribute = new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CATEGORY, MODULE_NAME, ruleName); attributes.add(ruleNameAttribute); - org.sleuthkit.datamodel.Blackboard tskBlackboard = Case.getCurrentCase().getSleuthkitCase().getBlackboard(); - if (!tskBlackboard.artifactExists(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT, attributes)) { - BlackboardArtifact artifact = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT); + if (!blackboard.artifactExists(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT, attributes)) { + BlackboardArtifact artifact = this.currentCase.getSleuthkitCase().newBlackboardArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT, file.getId()); artifact.addAttributes(attributes); + artifacts.add(artifact); + } + } + + @Messages({ + "# {0} - file number", "# {1} - total files", "AddLogicalImageTask.addingExtractedFile=Adding extracted file {0} of {1}", + "AddLogicalImageTask.errorAddingExtractedFiles=Error adding extracted files" + }) + private void addExtractedFiles(File src, Path resultsPath, List newDataSources) throws TskCoreException, IOException { + SleuthkitCase skCase = Case.getCurrentCase().getSleuthkitCase(); + SleuthkitCase.CaseDbTransaction trans = null; + + try { + trans = skCase.beginTransaction(); + LocalFilesDataSource localFilesDataSource = skCase.addLocalFilesDataSource(deviceId, this.src.getName(), timeZone, trans); + LocalFileImporter fileImporter = new LocalFileImporter(skCase, trans); + + try (BufferedReader br = new BufferedReader(new InputStreamReader( + new FileInputStream(resultsPath.toFile()), "UTF8"))) { // NON-NLS + String line; + br.readLine(); // skip the header line + int lineNumber = 2; + while ((line = br.readLine()) != null) { + if (cancelled) { + rollbackTransaction(trans); + return; + } + String[] fields = line.split("\t", -1); // NON-NLS + if (fields.length != 14) { + rollbackTransaction(trans); + throw new IOException(Bundle.AddLogicalImageTask_notEnoughFields(lineNumber, fields.length, 14)); + } + String vhdFilename = fields[0]; +// String fileSystemOffsetStr = fields[1]; +// String fileMetaAddressStr = fields[2]; +// String extractStatusStr = fields[3]; +// String ruleSetName = fields[4]; +// String ruleName = fields[5]; +// String description = fields[6]; + String filename = fields[7]; + String parentPath = fields[8]; + String extractedFilePath = fields[9]; + String crtime = fields[10]; + String mtime = fields[11]; + String atime = fields[12]; + String ctime = fields[13]; + parentPath = ROOT_STR + "/" + vhdFilename + "/" + parentPath; + + if (lineNumber % 100 == 0) { + progressMonitor.setProgressText(Bundle.AddLogicalImageTask_addingExtractedFile(lineNumber, totalFiles)); + } + + //addLocalFile here + fileImporter.addLocalFile( + Paths.get(src.toString(), extractedFilePath).toFile(), + filename, + parentPath, + Long.parseLong(ctime), + Long.parseLong(crtime), + Long.parseLong(atime), + Long.parseLong(mtime), + localFilesDataSource); + + lineNumber++; + } // end reading file + } + trans.commit(); + newDataSources.add(localFilesDataSource); + + } catch (NumberFormatException | TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Error adding extracted files", ex); // NON-NLS + rollbackTransaction(trans); + throw new TskCoreException(Bundle.AddLogicalImageTask_errorAddingExtractedFiles(), ex); + } + } + + private void rollbackTransaction(SleuthkitCase.CaseDbTransaction trans) throws TskCoreException { + if (null != trans) { try { - // index the artifact for keyword search - blackboard.indexArtifact(artifact); - } catch (Blackboard.BlackboardException ex) { - LOGGER.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS + trans.rollback(); + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, String.format("Failed to rollback transaction: %s", ex.getMessage()), ex); // NON-NLS } } } + + String makeQuery(boolean createVHD, String vhdFilename, String fileMetaAddressStr, String parentPath, String filename) throws TskCoreException { + String query; + if (createVHD) { + String targetImagePath = Paths.get(dest.toString(), vhdFilename).toString(); + Long dataSourceObjId = imagePathToObjIdMap.get(targetImagePath); + if (dataSourceObjId == null) { + throw new TskCoreException(Bundle.AddLogicalImageTask_cannotFindDataSourceObjId(targetImagePath)); + } + query = String.format("data_source_obj_id = '%s' AND meta_addr = '%s' AND name = '%s'", // NON-NLS + dataSourceObjId.toString(), fileMetaAddressStr, filename.replace("'", "''")); + } else { + String newParentPath = "/" + ROOT_STR + "/" + vhdFilename + "/" + parentPath; + query = String.format("name = '%s' AND parent_path = '%s'", // NON-NLS + filename.replace("'", "''"), newParentPath.replace("'", "''")); + } + return query; + } + } diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/Bundle.properties-MERGED index dfcde750b9..fd08147b08 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/Bundle.properties-MERGED @@ -2,6 +2,7 @@ # To change this template file, choose Tools | Templates # and open the template in the editor. +AddLogicalImageTask.addingExtractedFiles=Adding extracted files AddLogicalImageTask.addingInterestingFiles=Adding search results as interesting files # {0} - file AddLogicalImageTask.addingToReport=Adding {0} to report @@ -13,6 +14,9 @@ AddLogicalImageTask.cannotFindFiles=Cannot find {0} in {1} # {0} - src # {1} - dest AddLogicalImageTask.copyingImageFromTo=Copying image from {0} to {1} +# {0} - sparseImageDirectory +AddLogicalImageTask.directoryDoesNotContainSparseImage=Directory {0} does not contain any images +AddLogicalImageTask.doneAddingExtractedFiles=Done adding extracted files AddLogicalImageTask.doneAddingInterestingFiles=Done adding search results as interesting files # {0} - file AddLogicalImageTask.doneAddingToReport=Done adding {0} to report @@ -25,6 +29,10 @@ AddLogicalImageTask.failedToAddReport=Failed to add report {0}. Reason= {1} # {0} - src # {1} - dest AddLogicalImageTask.failedToCopyDirectory=Failed to copy directory {0} to {1} +# {0} - file +AddLogicalImageTask.failToGetCanonicalPath=Fail to get canonical path for {0} +AddLogicalImageTask.ingestionCancelled=Ingestion cancelled +AddLogicalImageTask.noCurrentCase=No current case # {0} - line number # {1} - fields length # {2} - expected length @@ -54,8 +62,6 @@ LogicalImagerDSProcessor.dataSourceType=Autopsy Logical Imager Results LogicalImagerDSProcessor.directoryAlreadyExists=Directory {0} already exists # {0} - directory LogicalImagerDSProcessor.failToCreateDirectory=Failed to create directory {0} -# {0} - file -LogicalImagerDSProcessor.failToGetCanonicalPath=Fail to get canonical path for {0} # {0} - imageDirPath LogicalImagerDSProcessor.imageDirPathNotFound={0} not found.\nUSB drive has been ejected. LogicalImagerDSProcessor.noCurrentCase=No current case diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/LogicalImagerDSProcessor.java b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/LogicalImagerDSProcessor.java index be45bcad97..058c0fa2ee 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/LogicalImagerDSProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/LogicalImagerDSProcessor.java @@ -19,7 +19,6 @@ package org.sleuthkit.autopsy.logicalimager.dsp; import java.io.File; -import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; @@ -131,8 +130,8 @@ public final class LogicalImagerDSProcessor implements DataSourceProcessor { "# {0} - imageDirPath", "LogicalImagerDSProcessor.imageDirPathNotFound={0} not found.\nUSB drive has been ejected.", "# {0} - directory", "LogicalImagerDSProcessor.failToCreateDirectory=Failed to create directory {0}", "# {0} - directory", "LogicalImagerDSProcessor.directoryAlreadyExists=Directory {0} already exists", - "# {0} - file", "LogicalImagerDSProcessor.failToGetCanonicalPath=Fail to get canonical path for {0}", - "LogicalImagerDSProcessor.noCurrentCase=No current case",}) + "LogicalImagerDSProcessor.noCurrentCase=No current case", + }) @Override public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { configPanel.storeSettings(); @@ -171,37 +170,10 @@ public final class LogicalImagerDSProcessor implements DataSourceProcessor { } File src = imageDirPath.toFile(); - try { - progressMonitor.setProgressText(Bundle.AddLogicalImageTask_copyingImageFromTo(src.toString(), dest.toString())); - FileUtils.copyDirectory(src, dest); - progressMonitor.setProgressText(Bundle.AddLogicalImageTask_doneCopying()); - } catch (IOException ex) { - // Copy directory failed - String msg = Bundle.AddLogicalImageTask_failedToCopyDirectory(src.toString(), dest.toString()); - errorList.add(msg); - callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources); - return; - } - - // Get all VHD files in the src directory - List imagePaths = new ArrayList<>(); - for (File f : dest.listFiles()) { - if (f.getName().endsWith(".vhd")) { - try { - imagePaths.add(f.getCanonicalPath()); - } catch (IOException ex) { - String msg = Bundle.LogicalImagerDSProcessor_failToGetCanonicalPath(f.getName()); - errorList.add(msg); - callback.done(DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS, errorList, emptyDataSources); - return; - } - } - } try { String deviceId = UUID.randomUUID().toString(); String timeZone = Calendar.getInstance().getTimeZone().getID(); - run(deviceId, imagePaths, - timeZone, src, dest, + run(deviceId, timeZone, src, dest, progressMonitor, callback); } catch (NoCurrentCaseException ex) { String msg = Bundle.LogicalImagerDSProcessor_noCurrentCase(); @@ -220,7 +192,6 @@ public final class LogicalImagerDSProcessor implements DataSourceProcessor { * @param deviceId An ASCII-printable identifier for the device * associated with the data source that is intended * to be unique across multiple cases (e.g., a UUID). - * @param imagePaths Paths to the image files. * @param timeZone The time zone to use when processing dates and * times for the image, obtained from * java.util.TimeZone.getID. @@ -230,11 +201,11 @@ public final class LogicalImagerDSProcessor implements DataSourceProcessor { * processing. * @param callback Callback to call when processing is done. */ - private void run(String deviceId, List imagePaths, String timeZone, + private void run(String deviceId, String timeZone, File src, File dest, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback ) throws NoCurrentCaseException { - addLogicalImageTask = new AddLogicalImageTask(deviceId, imagePaths, timeZone, src, dest, + addLogicalImageTask = new AddLogicalImageTask(deviceId, timeZone, src, dest, progressMonitor, callback); new Thread(addLogicalImageTask).start(); } diff --git a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/LogicalImagerPanel.java b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/LogicalImagerPanel.java index 06722722d6..1cdf09286b 100644 --- a/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/LogicalImagerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/logicalimager/dsp/LogicalImagerPanel.java @@ -333,9 +333,19 @@ final class LogicalImagerPanel extends JPanel implements DocumentListener { } }); if (vhdFiles.length == 0) { - setErrorMessage(Bundle.LogicalImagerPanel_messageLabel_directoryDoesNotContainSparseImage(path)); - firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), true, false); - return; + // No VHD files, try directories for individual files + String[] directories = dir.list(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return Paths.get(dir.toString(), name).toFile().isDirectory(); + } + }); + if (directories.length == 0) { + // No directories, bail + setErrorMessage(Bundle.LogicalImagerPanel_messageLabel_directoryDoesNotContainSparseImage(path)); + firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), true, false); + return; + } } manualImageDirPath = Paths.get(path); setNormalMessage(path); @@ -360,11 +370,11 @@ final class LogicalImagerPanel extends JPanel implements DocumentListener { } } - private boolean dirHasVhdFiles(File dir) { - File[] fList = dir.listFiles(new FilenameFilter() { + private boolean dirHasImagerResult(File dir) { + String[] fList = dir.list(new FilenameFilter() { @Override public boolean accept(File dir, String name) { - return name.endsWith(".vhd"); + return name.endsWith(".vhd") || Paths.get(dir.toString(), name).toFile().isDirectory(); } }); return (fList != null && fList.length != 0); @@ -382,9 +392,9 @@ final class LogicalImagerPanel extends JPanel implements DocumentListener { if (fList != null) { imageTableModel = new ImageTableModel(); // Find all directories with name like Logical_Imager_HOSTNAME_yyyymmdd_HH_MM_SS - // and has vhd files in it + // and has Logical Imager result in it for (File file : fList) { - if (file.isDirectory() && dirHasVhdFiles(file)) { + if (file.isDirectory() && dirHasImagerResult(file)) { String dir = file.getName(); Matcher m = regex.matcher(dir); if (m.find()) {