This commit is contained in:
Smoss 2013-03-06 11:17:47 -05:00
commit c8870c1bb7
16 changed files with 688 additions and 1493 deletions

View File

@ -1,23 +1,45 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
* Autopsy Forensic Browser
*
* Copyright 2013 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.datamodel;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.swing.Action;
import org.openide.nodes.Sheet;
import org.openide.util.Exceptions;
import org.sleuthkit.autopsy.directorytree.ExternalViewerAction;
import org.sleuthkit.autopsy.directorytree.ExtractAction;
import org.sleuthkit.autopsy.directorytree.HashSearchAction;
import org.sleuthkit.autopsy.directorytree.NewWindowViewAction;
import org.sleuthkit.autopsy.directorytree.TagFileAction;
import org.sleuthkit.datamodel.DerivedFile;
import org.sleuthkit.datamodel.LayoutFile;
import org.sleuthkit.datamodel.TskCoreException;
/**
* A Node for a DerivedFile content object.
*
* TODO should be able to extend FileNode after FileNode extends AbstractFsContentNode<AbstractFile>
*
* TODO should be able to extend FileNode after FileNode extends
* AbstractFsContentNode<AbstractFile>
*/
public class DerivedFileNode extends AbstractAbstractFileNode<DerivedFile> {
public class DerivedFileNode extends AbstractAbstractFileNode<DerivedFile> {
public static String nameForLayoutFile(LayoutFile lf) {
return lf.getName();
@ -27,14 +49,14 @@ public class DerivedFileNode extends AbstractAbstractFileNode<DerivedFile> {
super(df);
this.setDisplayName(df.getName());
// set name, display name, and icon
// set name, display name, and icon
if (df.isDir()) {
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/Folder-icon.png");
} else {
this.setIconBaseWithExtension(FileNode.getIconForFileType(df));
}
}
@Override
@ -65,6 +87,21 @@ public class DerivedFileNode extends AbstractAbstractFileNode<DerivedFile> {
return s;
}
@Override
public Action[] getActions(boolean context) {
List<Action> actionsList = new ArrayList<Action>();
actionsList.add(new NewWindowViewAction("View in New Window", this));
actionsList.add(new ExternalViewerAction("Open in External Viewer", this));
actionsList.add(null); // creates a menu separator
actionsList.add(new ExtractAction("Extract", content)); //might not need this actions - already local file
actionsList.add(new HashSearchAction("Search for files with the same MD5 hash", this));
actionsList.add(null); // creates a menu separator
actionsList.add(new TagFileAction(content));
return actionsList.toArray(new Action[0]);
}
@Override
public <T> T accept(ContentNodeVisitor<T> v) {
return v.visit(this);
@ -79,8 +116,4 @@ public class DerivedFileNode extends AbstractAbstractFileNode<DerivedFile> {
public boolean isLeafTypeNode() {
return true; //!this.hasContentChildren();
}
}

View File

@ -18,8 +18,13 @@
*/
package org.sleuthkit.autopsy.datamodel;
import java.util.ArrayList;
import java.util.List;
import javax.swing.Action;
import org.openide.nodes.Sheet;
import org.sleuthkit.autopsy.directorytree.ExplorerNodeActionVisitor;
import org.sleuthkit.autopsy.directorytree.FileSearchAction;
import org.sleuthkit.autopsy.directorytree.NewWindowViewAction;
import org.sleuthkit.datamodel.Image;
/**
@ -64,9 +69,13 @@ public class ImageNode extends AbstractContentNode<Image> {
*/
@Override
public Action[] getActions(boolean context) {
return new Action[]{ // SystemAction.get( NewAction.class ),
// SystemAction.get( PasteAction.class )
};
List<Action> actionsList = new ArrayList<Action>();
actionsList.add(new NewWindowViewAction("View in New Window", this));
actionsList.add(new FileSearchAction("Open File Search by Attributes"));
actionsList.addAll(ExplorerNodeActionVisitor.getActions(content));
return actionsList.toArray(new Action[0]);
}
@Override

View File

@ -18,9 +18,17 @@
*/
package org.sleuthkit.autopsy.datamodel;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.swing.Action;
import org.openide.nodes.Sheet;
import org.sleuthkit.autopsy.directorytree.ExplorerNodeActionVisitor;
import org.sleuthkit.autopsy.directorytree.ExternalViewerAction;
import org.sleuthkit.autopsy.directorytree.ExtractAction;
import org.sleuthkit.autopsy.directorytree.NewWindowViewAction;
import org.sleuthkit.autopsy.directorytree.TagFileAction;
import org.sleuthkit.datamodel.LayoutFile;
/**
@ -87,6 +95,20 @@ public class LayoutFileNode extends AbstractAbstractFileNode<LayoutFile> {
return v.visit(this);
}
@Override
public Action[] getActions(boolean context) {
List<Action> actionsList = new ArrayList<Action>();
actionsList.add(new NewWindowViewAction("View in New Window", this));
actionsList.add(new ExternalViewerAction("Open in External Viewer", this));
actionsList.add(null); // creates a menu separator
actionsList.add(new ExtractAction("Extract File", content));
actionsList.add(null); // creates a menu separator
actionsList.add(new TagFileAction(content));
return actionsList.toArray(new Action[0]);
}
private static void fillPropertyMap(Map<String, Object> map, LayoutFile content) {
AbstractAbstractFileNode.fillPropertyMap(map, content);
map.put(LayoutContentPropertyType.PARTS.toString(), content.getNumParts());

View File

@ -18,9 +18,12 @@
*/
package org.sleuthkit.autopsy.datamodel;
import java.util.ArrayList;
import java.util.List;
import javax.swing.Action;
import org.openide.nodes.Sheet;
import org.sleuthkit.autopsy.directorytree.ExtractUnallocAction;
import org.sleuthkit.autopsy.directorytree.ExplorerNodeActionVisitor;
import org.sleuthkit.autopsy.directorytree.NewWindowViewAction;
import org.sleuthkit.datamodel.Volume;
/**
@ -65,10 +68,12 @@ public class VolumeNode extends AbstractContentNode<Volume> {
*/
@Override
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)
};
List<Action> actionsList = new ArrayList<Action>();
actionsList.add(new NewWindowViewAction("View in New Window", this));
actionsList.addAll(ExplorerNodeActionVisitor.getActions(content));
return actionsList.toArray(new Action[0]);
}
@Override

View File

@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.directorytree;
import java.awt.event.ActionEvent;
import java.beans.PropertyVetoException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.sleuthkit.autopsy.datamodel.VolumeNode;
import org.sleuthkit.autopsy.datamodel.DirectoryNode;
@ -147,111 +148,11 @@ public class DataResultFilterNode extends FilterNode {
return propertySets;
}
/**
* Uses the default nodes actions per node, adds some custom ones and returns them per visited node type
*/
private static class GetPopupActionsDisplayableItemNodeVisitor extends DisplayableItemNodeVisitor.Default<List<Action>> {
@Override
public List<Action> visit(ImageNode img) {
List<Action> actions = new ArrayList<Action>();
//retain actions from the node if any
for (Action a : img.getActions(true)) {
actions.add(a);
}
actions.add(new NewWindowViewAction("View in New Window", img));
actions.add(new FileSearchAction("Open File Search by Attributes"));
actions.addAll(ExplorerNodeActionVisitor.getActions(img.getLookup().lookup(Content.class)));
return actions;
}
@Override
public List<Action> visit(VolumeNode vol) {
List<Action> actions = new ArrayList<Action>();
//retain actions from the node if any
for (Action a : vol.getActions(true)) {
actions.add(a);
}
actions.add(new NewWindowViewAction("View in New Window", vol));
actions.addAll(ExplorerNodeActionVisitor.getActions(vol.getLookup().lookup(Content.class)));
return actions;
}
@Override
public List<Action> visit(DirectoryNode dir) {
//preserve the default node's actions
List<Action> actions = new ArrayList<Action>();
for (Action action : dir.getActions(true)) {
actions.add(action);
}
return actions;
}
@Override
public List<Action> visit(LayoutFileNode lf) {
List<Action> actions = new ArrayList<Action>();
//retain actions from the node if any
for (Action a : lf.getActions(true)) {
actions.add(a);
}
actions.add(new NewWindowViewAction("View in New Window", lf));
actions.add(new ExternalViewerAction("Open in External Viewer", lf));
actions.add(null); // creates a menu separator
actions.add(new ExtractAction("Extract File", lf));
actions.add(null); // creates a menu separator
actions.add(new TagFileAction(lf));
return actions;
}
@Override
public List<Action> visit(VirtualDirectoryNode ld) {
List<Action> actions = new ArrayList<Action>();
//retain actions from the node if any
for (Action a : ld.getActions(true)) {
actions.add(a);
}
actions.add(new TagFileAction(ld));
return actions;
}
@Override
public List<Action> visit(DerivedFileNode dfn) {
List<Action> actions = new ArrayList<Action>();
//retain actions from the node if any
for (Action a : dfn.getActions(true)) {
actions.add(a);
}
actions.add(new NewWindowViewAction("View in New Window", dfn));
actions.add(new ExternalViewerAction("Open in External Viewer", dfn));
actions.add(null); // creates a menu separator
actions.add(new ExtractAction("Extract", dfn)); //might not need this actions - already local file
actions.add(new HashSearchAction("Search for files with the same MD5 hash", dfn));
actions.add(null); // creates a menu separator
actions.add(new TagFileAction(dfn));
return actions;
}
@Override
public List<Action> visit(FileNode f) {
//preserve the default node's actions
List<Action> actions = new ArrayList<Action>();
for (Action action : f.getActions(true)) {
actions.add(action);
}
return actions;
}
@Override
public List<Action> visit(BlackboardArtifactNode ban) {
@ -322,39 +223,22 @@ public class DataResultFilterNode extends FilterNode {
actions.add(new TagResultAction(ba));
}
}
//if (artifactTypeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) {
//actions.add(null); // creates a menu separator
//actions.add(new ResultDeleteAction("Delete Result", ba));
//}
return actions;
}
@Override
public List<Action> visit(KeywordHitsRootNode khrn) {
//List<Action> actions = new ArrayList<Action>();
//actions.add(null); // creates a menu separator
//actions.add(new ResultDeleteAction("Delete Results", BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT));
//return actions;
return super.visit(khrn);
}
@Override
public List<Action> visit(KeywordHitsListNode khsn) {
//TODO delete by list
return super.visit(khsn);
}
@Override
public List<Action> visit(KeywordHitsKeywordNode khmln) {
//TODO delete by keyword hit
return super.visit(khmln);
}
@Override
protected List<Action> defaultVisit(DisplayableItemNode ditem) {
return new ArrayList<Action>();
//preserve the default node's actions
List<Action> actions = new ArrayList<Action>();
for (Action action : ditem.getActions(true)) {
actions.add(action);
}
return actions;
}
private Content findLinked(BlackboardArtifactNode ba) {

View File

@ -80,7 +80,6 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
private transient ExplorerManager em = new ExplorerManager();
private static DirectoryTreeTopComponent instance;
private DataResultTopComponent dataResult = new DataResultTopComponent(true, "Directory Listing");
private LinkedList<String[]> backList;
private LinkedList<String[]> forwardList;
/**
@ -567,100 +566,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
// }
// change in node selection
else if (changed.equals(ExplorerManager.PROP_SELECTED_NODES)) {
if (getSelectedNode() == null && oldValue != null) {
try {
em.setSelectedNodes((Node[]) oldValue);
} catch (PropertyVetoException ex) {
logger.log(Level.WARNING, "Error resetting node", ex);
}
}
final Node[] oldNodes = (Node[]) oldValue;
final Node[] newNodes = (Node[]) newValue;
// Some lock that prevents certain Node operations is set during the
// ExplorerManager selection-change, so we must handle changes after the
// selection-change event is processed.
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
// change the cursor to "waiting cursor" for this operation
DirectoryTreeTopComponent.this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try {
// make sure dataResult is open
dataResult.open();
Node treeNode = DirectoryTreeTopComponent.this.getSelectedNode();
if (treeNode != null) {
OriginalNode origin = treeNode.getLookup().lookup(OriginalNode.class);
if (origin == null) {
return;
}
Node originNode = origin.getNode();
DirectoryTreeTopComponent.this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
//set node, wrap in filter node first to filter out children
Node drfn = new DataResultFilterNode(originNode, DirectoryTreeTopComponent.this.em);
DirectoryTreeTopComponent.this.dataResult.setNode(new TableFilterNode(drfn, true));
String displayName = "";
if (originNode.getLookup().lookup(Content.class) != null) {
Content content = originNode.getLookup().lookup(Content.class);
if (content != null) {
try {
displayName = content.getUniquePath();
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Exception while calling Content.getUniquePath() for node: " + originNode);
}
}
} else if (originNode.getLookup().lookup(String.class) != null) {
displayName = originNode.getLookup().lookup(String.class);
}
DirectoryTreeTopComponent.this.dataResult.setPath(displayName);
}
// set the directory listing to be active
if (oldNodes != null && newNodes != null
&& (oldNodes.length == newNodes.length)) {
boolean sameNodes = true;
for (int i = 0; i < oldNodes.length; i++) {
sameNodes = sameNodes && oldNodes[i].getName().equals(newNodes[i].getName());
}
if (!sameNodes) {
dataResult.requestActive();
}
}
} finally {
DirectoryTreeTopComponent.this.setCursor(null);
}
}
});
// update the back and forward list
Node[] selectedNode = em.getSelectedNodes();
if (selectedNode.length > 0 ) {
Node selectedContext = selectedNode[0];
final String[] selectedPath = NodeOp.createPath(selectedContext, em.getRootContext());
String[] currentLast = backList.peekLast();
String lastNodeName = null;
if (currentLast != null) {
lastNodeName = currentLast[currentLast.length-1];
}
String selectedNodeName = selectedContext.getName();
if (currentLast == null || !selectedNodeName.equals(lastNodeName)) {
//add to the list if the last if not the same as current
backList.addLast(selectedPath); // add the node to the "backList"
if (backList.size() > 1) {
backButton.setEnabled(true);
} else {
backButton.setEnabled(false);
}
forwardList.clear(); // clear the "forwardList"
forwardButton.setEnabled(false); // disable the forward Button
}
}
respondSelection((Node[]) oldValue, (Node[]) newValue);
} else if (changed.equals(IngestModuleEvent.DATA.toString())) {
final ModuleDataEvent event = (ModuleDataEvent) oldValue;
SwingUtilities.invokeLater(new Runnable() {
@ -687,6 +593,114 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
}
}
/**
* Event handler to run when selection changed
*
* TODO this needs to be revised
*
* @param oldNodes
* @param newNodes
*/
private void respondSelection(final Node[] oldNodes, final Node[] newNodes) {
//this looks redundant?
// if (getSelectedNode() == null && oldNodes != null) {
// try {
// em.setSelectedNodes(oldNodes);
// } catch (PropertyVetoException ex) {
// logger.log(Level.WARNING, "Error resetting node", ex);
// }
// }
// Some lock that prevents certain Node operations is set during the
// ExplorerManager selection-change, so we must handle changes after the
// selection-change event is processed.
//TODO find a different way to refresh data result viewer, scheduling this
//to EDT breaks loading of nodes in the background
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
// change the cursor to "waiting cursor" for this operation
DirectoryTreeTopComponent.this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try {
// make sure dataResult is open, redundant?
//dataResult.open();
Node treeNode = DirectoryTreeTopComponent.this.getSelectedNode();
if (treeNode != null) {
OriginalNode origin = treeNode.getLookup().lookup(OriginalNode.class);
if (origin == null) {
return;
}
Node originNode = origin.getNode();
DirectoryTreeTopComponent.this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
//set node, wrap in filter node first to filter out children
Node drfn = new DataResultFilterNode(originNode, DirectoryTreeTopComponent.this.em);
DirectoryTreeTopComponent.this.dataResult.setNode(new TableFilterNode(drfn, true));
String displayName = "";
if (originNode.getLookup().lookup(Content.class) != null) {
Content content = originNode.getLookup().lookup(Content.class);
if (content != null) {
try {
displayName = content.getUniquePath();
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Exception while calling Content.getUniquePath() for node: " + originNode);
}
}
} else if (originNode.getLookup().lookup(String.class) != null) {
displayName = originNode.getLookup().lookup(String.class);
}
DirectoryTreeTopComponent.this.dataResult.setPath(displayName);
}
// set the directory listing to be active
if (oldNodes != null && newNodes != null
&& (oldNodes.length == newNodes.length)) {
boolean sameNodes = true;
for (int i = 0; i < oldNodes.length; i++) {
sameNodes = sameNodes && oldNodes[i].getName().equals(newNodes[i].getName());
}
if (!sameNodes) {
dataResult.requestActive();
}
}
} finally {
DirectoryTreeTopComponent.this.setCursor(null);
}
}
});
// update the back and forward list
Node[] selectedNode = em.getSelectedNodes();
if (selectedNode.length > 0) {
Node selectedContext = selectedNode[0];
final String[] selectedPath = NodeOp.createPath(selectedContext, em.getRootContext());
String[] currentLast = backList.peekLast();
String lastNodeName = null;
if (currentLast != null) {
lastNodeName = currentLast[currentLast.length - 1];
}
String selectedNodeName = selectedContext.getName();
if (currentLast == null || !selectedNodeName.equals(lastNodeName)) {
//add to the list if the last if not the same as current
backList.addLast(selectedPath); // add the node to the "backList"
if (backList.size() > 1) {
backButton.setEnabled(true);
} else {
backButton.setEnabled(false);
}
forwardList.clear(); // clear the "forwardList"
forwardButton.setEnabled(false); // disable the forward Button
}
}
}
@Override
public synchronized void addPropertyChangeListener(PropertyChangeListener listener) {
pcs.addPropertyChangeListener(listener);
@ -739,8 +753,8 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
RootContentChildren contentRootChildren = (RootContentChildren) imagesNode.getChildren();
contentRootChildren.refreshContentKeys();
final TreeView tree = getTree();
tree.expandNode(imagesNode);
//final TreeView tree = getTree();
//tree.expandNode(imagesNode);
setSelectedNode(selectedPath, ImagesNode.NAME);
@ -751,6 +765,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
* be called in the gui thread
*/
void refreshTree(final BlackboardArtifact.ARTIFACT_TYPE... types) {
//save current selection
Node selectedNode = getSelectedNode();
final String[] selectedPath = NodeOp.createPath(selectedNode, em.getRootContext());
@ -791,6 +806,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
}
tree.expandNode(childNode);
//restores selection if it was under the Results node
setSelectedNode(selectedPath, ResultsNode.NAME);
}
@ -816,13 +832,13 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
Node newSelection = NodeOp.findPath(em.getRootContext(), path);
//resetHistoryListAndButtons();
if (newSelection != null) {
if (rootNodeName != null) {
if (rootNodeName != null) {
//called from tree auto refresh context
//remove last from backlist, because auto select will result in duplication
backList.pollLast();
}
//select
tree.expandNode(newSelection);
//tree.expandNode(newSelection);
em.setExploredContextAndSelection(newSelection, new Node[]{newSelection});
}
// We need to set the selection, which will refresh dataresult and get rid of the oob exception

View File

@ -45,7 +45,7 @@ import org.sleuthkit.datamodel.FileSystem;
import org.sleuthkit.datamodel.Image;
import org.sleuthkit.datamodel.Volume;
class ExplorerNodeActionVisitor extends ContentVisitor.Default<List<? extends Action>> {
public class ExplorerNodeActionVisitor extends ContentVisitor.Default<List<? extends Action>> {
private static ExplorerNodeActionVisitor instance = new ExplorerNodeActionVisitor();

View File

@ -31,6 +31,7 @@ import com.drew.metadata.exif.GpsDirectory;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
@ -70,6 +71,10 @@ public final class ExifParserFileIngestModule implements IngestModuleAbstractFil
private String args;
private static final int readHeaderSize = 2;
private final byte[] fileHeaderBuffer = new byte[readHeaderSize];
private static final char JPEG_SIGNATURE_BE = 0xFFD8;
private static final Logger logger = Logger.getLogger(ExifParserFileIngestModule.class.getName());
private static ExifParserFileIngestModule defaultInstance = null;
private static int messageId = 0;
@ -183,6 +188,12 @@ public final class ExifParserFileIngestModule implements IngestModuleAbstractFil
return IngestModuleAbstractFile.ProcessResult.ERROR;
}
/**
* Checks if should try to attempt to extract exif.
* Currently checks if JPEG image, first by extension, then by signature (if extension fails)
* @param f file to be checked
* @return true if to be processed
*/
private boolean parsableFormat(AbstractFile f) {
// Get the name, extension
String name = f.getName();
@ -195,8 +206,38 @@ public final class ExifParserFileIngestModule implements IngestModuleAbstractFil
return true;
}
return false;
return isJpegFileHeader(f);
}
/**
* Check if is jpeg file based on header
* @param file
* @return true if jpeg file, false otherwise
*/
private boolean isJpegFileHeader(AbstractFile file) {
if (file.getSize() < readHeaderSize) {
return false;
}
int bytesRead = 0;
try {
bytesRead = file.read(fileHeaderBuffer, 0, readHeaderSize);
} catch (TskCoreException ex) {
//ignore if can't read the first few bytes, not a JPEG
return false;
}
if (bytesRead != readHeaderSize) {
return false;
}
ByteBuffer bytes = ByteBuffer.wrap(fileHeaderBuffer);
char signature = bytes.getChar();
return signature == JPEG_SIGNATURE_BE;
}
@Override
public void complete() {

View File

@ -11,6 +11,8 @@ Improvements:
Bugfixes:
- show error message in hex and string viewer if specific offset of a file could not be read.
- file search actions not always enabled when new case is open.
- fixed directory tree history being reset when tree is refreshed.
- exif module better jpeg detection using signature and not only file extension.
---------------- VERSION 3.0.4 --------------

View File

@ -24,6 +24,7 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
@ -90,6 +91,11 @@ public final class SevenZipIngestModule implements IngestModuleAbstractFile {
private static final long MIN_FREE_DISK_SPACE = 1 * 1000 * 1000000L; //1GB
//counts archive depth
private ArchiveDepthCountTree archiveDepthCountTree;
//buffer for checking file headers and signatures
private static final int readHeaderSize = 4;
private final byte[] fileHeaderBuffer = new byte[readHeaderSize];
private static final int ZIP_SIGNATURE_BE = 0x504B0304;
//private constructor to ensure singleton instance
private SevenZipIngestModule() {
@ -617,8 +623,41 @@ public final class SevenZipIngestModule implements IngestModuleAbstractFile {
return true;
}
}
return false;
//if no extension match, check for zip signature
//(note, in near future, we will use pre-detected content type)
return isZipFileHeader(file);
}
/**
* Check if is zip file based on header
* @param file
* @return true if zip file, false otherwise
*/
private boolean isZipFileHeader(AbstractFile file) {
if (file.getSize() < readHeaderSize) {
return false;
}
int bytesRead = 0;
try {
bytesRead = file.read(fileHeaderBuffer, 0, readHeaderSize);
} catch (TskCoreException ex) {
//ignore if can't read the first few bytes, not a ZIP
return false;
}
if (bytesRead != readHeaderSize) {
return false;
}
ByteBuffer bytes = ByteBuffer.wrap(fileHeaderBuffer);
int signature = bytes.getInt();
return signature == ZIP_SIGNATURE_BE;
}
/**
* Stream used to unpack the archive to local file

View File

@ -1,28 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.4" maxVersion="1.8" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<EmptySpace min="0" pref="400" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<EmptySpace min="0" pref="300" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
</Layout>
</Form>

View File

@ -1,835 +0,0 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package org.sleuthkit.autopsy.timeline;
import com.sun.javafx.application.PlatformImpl;
import java.awt.Component;
import java.awt.Dimension;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.DateFormat;
import java.text.DateFormatSymbols;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Scanner;
import java.util.Stack;
import java.util.logging.Level;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.embed.swing.JFXPanel;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.chart.BarChart;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import org.openide.modules.InstalledFileLocator;
import org.openide.nodes.ChildFactory;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.corecomponents.DataContentPanel;
import org.sleuthkit.autopsy.corecomponents.DataResultPanel;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
import org.sleuthkit.autopsy.datamodel.DirectoryNode;
import org.sleuthkit.autopsy.datamodel.DisplayableItemNode;
import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor;
import org.sleuthkit.autopsy.datamodel.FileNode;
import org.sleuthkit.autopsy.recentactivity.JavaSystemCaller;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.Directory;
import org.sleuthkit.datamodel.File;
import org.sleuthkit.datamodel.FsContent;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
/**
*
*/
public class TimelineJPanel extends javax.swing.JPanel {
private static final Logger logger = Logger.getLogger(TimelineJPanel.class.getName());
private final java.io.File macRoot = InstalledFileLocator.getDefault().locate("mactime", TimelineJPanel.class.getPackage().getName(), false);
//private JFrame jf; //frame for holding all the elements
private Group group_Charts; //Orders the charts
private Scene scene_Charts; //Displays the charts
private HBox hBox_Charts; //Holds the navigation buttons in horiztonal fashion.
private VBox vBox_FX; //Holds the JavaFX Elements in vertical fashion.
private JFXPanel panel_Charts; //FX panel to hold the group
private BarChart chart_Events; //Yearly/Monthly events - Bar chart
private ScrollPane scroll_Events; //Scroll Panes for dealing with oversized an oversized chart
private final int Height_Frame = 850; //Sizing constants
private final int Width_Frame = 1300;
private Button button_DrillUp; //Navigation buttons
private Button button_DrillDown;
private Button button_Reset;
private Button button_Go;
private ComboBox<String> dropdown_SelectYears; //Dropdown box for selecting years. Useful when the charts' scale means some years are unclickable, despite having events.
private final Stack<BarChart> stack_PrevCharts = new Stack<BarChart>(); //Stack for storing drill-up information.
private BarChart chart_TopLevel; //the topmost chart, used for resetting to default view.
private DataResultPanel dataResultPanel;
/**
* Creates new form Timeline
*/
public TimelineJPanel() {
initComponents();
//customize();
}
public void setDataResultPanel(DataResultPanel dataResultPanel) {
this.dataResultPanel = dataResultPanel;
}
public void initialize() {
customize();
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 400, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 300, Short.MAX_VALUE)
);
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
// End of variables declaration//GEN-END:variables
private void customize() {
//Making the main frame *
//jf = new JFrame(Case.getCurrentCase().getName() + " - Autopsy Timeline");
//jf.setSize(Width_Frame, Height_Frame); //(Width, Height)
//JPanels are used as the cohesive glue that binds everything together.*/
//The chartJpanel holds the chart,
//aligned vertically (Y_AXIS)
final JPanel chartJPanel = new JPanel();
chartJPanel.setLayout(new BoxLayout(chartJPanel, BoxLayout.Y_AXIS));
//ComboJPanel holds both of the above JPanels together,
//aligned vertically (Y_AXIS)
final JPanel comboJPanel = new JPanel();
comboJPanel.setLayout(new BoxLayout(comboJPanel, BoxLayout.Y_AXIS));
//JavaFX thread
//JavaFX components MUST be run in the JavaFX thread, otherwise massive amounts of exceptions will be thrown and caught. Liable to freeze up and crash.
//Components can be declared whenever, but initialization and manipulation must take place here.
PlatformImpl.startup(new Runnable() {
@Override
public void run() {
System.out.println("JavaFX thread running . . .");
panel_Charts = new JFXPanel();
group_Charts = new Group();
scene_Charts = new Scene(group_Charts, Width_Frame, Math.round(Height_Frame / .75)); //Width, Height
vBox_FX = new VBox(5);
vBox_FX.setAlignment(Pos.BOTTOM_CENTER);
hBox_Charts = new HBox(10);
hBox_Charts.setAlignment(Pos.BOTTOM_CENTER);
//Initializing default values for the scroll pane
scroll_Events = new ScrollPane();
scroll_Events.setPrefSize(Width_Frame, Math.round(Height_Frame / .75)); //Width, Height
scroll_Events.setContent(null); //Needs some content, otherwise it crashes
//mactime file placeholder, goes to pre-grenerated one on my desktop
String bodyFilePath = makeBodyFile();
java.io.File mactimePath = new java.io.File(makeMacTime(bodyFilePath));
//java.io.File mactime = new java.io.File("C:" + java.io.File.separator + "Users" + java.io.File.separator + "nflower" + java.io.File.separator + "Downloads" + java.io.File.separator + "kanarazu-12-21-2012-11-15-46-mactime.txt");
final List<YearEpoch> lsye = parseMacTime(mactimePath); //The sum total of the mactime parsing. YearEpochs contain everything you need to make a timeline.
//Making a dropdown box to select years.
List<String> lsi = new ArrayList<String>(); //List is in the format of {Year : Number of Events}, used for selecting from the dropdown.
for (YearEpoch ye : lsye) {
lsi.add(ye.year + " : " + ye.getNumFiles());
}
ObservableList<String> listSelect = FXCollections.observableArrayList(lsi);
dropdown_SelectYears = new ComboBox(listSelect);
//Buttons for navigating up and down the timeline
button_DrillDown = new Button("Drill down");
button_DrillDown.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent e) {
//Placeholder, does nothing. Need to store a chart_LastSelected or something
}
});
button_DrillUp = new Button("Drill up");
button_DrillUp.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent e) {
BarChart bc;
if (stack_PrevCharts.size() == 0) {
bc = chart_TopLevel;
} else {
bc = stack_PrevCharts.pop();
}
chart_Events = bc;
scroll_Events.setContent(chart_Events);
}
});
button_Reset = new Button("Reset");
button_Reset.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent e) {
stack_PrevCharts.clear();
chart_Events = chart_TopLevel;
scroll_Events.setContent(chart_Events);
}
});
button_Go = new Button("");
button_Go.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent e) {
if (dropdown_SelectYears.getValue() != null) {
chart_Events = createMonthsWithDrill(findYear(lsye, Integer.valueOf(dropdown_SelectYears.getValue().split(" ")[0])));
scroll_Events.setContent(chart_Events);
}
}
});
//Adding things to the V and H boxes.
//hBox_Charts stores the pseudo menu bar at the top of the timeline. |Drill Up|Drill Down|Reset|View Year: [Select Year]||
hBox_Charts.getChildren().addAll(button_DrillUp, button_DrillDown, button_Reset, new Label("View Year:"), dropdown_SelectYears, button_Go);
vBox_FX.getChildren().addAll(hBox_Charts, scroll_Events); //FxBox_V holds things in a visual stack.
group_Charts.getChildren().add(vBox_FX); //Adding the FxBox to the group. Groups make things easier to manipulate without having to update a hundred things every change.
panel_Charts.setScene(scene_Charts);
panel_Charts.setAlignmentX(Component.LEFT_ALIGNMENT);
chartJPanel.add(panel_Charts);
chartJPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
comboJPanel.add(chartJPanel);
chart_TopLevel = createYearChartWithDrill(lsye);
chart_Events = chart_TopLevel;
scroll_Events.setContent(chart_Events);
// set bg color of comboJPanel
comboJPanel.setBackground(java.awt.Color.red);
comboJPanel.setVisible(true);
setLayout(new BoxLayout(TimelineJPanel.this, BoxLayout.PAGE_AXIS));
add(comboJPanel);
//setVisible(true);
}
});
}
private String makeBodyFile() {
// Setup timestamp
DateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy-HH-mm-ss");
Date date = new Date();
String datenotime = dateFormat.format(date);
Case currentCase = Case.getCurrentCase();
SleuthkitCase skCase = currentCase.getSleuthkitCase();
// Get report path
String bodyFilePath = currentCase.getCaseDirectory() + java.io.File.separator + "temp"
+ java.io.File.separator + currentCase.getName() + "-" + datenotime + ".txt";
// Run query to get all files
ResultSet rs = null;
try {
// exclude non-fs files/dirs and . and .. files
rs = skCase.runQuery("SELECT * FROM tsk_files "
+ "WHERE type = '" + TskData.TSK_DB_FILES_TYPE_ENUM.FS.getFileType() + "' "
+ "AND name != '.' "
+ "AND name != '..'");
List<FsContent> fs = skCase.resultSetToFsContents(rs);
// Loop files and write info to report
for (FsContent file : fs) {
BufferedWriter out = null;
try {
// MD5|name|inode|mode_as_string|ObjId|GID|size|atime|mtime|ctime|crtime
out = new BufferedWriter(new FileWriter(bodyFilePath, true));
if (file.getMd5Hash() != null) {
out.write(file.getMd5Hash());
}
out.write("|");
if (file.getUniquePath() != null) {
out.write(file.getUniquePath());
}
out.write("|");
out.write(Long.toString(file.getMetaAddr()));
out.write("|");
String modeString = file.getModesAsString();
if (modeString != null) {
out.write(modeString);
}
out.write("|");
out.write(Long.toString(file.getId()));
out.write("|");
out.write(Long.toString(file.getGid()));
out.write("|");
out.write(Long.toString(file.getSize()));
out.write("|");
out.write(Long.toString(file.getAtime()));
out.write("|");
out.write(Long.toString(file.getMtime()));
out.write("|");
out.write(Long.toString(file.getCtime()));
out.write("|");
out.write(Long.toString(file.getCrtime()));
out.write("\n");
} catch (IOException ex) {
logger.log(Level.WARNING, "Could not write the temp body file report.", ex);
} finally {
try {
out.flush();
out.close();
} catch (IOException ex) {
logger.log(Level.WARNING, "Could not flush and close the BufferedWriter.", ex);
}
}
}
} catch (SQLException ex) {
logger.log(Level.WARNING, "Failed to get all file information.", ex);
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "Failed to get the unique path.", ex);
} finally {
try {// Close the query
if (rs != null) {
skCase.closeRunQuery(rs);
}
} catch (SQLException ex) {
logger.log(Level.WARNING, "Failed to close the query.", ex);
}
}
return bodyFilePath;
}
private String makeMacTime(String pathToBodyFile) {
String macpath = "";
final String machome = macRoot.getAbsolutePath();
if (PlatformUtil.isWindowsOS()) {
macpath = machome + java.io.File.separator + "mactime.exe";
} else {
macpath = "perl " + machome + java.io.File.separator + "mactime.pl";
}
String macfile = Case.getCurrentCase().getCaseDirectory() + java.io.File.separator + "temp" + java.io.File.separator + Case.getCurrentCase().getName() + "-MACTIME.txt";
String command = macpath + " -b " + "\"" + pathToBodyFile + "\"" + " -d " + " -y " + ">" + "\"" + macfile + "\"";
try {
JavaSystemCaller.Exec.execute("\"" + command + "\"");
return macfile;
} catch (InterruptedException ie) {
logger.log(Level.WARNING, "Mactime process was interrupted by user", ie);
} catch (IOException ioe) {
logger.log(Level.SEVERE, "Could not create mactime file, encountered error ", ioe);
}
return null;
}
private List<YearEpoch> parseMacTime(java.io.File f) {
//System.out.println("Parsing mactime file.");
List<YearEpoch> years = new ArrayList<>();
Scanner scan;
try {
scan = new Scanner(new FileInputStream(f));
} catch (FileNotFoundException ex) {
logger.log(Level.SEVERE, "Error: could not find mactime file.", ex);
return years;
}
scan.useDelimiter(",");
scan.nextLine(); // skip the header line
SleuthkitCase skCase = Case.getCurrentCase().getSleuthkitCase();
int prevYear = -1;
YearEpoch ye = null;
while (scan.hasNextLine()) {
String[] s = scan.nextLine().split(","); //1999-02-08T11:08:08Z, 78706, m..b, rrwxrwxrwx, 0, 0, 8355, /img...
String[] datetime = s[0].split("T"); //{1999-02-08, 11:08:08Z}
String[] date = datetime[0].split("-"); // {1999, 02, 08}
int year = Integer.valueOf(date[0]);
int month = Integer.valueOf(date[1]) - 1; //Months are zero indexed: 1 = February, 6 = July, 11 = December
int day = Integer.valueOf(date[2]); //Days are 1 indexed
long ObjId = Long.valueOf(s[4]);
// when the year changes, create and add a new YearEpoch object to the list
if (year != prevYear) {
ye = new YearEpoch(year);
years.add(ye);
prevYear = year;
}
// create and add the file
AbstractFile file;
try {
file = skCase.getAbstractFileById(ObjId);
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Could not find a file with ID " + ObjId, ex);
continue;
}
ye.add(file, month, day);
}
return years;
}
/*
* The backbone of the timeline functionality, years are split into months, months into days, and days contain the events of that given day.
* All of those are Epochs.
*/
abstract class Epoch {
abstract public int getNumFiles();
}
private class YearEpoch extends Epoch {
private int year;
private List<MonthEpoch> months = new ArrayList<>();
YearEpoch(int year) {
this.year = year;
}
public int getYear() {
return year;
}
@Override
public int getNumFiles() {
int size = 0;
for (MonthEpoch me : months) {
size += me.getNumFiles();
}
return size;
}
public void add(AbstractFile af, int month, int day) {
// see if this month is in the list
MonthEpoch monthEpoch = null;
for (MonthEpoch me : months) {
if (me.getMonthInt() == month) {
monthEpoch = me;
break;
}
}
if (monthEpoch == null) {
monthEpoch = new MonthEpoch(month);
months.add(monthEpoch);
}
// add the file the the MonthEpoch object
monthEpoch.add(af, day);
}
}
private class MonthEpoch extends Epoch {
private int month; //Zero-indexed: June = 5, August = 7, etc
private List<DayEpoch> days = new ArrayList<>(); //List of DayEpochs in this month, max 31
MonthEpoch(int month) {
this.month = month;
}
public int getMonthInt() {
return month;
}
public int getTotalNumDays(int year) {
Calendar cal = Calendar.getInstance();
cal.set(year, month, 1);
return cal.getActualMaximum(Calendar.DAY_OF_MONTH);
}
@Override
public int getNumFiles() {
int numFiles = 0;
for (DayEpoch de : days) {
numFiles += de.getNumFiles();
}
return numFiles;
}
public DayEpoch getDay(int dayNum) {
DayEpoch de = null;
for (DayEpoch d : days) {
if (d.dayNum == dayNum) {
de = d;
break;
}
}
return de;
}
public void add(AbstractFile af, int day) {
DayEpoch dayEpoch = null;
for (DayEpoch de : days) {
if (de.getDayInt() == day) {
dayEpoch = de;
break;
}
}
if (dayEpoch == null) {
dayEpoch = new DayEpoch(day);
days.add(dayEpoch);
}
dayEpoch.add(af);
}
/**
* Returns the month's name in String format, e.g., September, July,
*/
String getMonthName() {
return new DateFormatSymbols().getMonths()[month];
}
/**
* @return the list of days in this month
*/
List<DayEpoch> getDays() {
return this.days;
}
}
private class DayEpoch extends Epoch {
private List<AbstractFile> files = new ArrayList<>();
int dayNum = 0; //Day of the month this Epoch represents, 1 indexed: 28=28.
DayEpoch(int dayOfMonth) {
this.dayNum = dayOfMonth;
}
public int getDayInt() {
return dayNum;
}
public int getNumFiles() {
return files.size();
}
public void add(AbstractFile af) {
files.add(af);
}
List<AbstractFile> getEvents() {
return this.files;
}
}
/*
* Displays a chart with events from one year only, separated into 1-month chunks.
* Always 12 per year, empty months are represented by no bar.
*/
private BarChart createMonthsWithDrill(final YearEpoch ye) {
final CategoryAxis xAxis = new CategoryAxis();
final NumberAxis yAxis = new NumberAxis();
xAxis.setLabel("Month (" + ye.year + ")");
yAxis.setLabel("Number of Events");
ObservableList<BarChart.Series<String, Number>> bcData = FXCollections.observableArrayList();
BarChart.Series<String, Number> se = new BarChart.Series<String, Number>();
for (final MonthEpoch me : ye.months) {
se.getData().add(new BarChart.Data<String, Number>(me.getMonthName(), me.getNumFiles())); //Adding new data at {X-pos, Y-Pos}
}
bcData.add(se);
final BarChart<String, Number> bc = new BarChart<String, Number>(xAxis, yAxis, bcData);
for (int i = 0; i < 12; i++) {
for (final BarChart.Data data : bc.getData().get(0).getData()) {
//Note:
// All the charts of this package have a problem where when the chart gets below a certain pixel ratio, the data stops drawing. The axes and the labels remain,
// But the actual chart data is invisible, unclickable, and unrendered. To partially compensate for that, data.getNode() can be manually scaled up to increase visibility.
// Sometimes I've had it jacked up to as much as x2400 just to see a sliver of information.
// But that doesn't work all the time. Adding it to a scrollpane and letting the user scroll up and down to view the chart is the other workaround. Both of these fixes suck.
data.getNode().setScaleX(.5);
data.getNode().addEventHandler(MouseEvent.MOUSE_PRESSED,
new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent e) {
if (e.getButton().equals(MouseButton.PRIMARY)) {
if (e.getClickCount() == 2) {
PlatformImpl.startup(new Runnable() {
@Override
public void run() {
chart_Events = createEventsByMonth(findMonth(ye.months, month_StringtoInt((String) data.getXValue())), ye);
scroll_Events.setContent(chart_Events);
}
});
}
}
}
});
}
}
bc.autosize();
bc.setPrefWidth(Width_Frame);
bc.setLegendVisible(false);
stack_PrevCharts.push(bc);
return bc;
}
/*
* Displays a chart with events from one month only.
* Up to 31 days per month, as low as 28 as determined by the specific MonthEpoch
*/
private BarChart createEventsByMonth(final MonthEpoch me, final YearEpoch ye) {
final CategoryAxis xAxis = new CategoryAxis();
final NumberAxis yAxis = new NumberAxis();
xAxis.setLabel("Day of Month");
yAxis.setLabel("Number of Events");
ObservableList<BarChart.Data> bcData = makeObservableListByMonthAllDays(me, ye.getYear());
BarChart.Series<String, Number> series = new BarChart.Series(bcData);
series.setName(me.getMonthName() + " " + ye.getYear());
ObservableList<BarChart.Series<String, Number>> ol = FXCollections.observableArrayList(series);
final BarChart<String, Number> bc = new BarChart<String, Number>(xAxis, yAxis, ol);
for (final BarChart.Data data : bc.getData().get(0).getData()) {
//data.getNode().setScaleX(2);
data.getNode().addEventHandler(MouseEvent.MOUSE_PRESSED,
new EventHandler<MouseEvent>() {
MonthEpoch myme = me;
@Override
public void handle(MouseEvent e) {
int day = (Integer.valueOf(((String) data.getXValue()).split("-")[1]));
DayEpoch de = myme.getDay(day);
List<AbstractFile> afs = Collections.EMPTY_LIST;
if (de != null) {
afs = de.getEvents();
} else {
logger.log(Level.SEVERE, "There were no events for the clicked-on day.");
}
final FsContentRootNode d = new FsContentRootNode("Test Root", afs);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
dataResultPanel.setNode(d);
}
});
}
});
}
bc.autosize();
bc.setPrefWidth(Width_Frame);
return bc;
}
/**
*
* @param mon The month to convert. Must be minimum 4 characters long "February" and "Febr" are acceptable.
* @return The integer value of the month. February = 1, July = 6
*/
private int month_StringtoInt(String mon) {
try {
Date date = new SimpleDateFormat("MMMM", Locale.ENGLISH).parse(mon);
Calendar cal = Calendar.getInstance();
cal.setTime(date);
return cal.get(Calendar.MONTH);
} catch (ParseException ex) {
logger.log(Level.WARNING, "Unable to convert string " + mon + " to integer", ex);
return -1;
}
}
/**
* Used for finding the proper month in a list of available months
* @param lst The list of months to search through. It is assumed that the desired match is in this list.
* @param match The month, in integer format, to retrieve.
* @return The month epoch as specified by match.
*/
private MonthEpoch findMonth(List<MonthEpoch> lst, int match) {
for (MonthEpoch e : lst) {
if (e.month == match) {
return e;
}
}
return null;
}
private ObservableList<BarChart.Data> makeObservableListByMonthAllDays(final MonthEpoch me, int year) {
ObservableList<BarChart.Data> bcData = FXCollections.observableArrayList();
int totalDays = me.getTotalNumDays(year);
for (int i = 1; i <= totalDays; ++i) {
DayEpoch day = me.getDay(i);
int numFiles = day == null ? 0 : day.getNumFiles();
BarChart.Data d = new BarChart.Data(me.month + 1 + "-" + i, numFiles);
d.setExtraValue(me);
bcData.add(d);
}
return bcData;
}
// The node factories used to make lists of files to send to the result viewer
private class FsContentNodeChildFactory extends ChildFactory<AbstractFile> {
List<AbstractFile> l;
FsContentNodeChildFactory(List<AbstractFile> l) {
this.l = l;
}
@Override
protected boolean createKeys(List<AbstractFile> list) {
list.addAll(l);
return true;
}
@Override
protected Node createNodeForKey(AbstractFile file) {
Node n;
if (file.isDir()) {
n = new DirectoryNode((Directory) file);
} else {
n = new FileNode((File) file) {
@Override
public boolean isLeafTypeNode() {
return false;
}
};
}
return n;
}
}
private class FsContentRootNode extends DisplayableItemNode {
FsContentRootNode(String NAME, List<AbstractFile> l) {
super(Children.create(new FsContentNodeChildFactory(l), true));
super.setName(NAME);
super.setDisplayName(NAME);
}
@Override
public DisplayableItemNode.TYPE getDisplayableItemNodeType() {
return DisplayableItemNode.TYPE.CONTENT;
}
@Override
public <T> T accept(DisplayableItemNodeVisitor<T> v) {
return null;
}
}
/**
* Used for finding the proper year in a list of available years
* @param lst The list of years to search through. It is assumed that the desired match is in this list.
* @param match The year to retrieve.
* @return The year epoch as specified by match.
*/
private YearEpoch findYear(List<YearEpoch> lst, int match) {
for (YearEpoch e : lst) {
if (e.year == match) {
return e;
}
}
return null;
}
/**
* Creates a BarChart with datapoints for all the years from the parsed
* mactime file.
*
* @param allYears The list of years that have data from the mactime file
* @return BarChart scaled to the year level
*/
private BarChart createYearChartWithDrill(final List<YearEpoch> allYears) {
final CategoryAxis xAxis = new CategoryAxis(); //Axes are very specific types. Categorys are strings.
final NumberAxis yAxis = new NumberAxis();
final Label l = new Label("");
l.setStyle("-fx-font: 24 arial;");
l.setTextFill(Color.AZURE);
xAxis.setLabel("Years");
yAxis.setLabel("Number of Events");
//Charts are made up of individual pieces of Chart.Data. In this case, a piece of data is a single bar on the graph.
//Data is packaged into a series, which can be assigned custom colors or styling
//After the series are created, 1 or more series are packaged into a single chart.
ObservableList<BarChart.Series<String, Number>> bcData = FXCollections.observableArrayList();
BarChart.Series<String, Number> se = new BarChart.Series<String, Number>();
for (final YearEpoch ye : allYears) {
se.getData().add(new BarChart.Data<String, Number>(String.valueOf(ye.year), ye.getNumFiles()));
}
bcData.add(se);
//Note:
// BarChart.Data wraps the Java Nodes class. BUT, until a BarChart.Data gets added to an actual series, it's node is null, and you can perform no operations on it.
// When the Data is added to a series(or a chart? I am unclear on where), a node is automaticaly generated for it, after which you can perform any of the operations it offers.
// In addtion, you are free to set the node to whatever you want. It wraps the most generic Node class.
// But it is for this reason that the chart generating functions have two forloops. I do not believe they can be condensed into a single loop due to the nodes being null until
// an undetermined point in time.
BarChart<String, Number> bc = new BarChart<String, Number>(xAxis, yAxis, bcData);
for (final BarChart.Data data : bc.getData().get(0).getData()) { //.get(0) refers to the BarChart.Series class to work on. There is only one series in this graph, so get(0) is safe.
data.getNode().setScaleX(.5);
data.getNode().addEventHandler(MouseEvent.MOUSE_CLICKED,
new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent e) {
if (e.getButton().equals(MouseButton.PRIMARY)) {
if (e.getClickCount() == 2) { //Checking for a doubleclick
PlatformImpl.startup(new Runnable() {
@Override
public void run() {
BarChart b = createMonthsWithDrill((YearEpoch) findYear(allYears, Integer.valueOf((String) data.getXValue())));
chart_Events = b;
scroll_Events.setContent(chart_Events);
}
});
//If a single click, hover a label over the cursor with information about the selection
} else if (e.getClickCount() == 1) {
l.setText(findYear(allYears, Integer.valueOf((String) data.getXValue())).getNumFiles() + " events");
l.setTranslateX(e.getX());
l.setTranslateY(e.getY());
}
}
}
});
}
bc.autosize(); //Get an auto height
bc.setPrefWidth(Width_Frame); //but override the width
bc.setLegendVisible(false); //The legend adds too much extra chart space, it's not necessary.
return bc;
}
}

View File

@ -23,19 +23,27 @@
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="-2" pref="51" max="-2" attributes="0"/>
<Component id="jLabel1" min="-2" max="-2" attributes="0"/>
<EmptySpace pref="131" max="32767" attributes="0"/>
<Group type="102" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="progressBar" pref="504" max="32767" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<Component id="jLabel1" min="-2" max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
</Group>
</Group>
<EmptySpace max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="-2" pref="37" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="jLabel1" min="-2" max="-2" attributes="0"/>
<EmptySpace pref="64" max="32767" attributes="0"/>
<EmptySpace min="-2" pref="7" max="-2" attributes="0"/>
<Component id="progressBar" min="-2" max="-2" attributes="0"/>
<EmptySpace pref="16" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
@ -48,5 +56,7 @@
</Property>
</Properties>
</Component>
<Component class="javax.swing.JProgressBar" name="progressBar">
</Component>
</SubComponents>
</Form>

View File

@ -16,9 +16,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.timeline;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
@ -29,8 +29,7 @@ import javax.swing.KeyStroke;
import org.openide.windows.WindowManager;
/**
*
* @author mciver
* Dialog with progress bar that pops up when timeline is being generated
*/
public class TimelineProgressDialog extends javax.swing.JDialog {
@ -49,11 +48,15 @@ public class TimelineProgressDialog extends javax.swing.JDialog {
public TimelineProgressDialog(java.awt.Frame parent, boolean modal) {
super(parent, modal);
initComponents();
setLocationRelativeTo(null);
//set icon the same as main app
setIconImage(WindowManager.getDefault().getMainWindow().getIconImage());
//progressBar.setIndeterminate(true);
setName("Make Timeline (Beta)");
// Close the dialog when Esc is pressed
String cancelName = "cancel";
InputMap inputMap = getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
@ -73,6 +76,40 @@ public class TimelineProgressDialog extends javax.swing.JDialog {
return returnStatus;
}
void updateProgressBar(final int progress) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
progressBar.setValue(progress);
}
});
}
void updateProgressBar(final String message) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
progressBar.setString(message);
}
});
}
void setProgressTotal(final int total) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
//progressBar.setIndeterminate(false);
progressBar.setMaximum(total);
//progressBar.setValue(0);
progressBar.setStringPainted(true);
progressBar.setVisible(true);
}
});
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
@ -83,6 +120,7 @@ public class TimelineProgressDialog extends javax.swing.JDialog {
private void initComponents() {
jLabel1 = new javax.swing.JLabel();
progressBar = new javax.swing.JProgressBar();
addWindowListener(new java.awt.event.WindowAdapter() {
public void windowClosing(java.awt.event.WindowEvent evt) {
@ -97,36 +135,48 @@ public class TimelineProgressDialog extends javax.swing.JDialog {
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGap(51, 51, 51)
.addComponent(jLabel1)
.addContainerGap(131, Short.MAX_VALUE))
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(progressBar, javax.swing.GroupLayout.DEFAULT_SIZE, 504, Short.MAX_VALUE)
.addGroup(layout.createSequentialGroup()
.addComponent(jLabel1)
.addGap(0, 0, Short.MAX_VALUE)))
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGap(37, 37, 37)
.addContainerGap()
.addComponent(jLabel1)
.addContainerGap(64, Short.MAX_VALUE))
.addGap(7, 7, 7)
.addComponent(progressBar, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap(16, Short.MAX_VALUE))
);
pack();
}// </editor-fold>//GEN-END:initComponents
/**
* Closes the dialog
*/
private void closeDialog(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_closeDialog
doClose(RET_CANCEL);
}//GEN-LAST:event_closeDialog
public void doClose(int retStatus) {
returnStatus = retStatus;
setVisible(false);
dispose();
}
void doClose(final int retStatus) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
returnStatus = retStatus;
setVisible(false);
dispose();
}
});
}
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JLabel jLabel1;
private javax.swing.JProgressBar progressBar;
// End of variables declaration//GEN-END:variables
private int returnStatus = RET_CANCEL;
}

View File

@ -7,6 +7,10 @@
Windows2
====================================================== -->
<folder name="Actions">
<folder name="Tools">
<file name="org-sleuthkit-autopsy-timeline-Simile2.instance_hidden"/>
<file name="org-sleuthkit-autopsy-timeline-Timeline.instance"/>
</folder>
<folder name="Window">
<file name="org-sleuthkit-autopsy-timeline-Timeline2TopComponent.instance_hidden"/>
<file name="org-sleuthkit-autopsy-timeline-TimelineTopComponent.instance_hidden"/>