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 diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageVisualPanel2.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageVisualPanel2.java index 9f269f60a6..333f4e9db4 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,15 @@ 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); + } + + /** + * 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 237ed01945..50ef1ebc0e 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,47 +197,58 @@ class AddImageWizardPanel3 implements WizardDescriptor.Panel { wizPanel.getComponent().appendProgressText(" Ingest started."); } } - - /** + + /** * Class for getting the currently processing directory. - * + * */ - 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; } - + /** * @return the currently processing directory */ @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()); + 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. */ @@ -251,9 +260,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(); @@ -304,7 +313,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(); @@ -321,15 +330,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 +361,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 +384,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 +436,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 +455,7 @@ class AddImageWizardPanel3 implements WizardDescriptor.Panel { } else { logger.log(Level.SEVERE, "Missing image process object"); } - + // Start ingest if we can startIngest(); @@ -457,7 +467,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 { } 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 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 @@ - + - + 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 36b10f81a7..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))); - 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..f8018115ed --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java @@ -0,0 +1,279 @@ +/* + * 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; +import org.sleuthkit.datamodel.Volume; + +/** + * Extracts all the unallocated space as a single file + */ +public final class ExtractUnallocAction extends AbstractAction{ + + private List llf; + long VolumeId; + String ImageName; + long ImageId; + volatile static boolean running = false; + private static final Logger logger = Logger.getLogger(ExtractUnallocAction.class.getName()); + + public ExtractUnallocAction(String title, Volume volu){ + super(title); + 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()); + } + + /** + * 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) { + 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(), "Unallocated Space 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) { + unalloc.delete(); + } else { + return; + } + } + ExtractUnallocWorker uw = new ExtractUnallocWorker(unalloc); + uw.execute(); + } else { + logger.log(Level.WARNING, "Tried to get unallocated content from volume ID " + VolumeId + ", 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 { + + File path; + private ProgressHandle progress; + private boolean canceled = false; + + ExtractUnallocWorker(File path) { + this.path = path; + running = true; + } + + @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; + }finally{ + running = false; + } + 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, returns null if it fails + */ + @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(), returns null if it fails + */ + @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, returns null if it fails + */ + @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", 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(), returns null if it fails + */ + @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; + } + } + } +} 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 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(); diff --git a/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbIngestModule.java b/HashDatabase/src/org/sleuthkit/autopsy/hashdatabase/HashDbIngestModule.java index ee8e456910..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; @@ -323,8 +325,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)) { @@ -333,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; @@ -375,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; 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(); 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); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentPanel.java index 01509990ff..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,14 +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 and set it - extractedTextPane.applyComponentOrientation(TextUtil.getTextDirection(text)); - + + 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);
     }
@@ -570,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
@@ -601,7 +617,7 @@ class ExtractedContentPanel extends javax.swing.JPanel {
             progress.setDisplayName("Loading text");
             progress.start();
             progress.switchToIndeterminate();
-            
+
             markup = source.getMarkup();
             return null;
         }
@@ -611,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);
 
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);