This commit is contained in:
adam-m 2012-07-19 16:01:35 -04:00
commit 93d7340e3e
6 changed files with 351 additions and 72 deletions

View File

@ -6,6 +6,15 @@
<code-name-base>org.sleuthkit.autopsy.datamodel</code-name-base>
<suite-component/>
<module-dependencies>
<dependency>
<code-name-base>org.netbeans.api.progress</code-name-base>
<build-prerequisite/>
<compile-dependency/>
<run-dependency>
<release-version>1</release-version>
<specification-version>1.28.1</specification-version>
</run-dependency>
</dependency>
<dependency>
<code-name-base>org.openide.awt</code-name-base>
<build-prerequisite/>

View File

@ -28,6 +28,8 @@ import java.util.List;
import java.util.TimeZone;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.SwingWorker;
import org.netbeans.api.progress.ProgressHandle;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentVisitor;
import org.sleuthkit.datamodel.Directory;
@ -214,25 +216,47 @@ public final class ContentUtils {
* it does
* @throws IOException
*/
public static void writeToFile(Content content, java.io.File outputFile) throws IOException {
public static void writeToFile(Content content, java.io.File outputFile, ProgressHandle progress, SwingWorker worker, boolean source) throws IOException {
InputStream in = new ReadContentInputStream(content);
boolean append = false;
FileOutputStream out = new FileOutputStream(outputFile, append);
// Get the unit size for a progress bar
int unit = (int) (content.getSize() / 100);
long totalRead = 0;
try {
byte[] buffer = new byte[TO_FILE_BUFFER_SIZE];
int len = in.read(buffer);
while (len != -1) {
// If there is a worker, check for a cancelation
if (worker!=null && worker.isCancelled()) {
break;
}
out.write(buffer, 0, len);
len = in.read(buffer);
totalRead+=len;
// If there is a progress bar and this is the source file,
// report any progress
if(progress!=null && source) {
int totalProgress = (int) (totalRead / unit);
progress.progress(content.getName(), totalProgress);
// If it's not the source, just update the file being processed
} else if(progress!=null && !source) {
progress.progress(content.getName());
}
}
} finally {
out.close();
}
}
public static void writeToFile(Content content, java.io.File outputFile) throws IOException {
writeToFile(content, outputFile, null, null, false);
}
/**
* Helper to ignore the '.' and '..' directories
*/
@ -250,11 +274,21 @@ public final class ContentUtils {
public static class ExtractFscContentVisitor extends ContentVisitor.Default<Void> {
java.io.File dest;
ProgressHandle progress;
SwingWorker worker;
boolean source = false;
/**
* Make new extractor for a specific destination
* @param dest The file/folder visited will be extracted as this file
*/
public ExtractFscContentVisitor(java.io.File dest, ProgressHandle progress, SwingWorker worker, boolean source) {
this.dest = dest;
this.progress = progress;
this.worker = worker;
this.source = source;
}
public ExtractFscContentVisitor(java.io.File dest) {
this.dest = dest;
}
@ -263,13 +297,13 @@ public final class ContentUtils {
* Convenience method to make a new instance for given destination
* and extract given content
*/
public static void extract(Content cntnt, java.io.File dest) {
cntnt.accept(new ExtractFscContentVisitor(dest));
public static void extract(Content cntnt, java.io.File dest, ProgressHandle progress, SwingWorker worker) {
cntnt.accept(new ExtractFscContentVisitor(dest, progress, worker, true));
}
public Void visit(File f) {
try {
ContentUtils.writeToFile(f, dest);
ContentUtils.writeToFile(f, dest, progress, worker, source);
} catch (IOException ex) {
logger.log(Level.SEVERE,
"Trouble extracting file to " + dest.getAbsolutePath(),
@ -292,12 +326,23 @@ public final class ContentUtils {
DestFileContentVisitor destFileCV = new DestFileContentVisitor();
try {
int numProcessed = 0;
// recurse on children
for (Content child : dir.getChildren()) {
java.io.File childFile = child.accept(destFileCV);
ExtractFscContentVisitor childVisitor =
new ExtractFscContentVisitor(childFile);
new ExtractFscContentVisitor(childFile, progress, worker, false);
// If this is the source directory of an extract it
// will have a progress and worker, and will keep track
// of the progress bar's progress
if(worker!=null && worker.isCancelled()) {
break;
}
if(progress!=null && source) {
progress.progress(child.getName(), numProcessed);
}
child.accept(childVisitor);
numProcessed++;
}
} catch (TskException ex) {
logger.log(Level.SEVERE,

View File

@ -6,6 +6,15 @@
<code-name-base>org.sleuthkit.autopsy.directorytree</code-name-base>
<suite-component/>
<module-dependencies>
<dependency>
<code-name-base>org.netbeans.api.progress</code-name-base>
<build-prerequisite/>
<compile-dependency/>
<run-dependency>
<release-version>1</release-version>
<specification-version>1.28.1</specification-version>
</run-dependency>
</dependency>
<dependency>
<code-name-base>org.netbeans.modules.settings</code-name-base>
<build-prerequisite/>

View File

@ -18,17 +18,23 @@
*/
package org.sleuthkit.autopsy.directorytree;
import java.awt.event.ActionEvent;
import javax.swing.JFileChooser;
import java.io.File;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.io.File;
import java.util.concurrent.CancellationException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.AbstractAction;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.SwingWorker;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
import org.openide.nodes.Node;
import org.openide.util.Cancellable;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.autopsy.datamodel.ContentUtils.ExtractFscContentVisitor;
import org.sleuthkit.autopsy.coreutils.Log;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentVisitor;
import org.sleuthkit.datamodel.Directory;
@ -41,6 +47,7 @@ public final class ExtractAction extends AbstractAction {
private static final InitializeContentVisitor initializeCV = new InitializeContentVisitor();
private FsContent fsContent;
private Logger logger = Logger.getLogger(ExtractAction.class.getName());
public ExtractAction(String title, Node contentNode) {
super(title);
@ -78,8 +85,7 @@ public final class ExtractAction extends AbstractAction {
*/
@Override
public void actionPerformed(ActionEvent e) {
Log.noteAction(this.getClass());
// Get file and check that it's okay to overwrite existing file
JFileChooser fc = new JFileChooser();
fc.setCurrentDirectory(new File(Case.getCurrentCase().getCaseDirectory()));
fc.setSelectedFile(new File(this.fsContent.getName()));
@ -88,7 +94,7 @@ public final class ExtractAction extends AbstractAction {
if (returnValue == JFileChooser.APPROVE_OPTION) {
File destination = fc.getSelectedFile();
// check that it's okay to overwrite existing file
// do the check
if (destination.exists()) {
int choice = JOptionPane.showConfirmDialog(
(Component) e.getSource(),
@ -97,7 +103,7 @@ public final class ExtractAction extends AbstractAction {
JOptionPane.OK_CANCEL_OPTION);
if (choice != JOptionPane.OK_OPTION) {
return;
return; // Just exit the function
}
if (!destination.delete()) {
@ -107,12 +113,133 @@ public final class ExtractAction extends AbstractAction {
}
}
ExtractFscContentVisitor.extract(fsContent, destination);
if(fsContent.isDir())
try {
ExtractFileThread extract = new ExtractFileThread();
extract.init(this.fsContent, e, destination);
extract.execute();
} catch (Exception ex) {
logger.log(Level.WARNING, "Unable to start background thread.", ex);
}
}
}
private class ExtractFileThread extends SwingWorker<Object,Void> {
private Logger logger = Logger.getLogger(ExtractFileThread.class.getName());
private ProgressHandle progress;
private FsContent fsContent;
ActionEvent e;
File destination;
private void init(FsContent fsContent, ActionEvent e, File destination) {
this.fsContent = fsContent;
this.e = e;
this.destination = destination;
}
@Override
protected Object doInBackground() throws Exception {
logger.log(Level.INFO, "Starting background processing for file extraction.");
// Setup progress bar
final String displayName = "Extracting";
progress = ProgressHandleFactory.createHandle(displayName, new Cancellable() {
@Override
public boolean cancel() {
if (progress != null)
progress.setDisplayName(displayName + " (Cancelling...)");
return ExtractAction.ExtractFileThread.this.cancel(true);
}
});
// Start the progress bar as indeterminate
progress.start();
progress.switchToIndeterminate();
if(fsContent.isFile()) {
// Max file size of 200GB
long filesize = fsContent.getSize();
int unit = (int) (filesize / 100);
progress.switchToDeterminate(100);
} else if(fsContent.isDir()) {
// If dir base progress off number of children
int toProcess = fsContent.getChildren().size();
progress.switchToDeterminate(toProcess);
}
// Start extracting the file/directory
ExtractFscContentVisitor.extract(fsContent, destination, progress, this);
logger.log(Level.INFO, "Done background processing");
return null;
}
@Override
protected void done() {
try {
super.get(); //block and get all exceptions thrown while doInBackground()
} catch (CancellationException ex) {
logger.log(Level.INFO, "Extraction was canceled.");
} catch (InterruptedException ex) {
logger.log(Level.INFO, "Extraction was interrupted.");
} catch (Exception ex) {
logger.log(Level.SEVERE, "Fatal error during file extraction.", ex);
} finally {
progress.finish();
if (!this.isCancelled()) {
logger.log(Level.INFO, "Extracting completed without cancellation.");
// Alert the user extraction is over
if(fsContent.isDir()) {
JOptionPane.showMessageDialog((Component) e.getSource(), "Directory extracted.");
else if(fsContent.isFile()){
} else if(fsContent.isFile()){
JOptionPane.showMessageDialog((Component) e.getSource(), "File extracted.");
}
} else {
logger.log(Level.INFO, "Attempting to delete file(s).");
if(delete(destination)) {
logger.log(Level.INFO, "Finished deletion sucessfully.");
} else {
logger.log(Level.WARNING, "Deletion attempt complete; not all files were sucessfully deleted.");
}
}
}
}
private boolean delete(File file) {
boolean sucess = true;
// If it's a file
if(file.isFile()) {
if(!file.delete()) {
sucess = false;
logger.log(Level.WARNING, "Failed to delete file {0}", file.getPath());
}
// If it's a directory
} else {
// If the dir is empty
if(file.list().length==0) {
if(!file.delete()) {
sucess = false;
logger.log(Level.WARNING, "Failed to delete the empty directory at {0}", file.getPath());
}
} else {
String files[] = file.list();
for(String s:files) {
File sub = new File(file, s);
sucess = delete(sub);
}
// Delete the newly-empty dir
if(file.list().length==0) {
if(!file.delete()) {
sucess = false;
logger.log(Level.WARNING, "Failed to delete the empty directory at {0}", file.getPath());
}
} else {
sucess = false;
logger.log(Level.WARNING, "Directory {0} did not recursivly delete sucessfully.", file.getPath());
}
}
}
return sucess;
}
}
}

13
Testing/script/config.xml Normal file
View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="ASCII"?>
<!-- This file is an example config file for regression.py.
the following tags are mandatory:
1 "indir" tag that specifies where the program can find the hash databases
any number of "image" tags that specify where the images are stored. They do not have to be local.
invalid paths and images are ignored by the tester.
in this file, the first three tags are accepted by the tester, while the last two are disregarded as errors-->
<Properties>
<indir name="indir" value="C:\Users\dhurd\Documents\GitHub\autopsy\Testing\script\input" />
<!--<image name="image1" value="P:\shared\Testing\script\input\64mb2.img"/>-->
</Properties>

View File

@ -1,4 +1,5 @@
#!/usr/bin/python
#en_US.UTF-8
import sys
import sqlite3
import re
@ -6,18 +7,24 @@ import subprocess
import os.path
import shutil
import time
import xml
from xml.dom.minidom import parse, parseString
# Last modified 7/13/12 3@ pm
# Usage: ./regression.py [-i FILE] [OPTIONS]
# Last modified 7/17/12 @5pm
# Usage: ./regression.py [-s FILE] OR [-l CONFIG] [OPTIONS]
# Run the RegressionTest.java file, and compare the result with a gold standard
# When the -i flag is set, this script only tests the image given by FILE.
# By default, it tests every image in ./input/
# An indexed NSRL database is expected at ./input/nsrl.txt-md5.idx,
# and an indexed notable hash database at ./input/notablehashes.txt-md5.idx
# In addition, any keywords to search for must be in ./input/notablekeywords.xml
# When the -l flag is set, the script looks for a config.xml file of the given name
# where images are stored. For usage notes please see the example "config.xml" in
# the /script folder.
# Options:
# -r, --rebuild Rebuild the gold standards from the test results for each image
# -u, --ignore
# -i, --ignore Ignores unallocated space when ingesting. Faster, but less accurate results.
hadErrors = False # If any of the tests failed
results = {} # Dictionary in which to store map ({imgname}->errors)
@ -28,14 +35,18 @@ outDir = os.path.join("output",time.strftime("%Y.%m.%d-%H.%M"))
# Run ingest on all the images in 'input', using notablekeywords.xml and notablehashes.txt-md5.idx
def testAddImageIngest(inFile, ignoreUnalloc):
def testAddImageIngest(inFile, ignoreUnalloc, list):
print "================================================"
print "Ingesting Image: " + inFile
# Set up case directory path
testCaseName = imageName(inFile)
#check for flags to append to folder name
if ignoreUnalloc:
testCaseName+="-u"
testCaseName+="-i"
if list:
testCaseName+="-l"
if os.path.exists(os.path.join(outDir,testCaseName)):
shutil.rmtree(os.path.join(outDir,testCaseName))
os.makedirs(os.path.join(outDir,testCaseName))
@ -44,20 +55,27 @@ def testAddImageIngest(inFile, ignoreUnalloc):
cwd = wgetcwd()
testInFile = wabspath(inFile)
# NEEDS windows path (backslashes) for .E00 images to work
testInFile = testInFile.replace("/", "\\")
if list:
knownBadPath = os.path.join(inDir, "notablehashes.txt-md5.idx")
keywordPath = os.path.join(inDir, "notablekeywords.xml")
nsrlPath = os.path.join(inDir, "nsrl.txt-md5.idx")
else:
knownBadPath = os.path.join(cwd,inDir,"notablehashes.txt-md5.idx")
knownBadPath = knownBadPath.replace("/", "\\")
keywordPath = os.path.join(cwd,inDir,"notablekeywords.xml")
keywordPath = keywordPath.replace("/", "\\")
nsrlPath = os.path.join(cwd,inDir,"nsrl.txt-md5.idx")
knownBadPath = knownBadPath.replace("/", "\\")
keywordPath = keywordPath.replace("/", "\\")
nsrlPath = nsrlPath.replace("/", "\\")
antlog = os.path.join(cwd,outDir,testCaseName,"antlog.txt")
antlog = antlog.replace("/", "\\")
timeout = 24 * 60 * 60 * 1000 # default of 24 hours, just to be safe
size = getImageSize(inFile) # get the size in bytes
size = getImageSize(inFile, list) # get the size in bytes
timeout = (size / 1000) / 1000 # convert to MB
timeout = timeout * 1000 # convert sec to ms
timeout = timeout * 1.5 # add a little extra umph
@ -80,7 +98,6 @@ def testAddImageIngest(inFile, ignoreUnalloc):
args.append("-Dignore_unalloc=" + "%s" % ignoreUnalloc)
args.append("-Dtest.timeout=" + str(timeout))
# print the ant testing command
print "CMD: " + " ".join(args)
@ -90,10 +107,14 @@ def testAddImageIngest(inFile, ignoreUnalloc):
#fnull.close();
subprocess.call(args)
def getImageSize(inFile):
def getImageSize(inFile, list):
name = imageName(inFile)
path = os.path.join(".",inDir)
size = 0
if list:
size += os.path.getsize(inFile)
else:
path = os.path.join(".",inDir)
for files in os.listdir(path):
filename = os.path.splitext(files)[0]
if filename == name:
@ -109,7 +130,7 @@ def testCompareToGold(inFile, ignore):
name = imageName(inFile)
if ignore:
name += ("-u")
name += ("-i")
cwd = wgetcwd()
goldFile = os.path.join("./",goldDir,name,"standard.db")
testFile = os.path.join("./",outDir,name,"AutopsyTestCase","autopsy.db")
@ -166,32 +187,38 @@ def testCompareToGold(inFile, ignore):
else:
print("Object counts match!")
def clearGoldDir(inFile, ignore):
def clearGoldDir(inFile, ignore, list):
cwd = wgetcwd()
inFile = imageName(inFile)
if ignore:
inFile += "-u"
inFile += "-i"
if list:
inFile += "-l"
if os.path.exists(os.path.join(cwd,goldDir,inFile)):
shutil.rmtree(os.path.join(cwd,goldDir,inFile))
os.makedirs(os.path.join(cwd,goldDir,inFile))
def copyTestToGold(inFile, ignore):
def copyTestToGold(inFile, ignore, list):
print "------------------------------------------------"
print "Recreating gold standard from results."
inFile = imageName(inFile)
if ignore:
inFile += "-u"
inFile += "-i"
if list:
inFile += "-l"
cwd = wgetcwd()
goldFile = os.path.join("./",goldDir,inFile,"standard.db")
testFile = os.path.join("./",outDir,inFile,"AutopsyTestCase","autopsy.db")
shutil.copy(testFile, goldFile)
def copyReportToGold(inFile, ignore):
def copyReportToGold(inFile, ignore, list):
print "------------------------------------------------"
print "Recreating gold report from results."
inFile = imageName(inFile)
if ignore:
inFile += "-u"
inFile += "-i"
if list:
inFile += "-l"
cwd = wgetcwd()
goldReport = os.path.join("./",goldDir,inFile,"report.html")
testReportPath = os.path.join("./",outDir,inFile,"AutopsyTestCase","Reports")
@ -208,12 +235,14 @@ def copyReportToGold(inFile, ignore):
else:
shutil.copy(testReport, goldReport)
def testCompareReports(inFile, ignore):
def testCompareReports(inFile, ignore, list):
print "------------------------------------------------"
print "Comparing report to golden report."
name = imageName(inFile)
if ignore:
name += "-u"
name += "-i"
if list:
name += "-l"
goldReport = os.path.join("./",goldDir,name,"report.html")
testReportPath = os.path.join("./",outDir,name,"AutopsyTestCase","Reports")
# Because Java adds a timestamp to the report file, one can't call it
@ -276,15 +305,20 @@ def imageType(inFile):
def imageName(inFile):
pathEnd = inFile.rfind("/")
pathEnd2 = inFile.rfind("\\")
extStart = inFile.rfind(".")
if(extStart == -1 and extStart == -1):
return inFile
if(pathEnd2 != -1):
return inFile[pathEnd2+1:extStart]
elif(extStart == -1):
return inFile[pathEnd+1:]
elif(pathEnd == -1):
return inFile[:extStart]
else:
elif(pathEnd!=-1 and extStart!=-1):
return inFile[pathEnd+1:extStart]
else:
return inFile[pathEnd2+1:extStart]
def markError(errString, inFile):
global hadErrors
@ -300,41 +334,47 @@ def wgetcwd():
return out.rstrip()
def wabspath(inFile):
if(inFile[1:2] == ":"):
proc = subprocess.Popen(("cygpath", "-m", inFile), stdout=subprocess.PIPE)
out,err = proc.communicate()
else:
proc = subprocess.Popen(("cygpath", "-m", os.path.abspath(inFile)), stdout=subprocess.PIPE)
out,err = proc.communicate()
return out.rstrip()
def copyLogs(inFile, ignore):
def copyLogs(inFile, ignore, list):
name = imageName(inFile)
if ignore:
name +="-u"
name +="-i"
if list:
name+="-l"
logDir = os.path.join("..","build","test","qa-functional","work","userdir0","var","log")
shutil.copytree(logDir,os.path.join(outDir,name,"logs"))
def testFile(image, rebuild, ignore):
def testFile(image, rebuild, ignore, list):
if imageType(image) != ImgType.UNKNOWN:
if ignore:
testAddImageIngest(image, True)
else:
testAddImageIngest(image, False)
copyLogs(image, ignore)
testAddImageIngest(image, ignore, list)
copyLogs(image, ignore, list)
if rebuild:
clearGoldDir(image, ignore)
copyTestToGold(image, ignore)
copyReportToGold(image, ignore)
clearGoldDir(image, ignore, list)
copyTestToGold(image, ignore, list)
copyReportToGold(image, ignore, list)
else:
testCompareToGold(image, ignore)
testCompareReports(image, ignore)
testCompareToGold(image, ignore, list)
testCompareReports(image, ignore, list)
def usage() :
def usage():
usage = "\
Usage: ./regression.py [-i FILE] [OPTIONS] \n\n\
Usage: ./regression.py [-s FILE] [OPTIONS] \n\n\
Run the RegressionTest.java file, and compare the result with a gold standard \n\n\
When the -i flag is set, this script only tests the image given by FILE.\n\
By default, it tests every image in ./input/\n\n\
An indexed NSRL database is expected at ./input/nsrl.txt-md5.idx,\n\
and an indexed notable hash database at ./input/notablehashes.txt-md5.idx\n\
In addition, any keywords to search for must be in ./input/notablekeywords.xml\n\n\
When the -l flag is set, the script looks for a config.xml file of the given name\n\
where images are stored. For usage notes please see the example config.xml in\n\
the /script folder.\
Options:\n\n\
-r, --rebuild\t\tRebuild the gold standards from the test results for each image\n\n\
-u, --ignore\t\tIgnore unallocated space while ingesting"
@ -344,19 +384,36 @@ def main():
rebuild = False
single = False
ignore = False
list = False
test = True
argi = 1
Config = None #file pointed to by --list
imgListB = [] #list of legal images from config
cwd = wgetcwd()
while argi < len(sys.argv):
arg = sys.argv[argi]
if arg == "-i" and argi+1 < len(sys.argv):
if arg == "-s" and argi+1 < len(sys.argv): #check for single
single = True
argi+=1
image = sys.argv[argi]
print "Running on single image: " + image
elif (arg == "--rebuild") or (arg == "-r"):
if arg == "-l" or arg == "--list": #check for config file
list = True
argi+=1
#check for file in ./
if(os.path.isfile(os.path.join("./", sys.argv[argi]))):
Config = parse(os.path.join("./", sys.argv[argi]))
#else check if it is a specified path
elif (os.path.exists(wabspath(sys.argv[argi]))):
Config = parse(sys.argv[argi])
else:
print sys.argv[argi]
print wabspath(sys.argv[argi])
markError("Ran with " + arg +" but no such file exists", arg)
elif (arg == "--rebuild") or (arg == "-r"): #check for rebuild flag
rebuild = True
print "Running in REBUILD mode"
elif (arg == "--ignore") or (arg == "-u"):
elif (arg == "--ignore") or (arg == "-i"): #check for ignore flag
ignore = True
print "Ignoring unallocated space"
else:
@ -365,9 +422,28 @@ def main():
argi+=1
if single:
testFile(image, rebuild, ignore)
if list:
listImages = []
errors = 0
global inDir
out = Config.getElementsByTagName("indir")[0].getAttribute("value").encode() #there should only be one indir element in the config
inDir = out
for element in Config.getElementsByTagName("image"):
elem = element.getAttribute("value").encode()
proc2 = subprocess.Popen(("cygpath", "-i", elem), stdout=subprocess.PIPE)
out2,err = proc2.communicate()
out2 = out2.rstrip()
if(os.path.exists(out2) and os.path.isfile(out2)):
listImages.append(elem)
else:
print out2 + " is not a valid path or is not an image"
errors+=1
print "Illegal files specified: " + str(errors)
for image in listImages:
testFile(image, rebuild, ignore, list)
elif test:
for inFile in os.listdir(inDir):
testFile(os.path.join(inDir,inFile), rebuild, ignore)
testFile(os.path.join(inDir,inFile), rebuild, ignore, list)
if hadErrors == True:
print "**********************************************"