From e1d41a5b3011416e938f793eace71109a1c7a5f4 Mon Sep 17 00:00:00 2001 From: 0xNF Date: Thu, 29 Nov 2012 13:12:16 -0500 Subject: [PATCH 01/17] Changes to the AddImage Panels -Displays an empty string instead of "Folder Information Unavailable" when processing something with no name -CurrentDirectoryFetcher is now static --- .../casemodule/AddImageVisualPanel2.java | 3 ++- .../casemodule/AddImageWizardPanel3.java | 17 +++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageVisualPanel2.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageVisualPanel2.java index 9f269f60a6..f42b418824 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageVisualPanel2.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageVisualPanel2.java @@ -53,6 +53,7 @@ final class AddImageVisualPanel2 extends JPanel { infoPanel.add(progressLabel); infoPanel.add(Box.createRigidArea(new Dimension(10, 10))); //spacer this.jScrollPane1.setBorder(null); + this.TextArea_CurrentDirectory.setBackground(this.getBackground()); } void resetInfoPanel() { @@ -108,7 +109,7 @@ final class AddImageVisualPanel2 extends JPanel { * @param dir the text to update with */ public void changeCurrentDir(String dir){ - this.TextArea_CurrentDirectory.setText(dir.trim().isEmpty() ? "Folder Information Unavailable" : dir); + this.TextArea_CurrentDirectory.setText(dir); } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardPanel3.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardPanel3.java index 237ed01945..6ffa8fd944 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardPanel3.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardPanel3.java @@ -205,11 +205,16 @@ class AddImageWizardPanel3 implements WizardDescriptor.Panel { * */ - private class CurrentDirectoryFetcher extends SwingWorker { + private static class CurrentDirectoryFetcher extends SwingWorker { AddImgTask task; + JProgressBar prog; + AddImageVisualPanel2 wiz; + AddImageProcess proc; - CurrentDirectoryFetcher(AddImgTask task){ - this.task = task; + CurrentDirectoryFetcher(JProgressBar prog, AddImageVisualPanel2 wiz, AddImageProcess proc){ + this.wiz = wiz; + this.proc = proc; + this.prog = prog; } /** @@ -218,13 +223,13 @@ class AddImageWizardPanel3 implements WizardDescriptor.Panel { @Override protected Integer doInBackground(){ try{ - while(task.progressBar.getValue() < 100 || task.progressBar.isIndeterminate()){ + while(prog.getValue() < 100 || prog.isIndeterminate()){ //TODO Rely on state variable in AddImgTask class EventQueue.invokeLater(new Runnable() { @Override public void run() { - wizPanel.getComponent().changeCurrentDir(process.currentDirectory()); + wiz.changeCurrentDir(proc.currentDirectory()); } @@ -304,7 +309,7 @@ class AddImageWizardPanel3 implements WizardDescriptor.Panel { process = currentCase.makeAddImageProcess(timeZone, true, noFatOrphans); - fetcher = new CurrentDirectoryFetcher(this); + fetcher = new CurrentDirectoryFetcher(this.progressBar, wizPanel.getComponent(), process); cancelledWhileRunning.enable(); try { wizPanel.setStateStarted(); From e5d6141dabc861c14d54259f0f4aabf18e034952 Mon Sep 17 00:00:00 2001 From: Brian Carrier Date: Thu, 29 Nov 2012 14:16:46 -0500 Subject: [PATCH 02/17] Added JRE_HOME to BUILDING.txt notes for packaging --- BUILDING.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/BUILDING.txt b/BUILDING.txt index 29bff518f9..987d4ac6b1 100644 --- a/BUILDING.txt +++ b/BUILDING.txt @@ -20,6 +20,10 @@ needed even if you have a 64-bit system). Note: Netbeans IDE is not required to build and run Autopsy, but it is a recommended IDE to use for development of Autopsy modules. +1d) (opetional) If you are going to package Autopsy, then you'll also +need to set JRE_HOME to the root JRE directory. + + 2) Get Sleuth Kit Setup 2a) Download and build the release version of Libewf2 (20120304 or From 486d6bf021f2e90bde7f5eff8bf51da822367685 Mon Sep 17 00:00:00 2001 From: adam-m Date: Thu, 29 Nov 2012 14:26:27 -0500 Subject: [PATCH 03/17] minor add image swingworker changes --- .../casemodule/AddImageWizardPanel3.java | 141 +++++++++--------- 1 file changed, 68 insertions(+), 73 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardPanel3.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardPanel3.java index 237ed01945..db3b232eb7 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardPanel3.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardPanel3.java @@ -41,8 +41,8 @@ import org.sleuthkit.datamodel.TskDataException; import org.sleuthkit.datamodel.TskException; /** - * The "Add Image" wizard panel3. Presents the - * options to finish/cancel image-add and run ingest. + * The "Add Image" wizard panel3. Presents the options to finish/cancel + * image-add and run ingest. */ class AddImageWizardPanel3 implements WizardDescriptor.Panel { @@ -53,10 +53,9 @@ class AddImageWizardPanel3 implements WizardDescriptor.Panel { * component from this class, just use getComponent(). */ private Component component = null; - private Image newImage = null; + private volatile Image newImage = null; private boolean ingested = false; private boolean readyToIngest = false; - // the paths of the image files to be added private String imgPath; // the time zone where the image is added @@ -67,13 +66,12 @@ class AddImageWizardPanel3 implements WizardDescriptor.Panel { private AddImageAction.CleanupTask cleanupImage; // initialized to null in readSettings() // flag to control the availiablity of next action private boolean imgAdded; // initalized to false in readSettings() - private CurrentDirectoryFetcher fetcher; private AddImageProcess process; private AddImageAction action; private AddImgTask addImageTask; private AddImageWizardPanel2 wizPanel; - + AddImageWizardPanel3(AddImageAction action, AddImageWizardPanel2 wizPanel) { this.action = action; this.wizPanel = wizPanel; @@ -85,7 +83,7 @@ class AddImageWizardPanel3 implements WizardDescriptor.Panel { * but never displayed, or not all panels are displayed, it is better to * create only those which really need to be visible. * - * @return component the UI component of this wizard panel + * @return component the UI component of this wizard panel */ @Override public Component getComponent() { @@ -99,7 +97,7 @@ class AddImageWizardPanel3 implements WizardDescriptor.Panel { * Help for this panel. When the panel is active, this is used as the help * for the wizard dialog. * - * @return HelpCtx.DEFAULT_HELP the help for this panel + * @return HelpCtx.DEFAULT_HELP the help for this panel */ @Override public HelpCtx getHelp() { @@ -113,7 +111,7 @@ class AddImageWizardPanel3 implements WizardDescriptor.Panel { * Tests whether the panel is finished. If the panel is valid, the "Finish" * button will be enabled. * - * @return true the finish button should be always enabled at this point + * @return true the finish button should be always enabled at this point */ @Override public boolean isValid() { @@ -129,7 +127,7 @@ class AddImageWizardPanel3 implements WizardDescriptor.Panel { /** * Adds a listener to changes of the panel's validity. * - * @param l the change listener to add + * @param l the change listener to add */ @Override public final void addChangeListener(ChangeListener l) { @@ -138,7 +136,7 @@ class AddImageWizardPanel3 implements WizardDescriptor.Panel { /** * Removes a listener to changes of the panel's validity. * - * @param l the change listener to move + * @param l the change listener to move */ @Override public final void removeChangeListener(ChangeListener l) { @@ -154,7 +152,7 @@ class AddImageWizardPanel3 implements WizardDescriptor.Panel { * buttons. This method can be called multiple times on one instance of * WizardDescriptor.Panel. * - * @param settings the setting to be read from + * @param settings the setting to be read from */ @Override public void readSettings(WizardDescriptor settings) { @@ -164,19 +162,19 @@ class AddImageWizardPanel3 implements WizardDescriptor.Panel { imgPath = (String) settings.getProperty(AddImageAction.IMGPATH_PROP); timeZone = settings.getProperty(AddImageAction.TIMEZONE_PROP).toString(); noFatOrphans = ((Boolean) settings.getProperty(AddImageAction.NOFATORPHANS_PROP)).booleanValue(); - + addImageTask = new AddImgTask(settings); addImageTask.execute(); } /** * Provides the wizard panel with the opportunity to update the settings - * with its current customized state. Rather than updating its settings - * with every change in the GUI, it should collect them, and then only save - * them when requested to by this method. This method can be called multiple + * with its current customized state. Rather than updating its settings with + * every change in the GUI, it should collect them, and then only save them + * when requested to by this method. This method can be called multiple * times on one instance of WizardDescriptor.Panel. * - * @param settings the setting to be stored to + * @param settings the setting to be stored to */ @Override public void storeSettings(WizardDescriptor settings) { @@ -186,10 +184,10 @@ class AddImageWizardPanel3 implements WizardDescriptor.Panel { readyToIngest = true; startIngest(); } - + /** - * Start ingest after verifying we have a new image, we are - * ready to ingest, and we haven't already ingested. + * Start ingest after verifying we have a new image, we are ready to ingest, + * and we haven't already ingested. */ private void startIngest() { if (newImage != null && readyToIngest && !ingested) { @@ -199,42 +197,38 @@ class AddImageWizardPanel3 implements WizardDescriptor.Panel { wizPanel.getComponent().appendProgressText(" Ingest started."); } } - - /** + + /** * Class for getting the currently processing directory. - * + * */ - - private class CurrentDirectoryFetcher extends SwingWorker { + private class CurrentDirectoryFetcher extends SwingWorker { + AddImgTask task; - - CurrentDirectoryFetcher(AddImgTask task){ + + CurrentDirectoryFetcher(AddImgTask task) { this.task = task; } - + /** * @return the currently processing directory */ @Override - protected Integer doInBackground(){ - try{ - while(task.progressBar.getValue() < 100 || task.progressBar.isIndeterminate()){ - - EventQueue.invokeLater(new Runnable() { + protected Integer doInBackground() { + try { + while (task.progressBar.getValue() < 100 || task.progressBar.isIndeterminate()) { + EventQueue.invokeLater(new Runnable() { @Override - public void run() { - wizPanel.getComponent().changeCurrentDir(process.currentDirectory()); + public void run() { + wizPanel.getComponent().changeCurrentDir(process.currentDirectory()); } - - }); - + Thread.sleep(2 * 1000); } return 1; - } - catch(InterruptedException ie){ + } catch (InterruptedException ie) { return -1; } } @@ -251,9 +245,9 @@ class AddImageWizardPanel3 implements WizardDescriptor.Panel { private boolean interrupted = false; private boolean hasCritError = false; private String errorString = null; - private long start; private WizardDescriptor settings; + private Logger logger = Logger.getLogger(AddImgTask.class.getName()); protected AddImgTask(WizardDescriptor settings) { this.progressBar = wizPanel.getComponent().getCrDbProgressBar(); @@ -321,15 +315,26 @@ class AddImageWizardPanel3 implements WizardDescriptor.Panel { } finally { // process is over, doesn't need to be dealt with if cancel happens cancelledWhileRunning.disable(); + + //enqueue what would be in done() to EDT thread + EventQueue.invokeLater(new Runnable() { + @Override + public void run() { + postProcessImage(); + } + }); + } - this.setProgress(100); + return 0; } /** * Commit the finished AddImageProcess, and cancel the CleanupTask that * would have reverted it. - * @param settings property set to get AddImageProcess and CleanupTask from + * + * @param settings property set to get AddImageProcess and CleanupTask + * from * @throws Exception if commit or adding the image to the case failed */ private void commitImage(WizardDescriptor settings) throws Exception { @@ -341,11 +346,9 @@ class AddImageWizardPanel3 implements WizardDescriptor.Panel { long imageId = 0; try { imageId = process.commit(); - } - catch (TskException e) { + } catch (TskException e) { logger.log(Level.WARNING, "Errors occured while committing the image", e); - } - finally { + } finally { //commit done, unlock db write in EWT thread //before doing anything else SleuthkitCase.dbWriteUnlock(); @@ -366,37 +369,28 @@ class AddImageWizardPanel3 implements WizardDescriptor.Panel { * * (called by EventDispatch Thread after doInBackground finishes) */ - @Override - protected void done() { + protected void postProcessImage() { progressBar.setIndeterminate(false); + setProgress(100); // attempt actions that might fail and force the process to stop - try { - //get() will block until doInBackground done and throw any exceptions - //that were thrown in the background task - //if process was stopped, stop should have been complete (otherwise, unsafe to revert() ) - get(); - logger.log(Level.INFO, "Adding image took " + (System.currentTimeMillis() - start) + " ms."); - } catch (InterruptedException e) { - } catch (ExecutionException e) { - } finally { - if (interrupted || hasCritError) { - logger.log(Level.INFO, "Handling errors or interruption that occured in add image process"); - revert(); - if (hasCritError) { - //core error - wizPanel.getComponent().setErrors(errorString, true); - } - return; - } else if (errorString != null) { - //data error (non-critical) - logger.log(Level.INFO, "Handling non-critical errors that occured in add image process"); - wizPanel.getComponent().setErrors(errorString, false); + if (interrupted || hasCritError) { + logger.log(Level.INFO, "Handling errors or interruption that occured in add image process"); + revert(); + if (hasCritError) { + //core error + wizPanel.getComponent().setErrors(errorString, true); } + return; + } else if (errorString != null) { + //data error (non-critical) + logger.log(Level.INFO, "Handling non-critical errors that occured in add image process"); + wizPanel.getComponent().setErrors(errorString, false); } + try { // When everything happens without an error: @@ -427,10 +421,11 @@ class AddImageWizardPanel3 implements WizardDescriptor.Panel { // Tell the panel we're done wizPanel.setStateFinished(); - + // Commit the image if (newImage != null) //already commited { + logger.log(Level.INFO, "Assuming image already committed, will not commit."); return; } @@ -445,7 +440,7 @@ class AddImageWizardPanel3 implements WizardDescriptor.Panel { } else { logger.log(Level.SEVERE, "Missing image process object"); } - + // Start ingest if we can startIngest(); @@ -457,7 +452,7 @@ class AddImageWizardPanel3 implements WizardDescriptor.Panel { wizPanel.getComponent().changeProgressBarTextAndColor("*Failed to add image.", 0, Color.black); // set error message // Log error/display warning - Logger logger = Logger.getLogger(AddImgTask.class.getName()); + logger.log(Level.SEVERE, "Error adding image to case", ex); } finally { } From 6b84d33c40bc089c523ec9e7113906ef2854b92b Mon Sep 17 00:00:00 2001 From: 0xNF Date: Thu, 29 Nov 2012 14:49:09 -0500 Subject: [PATCH 04/17] Added ability to extract unallocated space to a single file per volume --- .../directorytree/DataResultFilterNode.java | 2 +- .../directorytree/ExtractUnallocAction.java | 269 ++++++++++++++++++ 2 files changed, 270 insertions(+), 1 deletion(-) create mode 100644 Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java index 36b10f81a7..2d9de3dd45 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java @@ -160,7 +160,7 @@ public class DataResultFilterNode extends FilterNode { List actions = new ArrayList(); actions.add(new NewWindowViewAction("View in New Window", vol)); actions.addAll(ShowDetailActionVisitor.getActions(vol.getLookup().lookup(Content.class))); - + actions.add(new ExtractUnallocAction("Extract Unallocated Space as Single File", vol)); return actions; } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java new file mode 100644 index 0000000000..0fc49e8078 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java @@ -0,0 +1,269 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011 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.directorytree; + +import java.awt.Frame; +import java.awt.event.ActionEvent; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.logging.Level; +import javax.swing.AbstractAction; +import javax.swing.JOptionPane; +import javax.swing.SwingWorker; +import org.netbeans.api.progress.ProgressHandle; +import org.netbeans.api.progress.ProgressHandleFactory; +import org.openide.util.Cancellable; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.VolumeNode; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.ContentVisitor; +import org.sleuthkit.datamodel.Directory; +import org.sleuthkit.datamodel.FileSystem; +import org.sleuthkit.datamodel.LayoutDirectory; +import org.sleuthkit.datamodel.LayoutFile; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Extracts all the unallocated space as a single file + */ +public final class ExtractUnallocAction extends AbstractAction{ + + private static List llf; + private static Content vol; + private static final Logger logger = Logger.getLogger(ExtractUnallocAction.class.getName()); + + ExtractUnallocAction(String title, VolumeNode volume) { + super(title); + vol = volume.getLookup().lookup(Content.class); + llf = getUnallocFiles(vol); + Collections.sort(llf, new SortObjId()); + } + + /** + * Writes the unallocated files to $CaseDir/Export/ImgName-Unalloc-ImgObjectID-VolumeID.dat + * @param e + */ + @Override + public void actionPerformed(ActionEvent e) { + if (llf != null && llf.size() > 0) { + try { + String imgName = vol.getImage().getName(); //Image Name + long imgObjID = vol.getImage().getId(); //Image ID + long volumeID = vol.getId(); //Volume ID + String UnallocName = imgName + "-Unalloc-" + imgObjID + "-" + volumeID + ".dat"; + //Format for single Unalloc File is ImgName-Unalloc-ImgObjectID-VolumeID.dat + File unalloc = new File(Case.getCurrentCase().getCaseDirectory() + File.separator + "Export" + File.separator + UnallocName); + if (unalloc.exists()) { + int res = JOptionPane.showConfirmDialog(new Frame(), "The Unalloc File for this volume, " + UnallocName + " already exists, do you want to replace it?"); + if (res == JOptionPane.YES_OPTION) { + unalloc.delete(); + } else { + return; + } + } + ExtractUnallocWorker uw = new ExtractUnallocWorker(unalloc, this); + uw.execute(); + }catch (TskCoreException tce) { + logger.log(Level.WARNING, "Could not create Unalloc File; error getting image info", tce); + } + } else { + logger.log(Level.WARNING, "Tried to get unallocated content from volume ID " + vol.getId() + ", but its list of unallocated files was empty or null"); + } + } + + private List getUnallocFiles(Content c) { + UnallocVisitor uv = new UnallocVisitor(); + logger.log(Level.INFO, "Sending out Unallocated File Visitor"); + try { + return c.getChildren().get(0).accept(uv); //Launching it on the root directory + } catch (TskCoreException tce) { + logger.log(Level.WARNING, "Couldn't get a list of Unallocated Files, failed at sending out the visitor ", tce); + } + return null; + } + + + /** + * Private class for dispatching the file IO in a background thread. + */ + private class ExtractUnallocWorker extends SwingWorker { + + ExtractUnallocAction ua; + File path; + private ProgressHandle progress; + private boolean canceled = false; + + ExtractUnallocWorker(File path, ExtractUnallocAction ua) { + this.path = path; + this.ua = ua; + } + + @Override + protected Integer doInBackground() { + try { + progress = ProgressHandleFactory.createHandle("Extracting " + path.getName(), new Cancellable() { + @Override + public boolean cancel() { + logger.log(Level.INFO, "Canceling extraction of Unalloc file " + path.getName()); + canceled = true; + if (progress != null) { + progress.setDisplayName(path.getName() + " (Cancelling...)"); + } + return true; + } + }); + FileOutputStream fos = new FileOutputStream(path); + int MAX_BYTES = 8192; + byte[] buf = new byte[MAX_BYTES]; //read 8k at a time + logger.log(Level.INFO, "Writing Unalloc file to " + path.getPath()); + + progress.start(llf.size()); + int count = 0; + for (LayoutFile f : llf) { + long offset = 0L; + while (offset != f.getSize() && !canceled) { + offset += f.read(buf, offset, MAX_BYTES); //Offset + Bytes read + fos.write(buf); + } + progress.progress(count++); + } + progress.finish(); + fos.flush(); + fos.close(); + + if(canceled){ + path.delete(); + logger.log(Level.INFO, "Canceled extraction of " + path.getName() + " and deleted file"); + } + else{ + logger.log(Level.INFO, "Finished writing unalloc file"); + } + } catch (IOException ioe) { + logger.log(Level.WARNING, "Could not create Unalloc File; error writing file", ioe); + return -1; + } catch (TskCoreException tce) { + logger.log(Level.WARNING, "Could not create Unalloc File; error getting image info", tce); + return -1; + } + return 1; + } + } + + private static class UnallocVisitor extends ContentVisitor.Default> { + + /** + * If the volume has no FileSystem, then it will call this method to return the single instance of unallocated space. + * @param lf the LayoutFile the visitor encountered + * @return A list of size 1 + */ + @Override + public List visit(final org.sleuthkit.datamodel.LayoutFile lf) { + return new ArrayList() { + { + add(lf); + } + }; + } + + /** + * If the visitor finds a FileSystem, it will filter the results for directories and return on the Root Dir. + * @param fs the FileSystem the visitor encountered + * @return A list containing the layout files from subsequent Visits() + */ + @Override + public List visit(FileSystem fs) { + try { + for (Content c : fs.getChildren()){ + if(((AbstractFile)c).isRoot()){ + return c.accept(this); + } + } + } catch (TskCoreException tce) { + logger.log(Level.WARNING, "Couldn't get a list of Unallocated Files, failed at visiting FileSystem " + fs.getId(), tce); + } + return null; + } + + /** + * LayoutDirectory has all the Layout(Unallocated) files + * @param ld LayoutDirectory the visitor encountered + * @return A list containing all the LayoutFile in ld + */ + @Override + public List visit(LayoutDirectory ld){ + try{ + List lflst = new ArrayList(); + for(Content layout : ld.getChildren()){ + lflst.add((LayoutFile)layout); + } + return lflst; + } catch(TskCoreException tce){ + logger.log(Level.WARNING, "Could not get list of Layout Files, failed at visiting Layout Directory in " + vol.getId(), tce); + } + return null; + } + + /** + * The only time this visitor should ever encounter a directory is when parsing over Root + * @param dir the directory this visitor encountered + * @return A list containing LayoutFiles encountered during subsequent Visits() + */ + @Override + public List visit(Directory dir) { + try { + for (Content c : dir.getChildren()) { + if(c instanceof LayoutDirectory){ + return c.accept(this); + } + } + }catch (TskCoreException tce) { + logger.log(Level.WARNING, "Couldn't get a list of Unallocated Files, failed at visiting Directory " + dir.getId(), tce); + } + return null; + } + + @Override + protected List defaultVisit(Content cntnt) { + return null; + } + } + + private class SortObjId implements Comparator{ + + @Override + public int compare(LayoutFile o1, LayoutFile o2) { + if(o1.getId() == o2.getId()){ + return 0; + } + if(o1.getId() > o2.getId()){ + return -1; + } + else{ + return 1; + } + } + } +} From 0b835ecd46e42b67bb0ecfcf583d6072ed79596f Mon Sep 17 00:00:00 2001 From: adam-m Date: Thu, 29 Nov 2012 16:30:37 -0500 Subject: [PATCH 05/17] text view use only up to 1024 chars to detect text direction --- .../autopsy/keywordsearch/ExtractedContentPanel.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java index 01509990ff..567d6fcaaa 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java @@ -356,8 +356,10 @@ class ExtractedContentPanel extends javax.swing.JPanel { text = ""; } - //detect text direction and set it - extractedTextPane.applyComponentOrientation(TextUtil.getTextDirection(text)); + //detect text direction using first 1024 chars and set it + final int maxOrientChars = Math.min(text.length(), 1024); + final String orientDetectText = text.substring(0, maxOrientChars); + extractedTextPane.applyComponentOrientation(TextUtil.getTextDirection(orientDetectText)); extractedTextPane.setText(text); extractedTextPane.setCaretPosition(0); From ae7a971be491206d2815c1c553c8a3c65661d97b Mon Sep 17 00:00:00 2001 From: 0xNF Date: Thu, 29 Nov 2012 16:33:08 -0500 Subject: [PATCH 06/17] ExtractUnalloc now prevents users from running extract on a single volume more than once when it's already running. --- .../directorytree/ExtractUnallocAction.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java index 0fc49e8078..c7e224a052 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java @@ -53,6 +53,7 @@ public final class ExtractUnallocAction extends AbstractAction{ private static List llf; private static Content vol; + volatile static boolean running = false; private static final Logger logger = Logger.getLogger(ExtractUnallocAction.class.getName()); ExtractUnallocAction(String title, VolumeNode volume) { @@ -76,6 +77,10 @@ public final class ExtractUnallocAction extends AbstractAction{ String UnallocName = imgName + "-Unalloc-" + imgObjID + "-" + volumeID + ".dat"; //Format for single Unalloc File is ImgName-Unalloc-ImgObjectID-VolumeID.dat File unalloc = new File(Case.getCurrentCase().getCaseDirectory() + File.separator + "Export" + File.separator + UnallocName); + if(running){ + JOptionPane.showMessageDialog(new Frame(), "Extract is already running on this volume. Please select a different volume."); + return; + } if (unalloc.exists()) { int res = JOptionPane.showConfirmDialog(new Frame(), "The Unalloc File for this volume, " + UnallocName + " already exists, do you want to replace it?"); if (res == JOptionPane.YES_OPTION) { @@ -84,7 +89,7 @@ public final class ExtractUnallocAction extends AbstractAction{ return; } } - ExtractUnallocWorker uw = new ExtractUnallocWorker(unalloc, this); + ExtractUnallocWorker uw = new ExtractUnallocWorker(unalloc); uw.execute(); }catch (TskCoreException tce) { logger.log(Level.WARNING, "Could not create Unalloc File; error getting image info", tce); @@ -111,14 +116,13 @@ public final class ExtractUnallocAction extends AbstractAction{ */ private class ExtractUnallocWorker extends SwingWorker { - ExtractUnallocAction ua; File path; private ProgressHandle progress; private boolean canceled = false; - ExtractUnallocWorker(File path, ExtractUnallocAction ua) { + ExtractUnallocWorker(File path) { this.path = path; - this.ua = ua; + running = true; } @Override @@ -167,6 +171,8 @@ public final class ExtractUnallocAction extends AbstractAction{ } catch (TskCoreException tce) { logger.log(Level.WARNING, "Could not create Unalloc File; error getting image info", tce); return -1; + }finally{ + running = false; } return 1; } @@ -257,7 +263,7 @@ public final class ExtractUnallocAction extends AbstractAction{ public int compare(LayoutFile o1, LayoutFile o2) { if(o1.getId() == o2.getId()){ return 0; - } + } if(o1.getId() > o2.getId()){ return -1; } From 72fae4599bf9114ec074b303c581ae60f9f05b96 Mon Sep 17 00:00:00 2001 From: adam-m Date: Thu, 29 Nov 2012 16:43:13 -0500 Subject: [PATCH 07/17] (AUT-685) Calculate MD5 of 0-sized files --- .../org/sleuthkit/autopsy/hashdatabase/HashDbIngestModule.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbIngestModule.java b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbIngestModule.java index ee8e456910..eb3d241288 100644 --- a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbIngestModule.java +++ b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbIngestModule.java @@ -323,8 +323,7 @@ public class HashDbIngestModule implements IngestModuleAbstractFile { ProcessResult ret = ProcessResult.OK; boolean processFile = true; - if (fsContent.getSize() == 0 - || fsContent.getKnown().equals(TskData.FileKnown.BAD)) { + if (fsContent.getKnown().equals(TskData.FileKnown.BAD)) { processFile = false; } if (processFile && (nsrlIsSet || knownBadIsSet)) { From 132488dcd0d4c5861fedb73a5ee1f4fd50b6729d Mon Sep 17 00:00:00 2001 From: adam-m Date: Thu, 29 Nov 2012 17:36:24 -0500 Subject: [PATCH 08/17] better log message when cannot force stop file ingest worker, always free reference after stop() --- Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java index 0c9398f3cb..df0aec6e8d 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java @@ -371,7 +371,7 @@ public class IngestManager { try { s.stop(); } catch (Exception e) { - logger.log(Level.WARNING, "Exception while stopping module: " + s.getName(), e); + logger.log(Level.WARNING, "Unexpected exception while stopping module: " + s.getName(), e); } } @@ -379,10 +379,11 @@ public class IngestManager { //stop fs ingester thread boolean cancelled = abstractFileIngester.cancel(true); if (!cancelled) { - logger.log(Level.WARNING, "Unable to cancel file ingest worker"); - } else { - abstractFileIngester = null; + logger.log(Level.INFO, "Unable to cancel file ingest worker, likely already stopped"); } + + abstractFileIngester = null; + } List toStop = new ArrayList(); From 8d81bd05705269efa9a2de32f71274ea6bfa9e0a Mon Sep 17 00:00:00 2001 From: adam-m Date: Thu, 29 Nov 2012 18:33:26 -0500 Subject: [PATCH 09/17] AUT-683 Fix text direction setting in Text view --- .../keywordsearch/ExtractedContentPanel.java | 48 ++++++++++++------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java index 567d6fcaaa..b9894f83f5 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java @@ -25,6 +25,7 @@ import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.util.ArrayList; import java.util.List; +import java.util.logging.Level; import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.JMenuItem; import javax.swing.SwingWorker; @@ -39,6 +40,7 @@ import javax.swing.text.html.HTMLEditorKit; import javax.swing.text.html.HTMLEditorKit.HTMLFactory; import org.netbeans.api.progress.ProgressHandle; import org.netbeans.api.progress.ProgressHandleFactory; +import org.sleuthkit.autopsy.coreutils.EscapeUtil; import org.sleuthkit.autopsy.coreutils.TextUtil; /** @@ -320,7 +322,7 @@ class ExtractedContentPanel extends javax.swing.JPanel { */ void setSources(List sources) { sourceComboBox.removeAllItems(); - setPanelText(null); + setPanelText(null, false); for (MarkupSource ms : sources) { sourceComboBox.addItem(ms); @@ -351,16 +353,29 @@ class ExtractedContentPanel extends javax.swing.JPanel { return (MarkupSource) sourceComboBox.getSelectedItem(); } - private void setPanelText(String text) { - if (text == null ) { + private void setPanelText(String text, boolean detectDirection) { + if (text == null) { text = ""; } - - //detect text direction using first 1024 chars and set it - final int maxOrientChars = Math.min(text.length(), 1024); - final String orientDetectText = text.substring(0, maxOrientChars); - extractedTextPane.applyComponentOrientation(TextUtil.getTextDirection(orientDetectText)); - + + if (detectDirection) { + //detect text direction using first 1024 chars and set it + //get first up to 1024 chars, strip
 tag and unescape html to get the string on which to detect
+            final int len = text.length();
+            final int prefixLen = "
".length();
+            if (len > prefixLen) {
+                final int maxOrientChars = Math.min(len, 1024);
+                final String orientDetectText = EscapeUtil.unEscapeHtml(text.substring(prefixLen, maxOrientChars));
+                ComponentOrientation direction = TextUtil.getTextDirection(orientDetectText);
+                //logger.log(Level.INFO, "ORIENTATION LEFT TO RIGHT: " + direction.isLeftToRight());
+                extractedTextPane.applyComponentOrientation(direction);
+            } else {
+                extractedTextPane.applyComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+            }
+        } else {
+            extractedTextPane.applyComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
+        }
+
         extractedTextPane.setText(text);
         extractedTextPane.setCaretPosition(0);
     }
@@ -572,16 +587,15 @@ class ExtractedContentPanel extends javax.swing.JPanel {
         }
     }
 
-    
     /**
-     * Gets and sets new markup.  Updates GUI in GUI thread and gets markup in background thread.
-     * To be invoked from GUI thread only.
+     * Gets and sets new markup. Updates GUI in GUI thread and gets markup in
+     * background thread. To be invoked from GUI thread only.
      */
     private void setMarkup(MarkupSource source) {
-        setPanelText("Loading text... Please wait");
+        setPanelText("Loading text... Please wait", false);
         new SetMarkup(source).execute();
     }
-    
+
     /**
      * Swingworker to get makrup source content String from Solr in background
      * thread and then set the panel text in the EDT Helps not to block the UI
@@ -603,7 +617,7 @@ class ExtractedContentPanel extends javax.swing.JPanel {
             progress.setDisplayName("Loading text");
             progress.start();
             progress.switchToIndeterminate();
-            
+
             markup = source.getMarkup();
             return null;
         }
@@ -613,9 +627,9 @@ class ExtractedContentPanel extends javax.swing.JPanel {
             //super.done();
             progress.finish();
             if (markup != null) {
-                setPanelText(markup);
+                setPanelText(markup, true);
             } else {
-                setPanelText("");
+                setPanelText("", false);
             }
             updateControls(source);
 

From a48f5e674877cc53fe1245c91dd142cc5073565f Mon Sep 17 00:00:00 2001
From: adam-m 
Date: Thu, 29 Nov 2012 18:40:35 -0500
Subject: [PATCH 10/17] search for files with same md5sum if at least 1 hash
 exists in db

---
 .../hashdatabase/HashDbSearchAction.java      | 33 ++++++++-----------
 1 file changed, 13 insertions(+), 20 deletions(-)

diff --git a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbSearchAction.java b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbSearchAction.java
index a739a1259a..f80e9063be 100644
--- a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbSearchAction.java
+++ b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbSearchAction.java
@@ -32,21 +32,21 @@ import org.sleuthkit.datamodel.FsContent;
 /**
  * Searches for FsContent Files with the same MD5 hash as the given Node's
  * FsContent's MD5 hash. This action should only be available from Nodes with
- * specific Content attached; it is manually programmed into a Node's available actions.
+ * specific Content attached; it is manually programmed into a Node's available
+ * actions.
  */
 public class HashDbSearchAction extends CallableSystemAction implements HashSearchProvider {
 
     private static final InitializeContentVisitor initializeCV = new InitializeContentVisitor();
     private FsContent fsContent;
-
     private static HashDbSearchAction instance = null;
 
     HashDbSearchAction() {
         super();
     }
-    
+
     public static HashDbSearchAction getDefault() {
-        if(instance == null){
+        if (instance == null) {
             instance = new HashDbSearchAction();
         }
         return instance;
@@ -60,7 +60,7 @@ public class HashDbSearchAction extends CallableSystemAction implements HashSear
     }
 
     /**
-     * Returns the FsContent if it is supported, otherwise null. It should 
+     * Returns the FsContent if it is supported, otherwise null. It should
      * realistically never return null or a Directory, only a File.
      */
     private static class InitializeContentVisitor extends ContentVisitor.Default {
@@ -82,28 +82,21 @@ public class HashDbSearchAction extends CallableSystemAction implements HashSear
     }
 
     /**
-     * Find all files with the same MD5 hash as this' fsContent. fsContent should
-     * be previously set by calling the search function, which in turn calls performAction.
+     * Find all files with the same MD5 hash as this' fsContent. fsContent
+     * should be previously set by calling the search function, which in turn
+     * calls performAction.
      */
     @Override
     public void performAction() {
-        // Make sure all files have an md5 hash
-        if(HashDbSearcher.allFilesMd5Hashed()) {
+        // Make sure at least 1 file has an md5 hash
+        if (HashDbSearcher.countFilesMd5Hashed() > 0) {
             doSearch();
-        // and if not, warn the user
-        } else if(HashDbSearcher.countFilesMd5Hashed() > 0) {
-            Object selected = JOptionPane.showConfirmDialog(null, "Not all files have MD5 hashes. "
-                    + "Search results will be incomplete.\n"
-                    + "Would you like to search anyway?", "File Search by MD5 Hash", JOptionPane.YES_NO_OPTION);
-            if(selected.equals(JOptionPane.YES_OPTION)) {
-                doSearch();
-            }
         } else {
-                JOptionPane.showMessageDialog(null, "No files currently have an MD5 hash.",
-                        "File Search by MD5 Hash", JOptionPane.ERROR_MESSAGE);
+            JOptionPane.showMessageDialog(null, "No files currently have an MD5 hash calculated, run HashDB ingest first.",
+                    "File Search by MD5 Hash", JOptionPane.ERROR_MESSAGE);
         }
     }
-    
+
     private void doSearch() {
         HashDbSearchThread hashThread = new HashDbSearchThread(fsContent);
         hashThread.execute();

From ed8d4cab8685e20edd889c844aa0f7ef77639b01 Mon Sep 17 00:00:00 2001
From: adam-m 
Date: Thu, 29 Nov 2012 18:45:52 -0500
Subject: [PATCH 11/17] allow to run hash search from dialog if at least 1 hash
 present

---
 .../autopsy/hashdatabase/HashDbSearchPanel.java | 17 +++--------------
 1 file changed, 3 insertions(+), 14 deletions(-)

diff --git a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbSearchPanel.java b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbSearchPanel.java
index cc79736d54..7c50fc25f6 100644
--- a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbSearchPanel.java
+++ b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbSearchPanel.java
@@ -292,20 +292,9 @@ public class HashDbSearchPanel extends javax.swing.JPanel implements ActionListe
     boolean search() {
         // Check if any hashed have been entered
         if(hashTable.getRowCount()!=0) {
-            // Make sure all files have an md5 hash
-            if(HashDbSearcher.allFilesMd5Hashed()) {
-                return doSearch();
-            // and if not, warn the user
-            } else if(HashDbSearcher.countFilesMd5Hashed() > 0) {
-                errorField.setVisible(false);
-                Object selected = JOptionPane.showConfirmDialog(null, "Not all files have MD5 hashes. "
-                        + "Search results will be incomplete.\n"
-                        + "Would you like to search anyway?", "File Search by MD5 Hash", JOptionPane.YES_NO_OPTION);
-                if(selected.equals(JOptionPane.YES_OPTION)) {
-                    return doSearch();
-                } else {
-                    return false;
-                }
+            // Make sure at least 1 file has an md5 hash
+           if(HashDbSearcher.countFilesMd5Hashed() > 0) {
+               return doSearch();
             } else {
                 JOptionPane.showMessageDialog(null, "No files currently have an MD5 hash.",
                         "File Search by MD5 Hash", JOptionPane.ERROR_MESSAGE);

From b38651e2593cf492023181f9ef4235350df914aa Mon Sep 17 00:00:00 2001
From: 0xNF 
Date: Fri, 30 Nov 2012 10:34:12 -0500
Subject: [PATCH 12/17] CaseSchema should no longer report errors when the case
 number contains strings

---
 Core/src/org/sleuthkit/autopsy/casemodule/CaseSchema.xsd | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseSchema.xsd b/Core/src/org/sleuthkit/autopsy/casemodule/CaseSchema.xsd
index 41e3724f69..30a86d859a 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseSchema.xsd
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseSchema.xsd
@@ -6,9 +6,9 @@
 
 
 
-
+
 
-
+
 
 
 

From c93928cf4d639064673b8e9894dabb7584ce0c27 Mon Sep 17 00:00:00 2001
From: 0xNF 
Date: Fri, 30 Nov 2012 10:43:33 -0500
Subject: [PATCH 13/17] ExtractUnallocAction now applies to all types of
 VolumeNodes, not just ones in the middle panel

---
 .../autopsy/datamodel/VolumeNode.java         |  2 +
 .../directorytree/DataResultFilterNode.java   |  1 -
 .../directorytree/ExtractUnallocAction.java   | 44 ++++++++++---------
 .../ShowDetailActionVisitor.java              | 42 +++++++++++-------
 4 files changed, 52 insertions(+), 37 deletions(-)

diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java
index 10aaeccf1d..091ac4797e 100644
--- a/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java
+++ b/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java
@@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.datamodel;
 
 import javax.swing.Action;
 import org.openide.nodes.Sheet;
+import org.sleuthkit.autopsy.directorytree.ExtractUnallocAction;
 import org.sleuthkit.datamodel.Volume;
 
 /**
@@ -66,6 +67,7 @@ public class VolumeNode extends AbstractContentNode {
     public Action[] getActions(boolean popup) {
         return new Action[]{ //new ShowDetailAction("Volume Details", this.getName(), this),
                 //new ShowDetailAction("File System Details", this.getName(), this)
+            //new ExtractUnallocAction("Extract Unallocated Files to single Single", this)
                 };
     }
 
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java
index 2d9de3dd45..e0ad518535 100644
--- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java
@@ -160,7 +160,6 @@ public class DataResultFilterNode extends FilterNode {
             List actions = new ArrayList();
             actions.add(new NewWindowViewAction("View in New Window", vol));
             actions.addAll(ShowDetailActionVisitor.getActions(vol.getLookup().lookup(Content.class)));
-            actions.add(new ExtractUnallocAction("Extract Unallocated Space as Single File", vol));
             return actions;
         }
 
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java
index c7e224a052..f8018115ed 100644
--- a/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java
@@ -45,21 +45,32 @@ import org.sleuthkit.datamodel.FileSystem;
 import org.sleuthkit.datamodel.LayoutDirectory;
 import org.sleuthkit.datamodel.LayoutFile;
 import org.sleuthkit.datamodel.TskCoreException;
+import org.sleuthkit.datamodel.Volume;
 
 /**
  * Extracts all the unallocated space as a single file
  */
 public final class ExtractUnallocAction extends AbstractAction{
 
-    private static List llf;
-    private static Content vol;
+    private List llf;
+    long VolumeId;
+    String ImageName;
+    long ImageId;
     volatile static boolean running = false;
     private static final Logger logger = Logger.getLogger(ExtractUnallocAction.class.getName());
     
-    ExtractUnallocAction(String title, VolumeNode volume) {
+    public ExtractUnallocAction(String title, Volume volu){
         super(title);
-        vol = volume.getLookup().lookup(Content.class);
-        llf = getUnallocFiles(vol);
+        VolumeId = volu.getId();
+        try{
+            ImageName = volu.getImage().getName();
+            ImageId = volu.getImage().getId();
+        } catch(TskCoreException tce){
+            logger.log(Level.WARNING, "Unable to properly create ExtractUnallocAction, extraction may be incomplete", tce);
+            ImageName = "";
+            ImageId = 0;
+        }
+        llf = getUnallocFiles(volu);
         Collections.sort(llf, new SortObjId());
     }
 
@@ -70,15 +81,11 @@ public final class ExtractUnallocAction extends AbstractAction{
     @Override
     public void actionPerformed(ActionEvent e) {
         if (llf != null && llf.size() > 0) {
-            try {
-                String imgName = vol.getImage().getName();   //Image Name
-                long imgObjID = vol.getImage().getId();      //Image ID
-                long volumeID = vol.getId();                 //Volume ID
-                String UnallocName = imgName + "-Unalloc-" + imgObjID + "-" + volumeID + ".dat";
+                String UnallocName = ImageName + "-Unalloc-" + ImageId + "-" + VolumeId + ".dat";
                 //Format for single Unalloc File is ImgName-Unalloc-ImgObjectID-VolumeID.dat
                 File unalloc = new File(Case.getCurrentCase().getCaseDirectory() + File.separator + "Export" + File.separator + UnallocName);
                 if(running){
-                    JOptionPane.showMessageDialog(new Frame(), "Extract is already running on this volume. Please select a different volume.");
+                    JOptionPane.showMessageDialog(new Frame(), "Unallocated Space is already running on this volume. Please select a different volume.");
                     return;
                 }
                 if (unalloc.exists()) {
@@ -91,11 +98,8 @@ public final class ExtractUnallocAction extends AbstractAction{
                 }                
                 ExtractUnallocWorker uw = new ExtractUnallocWorker(unalloc);
                 uw.execute();
-            }catch (TskCoreException tce) {
-                logger.log(Level.WARNING, "Could not create Unalloc File; error getting image info", tce);
-            }
         } else {
-            logger.log(Level.WARNING, "Tried to get unallocated content from volume ID " + vol.getId() + ", but its list of unallocated files was empty or null");
+            logger.log(Level.WARNING, "Tried to get unallocated content from volume ID " + VolumeId + ", but its list of unallocated files was empty or null");
         }
     }
 
@@ -183,7 +187,7 @@ public final class ExtractUnallocAction extends AbstractAction{
         /**
          * If the volume has no FileSystem, then it will call this method to return the single instance of unallocated space.
          * @param lf the LayoutFile the visitor encountered
-         * @return A list of size 1
+         * @return A list of size 1, returns null if it fails
          */
         @Override
         public List visit(final org.sleuthkit.datamodel.LayoutFile lf) {
@@ -197,7 +201,7 @@ public final class ExtractUnallocAction extends AbstractAction{
         /**
          * If the visitor finds a FileSystem, it will filter the results for directories and return on the Root Dir.
          * @param fs the FileSystem the visitor encountered
-         * @return A list containing the layout files from subsequent Visits()
+         * @return A list containing the layout files from subsequent Visits(), returns null if it fails
          */
         @Override
         public List visit(FileSystem fs) {
@@ -216,7 +220,7 @@ public final class ExtractUnallocAction extends AbstractAction{
         /**
          * LayoutDirectory has all the Layout(Unallocated) files
          * @param ld LayoutDirectory the visitor encountered
-         * @return A list containing all the LayoutFile in ld
+         * @return A list containing all the LayoutFile in ld, returns null if it fails
          */
         @Override
         public List visit(LayoutDirectory ld){
@@ -227,7 +231,7 @@ public final class ExtractUnallocAction extends AbstractAction{
                 }
                 return lflst;
             } catch(TskCoreException tce){
-                logger.log(Level.WARNING, "Could not get list of Layout Files, failed at visiting Layout Directory in " + vol.getId(), tce);
+                logger.log(Level.WARNING, "Could not get list of Layout Files, failed at visiting Layout Directory", tce);
             }
             return null;
         }
@@ -235,7 +239,7 @@ public final class ExtractUnallocAction extends AbstractAction{
         /**
          * The only time this visitor should ever encounter a directory is when parsing over Root
          * @param dir the directory this visitor encountered
-         * @return A list containing LayoutFiles encountered during subsequent Visits()
+         * @return A list containing LayoutFiles encountered during subsequent Visits(), returns null if it fails
          */
         @Override
         public List visit(Directory dir) {
diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ShowDetailActionVisitor.java b/Core/src/org/sleuthkit/autopsy/directorytree/ShowDetailActionVisitor.java
index c539f9b0de..537b127da2 100644
--- a/Core/src/org/sleuthkit/autopsy/directorytree/ShowDetailActionVisitor.java
+++ b/Core/src/org/sleuthkit/autopsy/directorytree/ShowDetailActionVisitor.java
@@ -44,11 +44,7 @@ import org.sleuthkit.datamodel.FileSystem;
 import org.sleuthkit.datamodel.Image;
 import org.sleuthkit.datamodel.Volume;
 
-/**
- * 
- *
- * @author jantonius
- */
+
 class ShowDetailActionVisitor extends ContentVisitor.Default> {
     
     private static ShowDetailActionVisitor instance = new ShowDetailActionVisitor();
@@ -231,11 +227,32 @@ class ShowDetailActionVisitor extends ContentVisitor.Default visit(final Volume vol) {
-        final String title = "Volume Details";
+        List lst = new ArrayList();
+        lst.add(new VolumeDetailsAction("Volume Details", vol));
+        lst.add(new ExtractUnallocAction("Extract Unallocated Space to Single File", vol));
+        return lst;
+    }
+       
 
-        return Collections.singletonList(new AbstractAction(title) {
+    @Override
+    protected List defaultVisit(Content di) {
+        return new ArrayList();
+    }
+}
 
-            @Override
+
+ class VolumeDetailsAction extends AbstractAction{
+     
+     String title;
+     Volume vol;
+     
+     VolumeDetailsAction(String title, Volume vol){
+         super(title);
+         this.title = title;
+         this.vol = vol;
+     }
+     
+                 @Override
             public void actionPerformed(ActionEvent e) {
                 Logger.noteAction(ShowDetailActionVisitor.class);
 
@@ -289,11 +306,4 @@ class ShowDetailActionVisitor extends ContentVisitor.Default defaultVisit(Content di) {
-        return new ArrayList();
-    }
-}
\ No newline at end of file
+ }
\ No newline at end of file

From 9ab004802a12c58053eb039e49bc2409ad2ca04b Mon Sep 17 00:00:00 2001
From: 0xNF 
Date: Fri, 30 Nov 2012 12:18:45 -0500
Subject: [PATCH 14/17] Download Path column now correctly shows location of
 the the local file.

---
 .../src/org/sleuthkit/autopsy/recentactivity/Firefox.java     | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java
index 74d6c46d6e..c6995577a8 100644
--- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java
+++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java
@@ -282,8 +282,8 @@ public class Firefox extends Extract implements IngestModuleImage {
                         //TODO Revisit usage of deprecated constructor as per TSK-583
                         //bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_LAST_ACCESSED.getTypeID(), "RecentActivity", "Last Visited", (Long.valueOf(result.get("startTime").toString()))));
                         bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED.getTypeID(), "RecentActivity", (Long.valueOf(result.get("startTime").toString()))));
-                        bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH_ID.getTypeID(), "RecentActivity", Util.findID(urldecodedtarget)));
-                        bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH.getTypeID(), "RecentActivity", urldecodedtarget));
+                        bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH_ID.getTypeID(), "RecentActivity", Util.findID(urldecodedtarget))); 
+                        bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH.getTypeID(), "RecentActivity", ((result.get("target").toString() != null) ? result.get("target").toString() : "")));
                         bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID(), "RecentActivity", "FireFox"));
                         bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN.getTypeID(), "RecentActivity", (Util.extractDomain((result.get("source").toString() != null) ? result.get("source").toString() : ""))));
                         this.addArtifact(ARTIFACT_TYPE.TSK_WEB_DOWNLOAD, FFSqlitedb.get(j), bbattributes);

From 3f4b1fe476267d71efb1e1be3071224a92088d19 Mon Sep 17 00:00:00 2001
From: 0xNF 
Date: Fri, 30 Nov 2012 13:30:40 -0500
Subject: [PATCH 15/17] CurrentlyProcessing tags will now vanish when AddImage
 completes.

---
 .../casemodule/AddImageVisualPanel2.java      |  8 +++++++
 .../casemodule/AddImageWizardPanel3.java      | 22 ++++++++++++-------
 2 files changed, 22 insertions(+), 8 deletions(-)

diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageVisualPanel2.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageVisualPanel2.java
index f42b418824..333f4e9db4 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageVisualPanel2.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageVisualPanel2.java
@@ -111,6 +111,14 @@ final class AddImageVisualPanel2 extends JPanel {
     public void changeCurrentDir(String dir){
         this.TextArea_CurrentDirectory.setText(dir);
     }
+    
+    /**
+     * Sets the CurrentlyProcessing tag and text area to be invisible
+     */
+    public void setProcessInvis(){
+        this.Label_CurrentDirectory_Static.setVisible(false);
+        this.TextArea_CurrentDirectory.setVisible(false);
+    }
 
     
 
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardPanel3.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardPanel3.java
index 6ffa8fd944..aa5f04b690 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardPanel3.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardPanel3.java
@@ -226,25 +226,31 @@ class AddImageWizardPanel3 implements WizardDescriptor.Panel {
                 while(prog.getValue() < 100 || prog.isIndeterminate()){ //TODO Rely on state variable in AddImgTask class
                     
                     EventQueue.invokeLater(new Runnable() {
-
                         @Override
-                        public void run() {        
-                             wiz.changeCurrentDir(proc.currentDirectory());
+                        public void run() {
+                            wiz.changeCurrentDir(proc.currentDirectory());
                         }
-                        
-                  
                     });
-                            
+
                     Thread.sleep(2 * 1000);
                 }
                 return 1;
-            }
-            catch(InterruptedException ie){
+            } catch (InterruptedException ie) {
                 return -1;
             }
         }
+
+        /**
+         * When done, set the Wizards processing tags to be invisible
+         */
+        @Override
+        protected void done() {
+            wiz.setProcessInvis();
+        }
     }
 
+
+
     /**
      * Thread that will make the JNI call to ingest the image.
      */

From 42523e99258c07e005dd542b7af54ebc7da98ea0 Mon Sep 17 00:00:00 2001
From: adam-m 
Date: Fri, 30 Nov 2012 17:34:38 -0500
Subject: [PATCH 16/17] update to use non-static hasher

---
 .../sleuthkit/autopsy/hashdatabase/HashDbIngestModule.java  | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbIngestModule.java b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbIngestModule.java
index eb3d241288..2a59d671df 100644
--- a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbIngestModule.java
+++ b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbIngestModule.java
@@ -69,6 +69,8 @@ public class HashDbIngestModule implements IngestModuleAbstractFile {
     static long lookuptime = 0;
     private Map knownBadSets = new HashMap();
     private HashDbManagementPanel panel;
+    
+    private final Hash hasher = new Hash();
 
     private HashDbIngestModule() {
         count = 0;
@@ -332,7 +334,7 @@ public class HashDbIngestModule implements IngestModuleAbstractFile {
                     String md5Hash = fsContent.getMd5Hash();
                     if (md5Hash == null || md5Hash.isEmpty()) {
                         long calcstart = System.currentTimeMillis();
-                        md5Hash = Hash.calculateMd5(fsContent);
+                        md5Hash = hasher.calculateMd5(fsContent);
                         calctime += (System.currentTimeMillis() - calcstart);
                     }
                     TskData.FileKnown status = TskData.FileKnown.UKNOWN;
@@ -374,7 +376,7 @@ public class HashDbIngestModule implements IngestModuleAbstractFile {
                     String md5Hash = fsContent.getMd5Hash();
                     if (md5Hash == null || md5Hash.isEmpty()) {
                         long calcstart = System.currentTimeMillis();
-                        Hash.calculateMd5(fsContent);
+                        hasher.calculateMd5(fsContent);
                         calctime += (System.currentTimeMillis() - calcstart);
                     }
                     ret = ProcessResult.OK;

From f2155f3aa71201f54a15dfc818e2d37ae8c5c56c Mon Sep 17 00:00:00 2001
From: adam-m 
Date: Fri, 30 Nov 2012 17:49:57 -0500
Subject: [PATCH 17/17] Case closing: close top components before sending out
 events when case is changed, for a cleaner shutdown

---
 Core/src/org/sleuthkit/autopsy/casemodule/Case.java | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
index 5d389844b4..02b31d6a6e 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java
@@ -173,11 +173,13 @@ public class Case {
 
         String oldCaseName = oldCase != null ? oldCase.name : "";
 
+        doCaseChange(null); //closes windows, etc
         pcs.firePropertyChange(CASE_CURRENT_CASE, oldCase, null);
-        doCaseChange(null);
+        
 
-        pcs.firePropertyChange(CASE_NAME, oldCaseName, "");
         doCaseNameChange("");
+        pcs.firePropertyChange(CASE_NAME, oldCaseName, "");
+        
 
 
 
@@ -827,14 +829,16 @@ public class Case {
                 Case.runAddImageAction();
             }
         } else { // case is closed
+            // close all top components first
+            CoreComponentControl.closeCoreWindows();
+            
             // disable these menus
             CallableSystemAction.get(AddImageAction.class).setEnabled(false); // Add Image menu
             CallableSystemAction.get(CaseCloseAction.class).setEnabled(false); // Case Close menu
             CallableSystemAction.get(CasePropertiesAction.class).setEnabled(false); // Case Properties menu
             CallableSystemAction.get(CaseDeleteAction.class).setEnabled(false); // Delete Case menu
 
-            // close all top components
-            CoreComponentControl.closeCoreWindows();
+            
 
             Frame f = WindowManager.getDefault().getMainWindow();
             f.setTitle(Case.getAppName()); // set the window name to just application name