diff --git a/Core/manifest.mf b/Core/manifest.mf index 00681f3e7f..0f9b4579f6 100644 --- a/Core/manifest.mf +++ b/Core/manifest.mf @@ -2,7 +2,7 @@ Manifest-Version: 1.0 OpenIDE-Module: org.sleuthkit.autopsy.core/10 OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/core/Bundle.properties OpenIDE-Module-Layer: org/sleuthkit/autopsy/core/layer.xml -OpenIDE-Module-Implementation-Version: 31 +OpenIDE-Module-Implementation-Version: 32 OpenIDE-Module-Requires: org.openide.windows.WindowManager AutoUpdate-Show-In-Client: true AutoUpdate-Essential-Module: true diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties index 3bf03f2103..3814ec6d0a 100644 --- a/Core/nbproject/project.properties +++ b/Core/nbproject/project.properties @@ -138,5 +138,5 @@ nbm.homepage=http://www.sleuthkit.org/ nbm.module.author=Brian Carrier nbm.needs.restart=true source.reference.curator-recipes-2.8.0.jar=release/modules/ext/curator-recipes-2.8.0-sources.jar -spec.version.base=10.19 +spec.version.base=10.20 diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java index 53cf076c2e..9c059205f5 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/AddTagAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2019 Basis Technology Corp. + * Copyright 2013-2020 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -139,7 +139,7 @@ abstract class AddTagAction extends AbstractAction implements Presenter.Popup { if (!tagNamesMap.isEmpty()) { for (Map.Entry entry : tagNamesMap.entrySet()) { TagName tagName = entry.getValue(); - TagSet tagSet = tagName.getTagSet(); + TagSet tagSet = tagsManager.getTagSet(tagName); // Show custom tags before predefined tags in the menu if (tagSet != null) { diff --git a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java index ba586c7b22..a0c3b1c5c0 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java +++ b/Core/src/org/sleuthkit/autopsy/actions/GetTagNameAndCommentDialog.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2018 Basis Technology Corp. + * Copyright 2013-2020 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,7 +23,6 @@ import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.util.ArrayList; -import java.util.HashMap; import java.util.logging.Level; import java.util.List; import java.util.Map; @@ -36,7 +35,6 @@ import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JList; import javax.swing.KeyStroke; -import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.casemodule.Case; @@ -150,7 +148,7 @@ public class GetTagNameAndCommentDialog extends JDialog { } ); - try { + try { TagsManager tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); List standardTagNames = TagsManager.getStandardTagNames(); Map tagNamesMap = new TreeMap<>(tagsManager.getDisplayNamesToTagNamesMap()); @@ -161,7 +159,7 @@ public class GetTagNameAndCommentDialog extends JDialog { tagNamesMap.entrySet().stream().map((entry) -> entry.getValue()).forEachOrdered((tagName) -> { TagSet tagSet = null; try { - tagSet = tagName.getTagSet(); + tagSet = tagsManager.getTagSet(tagName); } catch (TskCoreException ex) { Logger.getLogger(GetTagNameAndCommentDialog.class .getName()).log(Level.SEVERE, "Failed to get tag set", ex); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/actions/ReplaceTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/ReplaceTagAction.java index 91929db577..34ed836e3e 100644 --- a/Core/src/org/sleuthkit/autopsy/actions/ReplaceTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/ReplaceTagAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2018 Basis Technology Corp. + * Copyright 2018-2020 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -131,7 +131,7 @@ abstract class ReplaceTagAction extends AbstractAction implements if (!tagNamesMap.isEmpty()) { for (Map.Entry entry : tagNamesMap.entrySet()) { TagName tagName = entry.getValue(); - TagSet tagSet = tagName.getTagSet(); + TagSet tagSet = tagsManager.getTagSet(tagName); // Show custom tags before predefined tags in the menu if (tagSet != null) { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java index 746bac478c..1a9b2e792d 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2020 Basis Technology Corp. + * Copyright 2013-2020 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -56,7 +56,7 @@ public class TagsManager implements Closeable { private final SleuthkitCase caseDb; private static String DEFAULT_TAG_SET_NAME = "Project VIC"; - + private static final Object lock = new Object(); static { @@ -235,16 +235,16 @@ public class TagsManager implements Closeable { public static String getNotableTagDisplayName() { return TagNameDefinition.getNotableTagDisplayName(); } - + /** * Creates a new TagSetDefinition file. - * + * * @param tagSetDef The tag set definition. - * - * @throws IOException + * + * @throws IOException */ public static void addTagSetDefinition(TagSetDefinition tagSetDef) throws IOException { - synchronized(lock) { + synchronized (lock) { TagSetDefinition.writeTagSetDefinition(tagSetDef); } } @@ -267,20 +267,20 @@ public class TagsManager implements Closeable { caseDb.addOrUpdateTagName(def.getDisplayName(), def.getDescription(), def.getColor(), def.getKnownStatus()); } //Assume new case and add tag sets - for(TagSetDefinition setDef: TagSetDefinition.readTagSetDefinitions()) { + for (TagSetDefinition setDef : TagSetDefinition.readTagSetDefinitions()) { List tagNameList = new ArrayList<>(); - for(TagNameDefinition tagNameDef: setDef.getTagNameDefinitions()) { + for (TagNameDefinition tagNameDef : setDef.getTagNameDefinitions()) { tagNameList.add(caseDb.addOrUpdateTagName(tagNameDef.getDisplayName(), tagNameDef.getDescription(), tagNameDef.getColor(), tagNameDef.getKnownStatus())); } - - if(!tagNameList.isEmpty()) { + + if (!tagNameList.isEmpty()) { taggingMgr.addTagSet(setDef.getName(), tagNameList); } } } } catch (TskCoreException ex) { LOGGER.log(Level.SEVERE, "Error updating standard tag name and tag set definitions", ex); - } catch(IOException ex) { + } catch (IOException ex) { LOGGER.log(Level.SEVERE, "Error loading tag set JSON files", ex); } @@ -288,28 +288,41 @@ public class TagsManager implements Closeable { tagName.saveToCase(caseDb); } } - + /** * Get a list of all tag sets currently in the case database. - * + * * @return A list, possibly empty, of TagSet objects. - * + * * @throws TskCoreException */ public List getAllTagSets() throws TskCoreException { return caseDb.getTaggingManager().getTagSets(); } - + + /** + * Gets the tag set a tag name (tag definition) belongs to, if any. + * + * @param tagName The tag name. + * + * @return A TagSet object or null. + * + * @throws TskCoreException If there is an error querying the case database. + */ + public TagSet getTagSet(TagName tagName) throws TskCoreException { + return caseDb.getTaggingManager().getTagSet(tagName); + } + /** * Add a new TagSet to the case database. Tags will be ranked in the order * which they are passed to this method. - * - * @param name Tag set name. + * + * @param name Tag set name. * @param tagNameList List of TagName in rank order. - * + * * @return A new TagSet object. - * - * @throws TskCoreException + * + * @throws TskCoreException */ public TagSet addTagSet(String name, List tagNameList) throws TskCoreException { return caseDb.getTaggingManager().addTagSet(name, tagNameList); @@ -501,7 +514,7 @@ public class TagsManager implements Closeable { * name to the case database. */ public TagName addTagName(String displayName, String description, TagName.HTML_COLOR color, TskData.FileKnown knownStatus) throws TagNameAlreadyExistsException, TskCoreException { - synchronized(lock) { + synchronized (lock) { try { TagName tagName = caseDb.addOrUpdateTagName(displayName, description, color, knownStatus); Set customTypes = TagNameDefinition.getTagNameDefinitions(); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Persona.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Persona.java index fe4703aa1b..cc1b17ab80 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Persona.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/Persona.java @@ -255,7 +255,7 @@ public class Persona { /** * Sets the comment of this persona. * - * @param name The new comment. + * @param comment The new comment. * * @throws CentralRepoException If there is an error. */ @@ -313,7 +313,9 @@ public class Persona { /** * Modifies the confidence / justification of the given PersonaAccount * - * @param account account to modify + * @param account Account to modify. + * @param confidence Level of confidence. + * @param justification Justification. * * @throws CentralRepoException If there is an error in querying the * Personas table. @@ -485,7 +487,9 @@ public class Persona { /** * Modifies the given alias. * - * @param alias alias to modify + * @param key Key for the alias to modify. + * @param confidence Level of confidence. + * @param justification Justification. * * @throws CentralRepoException If there is an error in querying the * Personas table. @@ -535,7 +539,9 @@ public class Persona { /** * Modifies the given metadata. * - * @param metadata metadata to modify + * @param key Key for the metadata to modify. + * @param confidence Level of confidence. + * @param justification Justification. * * @throws CentralRepoException If there is an error in querying the * Personas table. diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties index db3e61bef5..26463c8818 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties @@ -79,3 +79,4 @@ CreatePersonaAccountDialog.typeLbl.text=Type: CreatePersonaAccountDialog.identifierTextField.text= CreatePersonaAccountDialog.identiferLbl.text=Identifier: CreatePersonaAccountDialog.okBtn.text=OK +PersonasTopComponent.introText.text=Personas represent an online identity. They span cases and are stored in the Central Repository based on accounts that were found in artifacts. You can create, edit, and delete personas here. diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties-MERGED index 9a1155bbe0..9096e0b9a5 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/Bundle.properties-MERGED @@ -4,8 +4,10 @@ AddMetadataDialog_dup_Title=Metadata add failure AddMetadataDialog_empty_name_msg=A metadata entry cannot have an empty name or value. AddMetadataDialog_empty_name_Title=Missing field(s) CreatePersonaAccountDialog.title.text=Create Account -CreatePersonaAccountDialog_dup_msg=An account with this identifier and type already exists. -CreatePersonaAccountDialog_dup_Title=Account creation failure +CreatePersonaAccountDialog_error_msg=Failed to create account. +CreatePersonaAccountDialog_error_title=Account failure +CreatePersonaAccountDialog_success_msg=Account added. +CreatePersonaAccountDialog_success_title=Account added CTL_OpenPersonas=Personas CTL_PersonasTopComponentAction=Personas CTL_PersonaDetailsTopComponent=Persona Details @@ -121,6 +123,7 @@ CreatePersonaAccountDialog.typeLbl.text=Type: CreatePersonaAccountDialog.identifierTextField.text= CreatePersonaAccountDialog.identiferLbl.text=Identifier: CreatePersonaAccountDialog.okBtn.text=OK +PersonasTopComponent.introText.text=Personas represent an online identity. They span cases and are stored in the Central Repository based on accounts that were found in artifacts. You can create, edit, and delete personas here. PersonasTopComponent_delete_confirmation_msg=Are you sure you want to delete this persona? PersonasTopComponent_delete_confirmation_Title=Are you sure? PersonasTopComponent_delete_exception_msg=Failed to delete persona. diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/CreatePersonaAccountDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/CreatePersonaAccountDialog.java index 1ac9abf3b7..d4d23f7f0e 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/CreatePersonaAccountDialog.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/CreatePersonaAccountDialog.java @@ -212,6 +212,10 @@ public class CreatePersonaAccountDialog extends JDialog { setVisible(true); } + @Messages({ + "CreatePersonaAccountDialog_error_title=Account failure", + "CreatePersonaAccountDialog_error_msg=Failed to create account.", + }) private CentralRepoAccount createAccount(CentralRepoAccount.CentralRepoAccountType type, String identifier) { CentralRepoAccount ret = null; try { @@ -220,18 +224,19 @@ public class CreatePersonaAccountDialog extends JDialog { ret = cr.getOrCreateAccount(type, identifier); } } catch (CentralRepoException e) { - logger.log(Level.SEVERE, "Failed to access central repository", e); + logger.log(Level.SEVERE, "Failed to create account", e); JOptionPane.showMessageDialog(this, - Bundle.PersonaAccountDialog_get_types_exception_Title(), - Bundle.PersonaAccountDialog_get_types_exception_msg(), + Bundle.CreatePersonaAccountDialog_error_title(), + Bundle.CreatePersonaAccountDialog_error_msg(), JOptionPane.ERROR_MESSAGE); } return ret; } @Messages({ - "CreatePersonaAccountDialog_dup_Title=Account creation failure", - "CreatePersonaAccountDialog_dup_msg=An account with this identifier and type already exists.",}) + "CreatePersonaAccountDialog_success_title=Account added", + "CreatePersonaAccountDialog_success_msg=Account added.", + }) private void okBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okBtnActionPerformed if (identifierTextField.getText().isEmpty()) { JOptionPane.showMessageDialog(this, @@ -246,6 +251,12 @@ public class CreatePersonaAccountDialog extends JDialog { String identifier = identifierTextField.getText(); if (createAccount(type, identifier) != null) { + // show account created message + JOptionPane.showMessageDialog(this, + Bundle.CreatePersonaAccountDialog_success_msg(), + Bundle.CreatePersonaAccountDialog_success_title(), + JOptionPane.INFORMATION_MESSAGE); + dispose(); } }//GEN-LAST:event_okBtnActionPerformed diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonasTopComponent.form b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonasTopComponent.form index e731a133b8..45edb3e159 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonasTopComponent.form +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonasTopComponent.form @@ -1,26 +1,5 @@ - -
@@ -44,17 +23,52 @@ - + + - + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -71,14 +85,7 @@ - - - - - - - - + @@ -88,8 +95,17 @@ - - + + + + + + + + + + + @@ -109,7 +125,7 @@ - + @@ -117,7 +133,7 @@ - + @@ -154,6 +170,13 @@ + + + + + + + @@ -191,13 +214,6 @@ - - - - - - - @@ -208,16 +224,6 @@ - - - - - - - - - - @@ -234,17 +240,33 @@ - + + + + + + + + + + + - + - + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonasTopComponent.java b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonasTopComponent.java index d4d1a7b340..7b1b95afa3 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonasTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/persona/PersonasTopComponent.java @@ -263,23 +263,39 @@ public final class PersonasTopComponent extends TopComponent { private void initComponents() { searchButtonGroup = new javax.swing.ButtonGroup(); - jSplitPane1 = new javax.swing.JSplitPane(); + introTextScrollPane = new javax.swing.JScrollPane(); + introText = new javax.swing.JTextArea(); + mainSplitPane = new javax.swing.JSplitPane(); searchPanel = new javax.swing.JPanel(); searchField = new javax.swing.JTextField(); searchNameRadio = new javax.swing.JRadioButton(); searchAccountRadio = new javax.swing.JRadioButton(); + searchBtn = new javax.swing.JButton(); resultsPane = new javax.swing.JScrollPane(); resultsTable = new javax.swing.JTable(); - searchBtn = new javax.swing.JButton(); createAccountBtn = new javax.swing.JButton(); - createBtn = new javax.swing.JButton(); editBtn = new javax.swing.JButton(); deleteBtn = new javax.swing.JButton(); - jSeparator1 = new javax.swing.JSeparator(); + createButtonSeparator = new javax.swing.JSeparator(); + createBtn = new javax.swing.JButton(); + detailsScrollPane = new javax.swing.JScrollPane(); detailsPanel = new org.sleuthkit.autopsy.centralrepository.persona.PersonaDetailsPanel(); setName(""); // NOI18N + introTextScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER); + + introText.setBackground(getBackground()); + introText.setColumns(20); + introText.setLineWrap(true); + introText.setRows(5); + introText.setText(org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.introText.text")); // NOI18N + introText.setWrapStyleWord(true); + introText.setFocusable(false); + introTextScrollPane.setViewportView(introText); + + mainSplitPane.setDividerLocation(400); + searchField.setText(org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.searchField.text")); // NOI18N searchButtonGroup.add(searchNameRadio); @@ -289,6 +305,8 @@ public final class PersonasTopComponent extends TopComponent { searchButtonGroup.add(searchAccountRadio); org.openide.awt.Mnemonics.setLocalizedText(searchAccountRadio, org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.searchAccountRadio.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(searchBtn, org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.searchBtn.text")); // NOI18N + resultsTable.setToolTipText(org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.resultsTable.toolTipText")); // NOI18N resultsTable.getTableHeader().setReorderingAllowed(false); resultsPane.setViewportView(resultsTable); @@ -298,18 +316,16 @@ public final class PersonasTopComponent extends TopComponent { resultsTable.getColumnModel().getColumn(1).setHeaderValue(org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.resultsTable.columnModel.title1")); // NOI18N } - org.openide.awt.Mnemonics.setLocalizedText(searchBtn, org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.searchBtn.text")); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(createAccountBtn, org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.createAccountBtn.text")); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(createBtn, org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.createBtn.text")); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(editBtn, org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.editBtn.text")); // NOI18N editBtn.setEnabled(false); org.openide.awt.Mnemonics.setLocalizedText(deleteBtn, org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.deleteBtn.text")); // NOI18N deleteBtn.setEnabled(false); + org.openide.awt.Mnemonics.setLocalizedText(createBtn, org.openide.util.NbBundle.getMessage(PersonasTopComponent.class, "PersonasTopComponent.createBtn.text")); // NOI18N + javax.swing.GroupLayout searchPanelLayout = new javax.swing.GroupLayout(searchPanel); searchPanel.setLayout(searchPanelLayout); searchPanelLayout.setHorizontalGroup( @@ -317,13 +333,7 @@ public final class PersonasTopComponent extends TopComponent { .addGroup(searchPanelLayout.createSequentialGroup() .addContainerGap() .addGroup(searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jSeparator1) - .addGroup(searchPanelLayout.createSequentialGroup() - .addComponent(createBtn) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(editBtn) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(deleteBtn)) + .addComponent(createButtonSeparator) .addComponent(resultsPane, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) .addComponent(searchField) .addGroup(searchPanelLayout.createSequentialGroup() @@ -333,7 +343,14 @@ public final class PersonasTopComponent extends TopComponent { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(searchBtn)) .addGroup(searchPanelLayout.createSequentialGroup() - .addComponent(createAccountBtn) + .addGroup(searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(createAccountBtn) + .addGroup(searchPanelLayout.createSequentialGroup() + .addComponent(createBtn) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(editBtn) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(deleteBtn))) .addGap(0, 0, Short.MAX_VALUE))) .addContainerGap()) ); @@ -348,42 +365,52 @@ public final class PersonasTopComponent extends TopComponent { .addComponent(searchAccountRadio) .addComponent(searchBtn)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(resultsPane, javax.swing.GroupLayout.DEFAULT_SIZE, 457, Short.MAX_VALUE) + .addComponent(resultsPane, javax.swing.GroupLayout.DEFAULT_SIZE, 300, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(searchPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(editBtn) .addComponent(createBtn) .addComponent(deleteBtn)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 4, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(createButtonSeparator, javax.swing.GroupLayout.PREFERRED_SIZE, 4, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(createAccountBtn, javax.swing.GroupLayout.PREFERRED_SIZE, 32, javax.swing.GroupLayout.PREFERRED_SIZE) .addContainerGap()) ); - jSplitPane1.setLeftComponent(searchPanel); - jSplitPane1.setRightComponent(detailsPanel); + mainSplitPane.setLeftComponent(searchPanel); + + detailsScrollPane.setViewportView(detailsPanel); + + mainSplitPane.setRightComponent(detailsScrollPane); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jSplitPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 794, Short.MAX_VALUE) + .addComponent(introTextScrollPane) + .addComponent(mainSplitPane, javax.swing.GroupLayout.DEFAULT_SIZE, 724, Short.MAX_VALUE) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jSplitPane1) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addComponent(introTextScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 49, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(mainSplitPane, javax.swing.GroupLayout.DEFAULT_SIZE, 470, Short.MAX_VALUE)) ); }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton createAccountBtn; private javax.swing.JButton createBtn; + private javax.swing.JSeparator createButtonSeparator; private javax.swing.JButton deleteBtn; private org.sleuthkit.autopsy.centralrepository.persona.PersonaDetailsPanel detailsPanel; + private javax.swing.JScrollPane detailsScrollPane; private javax.swing.JButton editBtn; - private javax.swing.JSeparator jSeparator1; - private javax.swing.JSplitPane jSplitPane1; + private javax.swing.JTextArea introText; + private javax.swing.JScrollPane introTextScrollPane; + private javax.swing.JSplitPane mainSplitPane; private javax.swing.JScrollPane resultsPane; private javax.swing.JTable resultsTable; private javax.swing.JRadioButton searchAccountRadio; diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED index f1530d9f89..46d90c75c5 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/Bundle.properties-MERGED @@ -17,7 +17,7 @@ ContactsViewer_columnHeader_Phone=Phone ContactsViewer_noContacts_message= ContactsViewer_tabTitle=Contacts MediaViewer_Name=Media Attachments -MessageNode_Node_Property_Attms=Attachments +MessageNode_Node_Property_Attms=Attachment Count MessageNode_Node_Property_Date=Date MessageNode_Node_Property_From=From MessageNode_Node_Property_Subject=Subject diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java index 5070181e08..dece917507 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/MessageNode.java @@ -48,22 +48,22 @@ import org.sleuthkit.datamodel.blackboardutils.attributes.MessageAttachments; class MessageNode extends BlackboardArtifactNode { public static final String UNTHREADED_ID = ""; - + private static final Logger logger = Logger.getLogger(MessageNode.class.getName()); - + private final String threadID; - + private final Action preferredAction; - MessageNode(BlackboardArtifact artifact, String threadID, Action preferredAction) { + MessageNode(BlackboardArtifact artifact, String threadID, Action preferredAction) { super(artifact); - + this.preferredAction = preferredAction; final String stripEnd = StringUtils.stripEnd(artifact.getDisplayName(), "s"); // NON-NLS String removeEndIgnoreCase = StringUtils.removeEndIgnoreCase(stripEnd, "message"); // NON-NLS setDisplayName(removeEndIgnoreCase.isEmpty() ? stripEnd : removeEndIgnoreCase); - + this.threadID = threadID; } @@ -73,12 +73,12 @@ class MessageNode extends BlackboardArtifactNode { "MessageNode_Node_Property_To=To", "MessageNode_Node_Property_Date=Date", "MessageNode_Node_Property_Subject=Subject", - "MessageNode_Node_Property_Attms=Attachments" + "MessageNode_Node_Property_Attms=Attachment Count" }) - + @Override protected Sheet createSheet() { - Sheet sheet = super.createSheet(); + Sheet sheet = Sheet.createDefault(); Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES); if (sheetSet == null) { sheetSet = Sheet.createPropertiesSet(); @@ -89,42 +89,45 @@ class MessageNode extends BlackboardArtifactNode { final BlackboardArtifact artifact = getArtifact(); BlackboardArtifact.ARTIFACT_TYPE fromID = BlackboardArtifact.ARTIFACT_TYPE.fromID(artifact.getArtifactTypeID()); - - if(fromID == null || - (fromID != TSK_EMAIL_MSG && - fromID != TSK_MESSAGE)) { + + if (fromID == null + || (fromID != TSK_EMAIL_MSG + && fromID != TSK_MESSAGE)) { return sheet; } - - sheetSet.put(new NodeProperty<>("ThreadID", "ThreadID","",threadID == null ? UNTHREADED_ID : threadID)); //NON-NLS + if (threadID != null) { + sheetSet.put(new NodeProperty<>("ThreadID", "ThreadID", "", threadID)); //NON-NLS + } sheetSet.put(new NodeProperty<>("Subject", Bundle.MessageNode_Node_Property_Subject(), "", - getAttributeDisplayString(artifact, TSK_SUBJECT))); //NON-NLS + getAttributeDisplayString(artifact, TSK_SUBJECT))); //NON-NLS try { sheetSet.put(new NodeProperty<>("Attms", Bundle.MessageNode_Node_Property_Attms(), "", getAttachmentsCount())); //NON-NLS } catch (TskCoreException ex) { logger.log(Level.WARNING, "Error loading attachment count for " + artifact, ex); //NON-NLS } - - switch (fromID) { - case TSK_EMAIL_MSG: - sheetSet.put(new NodeProperty<>("From", Bundle.MessageNode_Node_Property_From(), "", - StringUtils.strip(getAttributeDisplayString(artifact, TSK_EMAIL_FROM), " \t\n;"))); //NON-NLS - sheetSet.put(new NodeProperty<>("To", Bundle.MessageNode_Node_Property_To(), "", - StringUtils.strip(getAttributeDisplayString(artifact, TSK_EMAIL_TO), " \t\n;"))); //NON-NLS - sheetSet.put(new NodeProperty<>("Date", Bundle.MessageNode_Node_Property_Date(), "", - getAttributeDisplayString(artifact, TSK_DATETIME_SENT))); //NON-NLS - break; - case TSK_MESSAGE: - sheetSet.put(new NodeProperty<>("From", Bundle.MessageNode_Node_Property_From(), "", - getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_FROM))); //NON-NLS - sheetSet.put(new NodeProperty<>("To", Bundle.MessageNode_Node_Property_To(), "", - getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_TO))); //NON-NLS - sheetSet.put(new NodeProperty<>("Date", Bundle.MessageNode_Node_Property_Date(), "", - getAttributeDisplayString(artifact, TSK_DATETIME))); //NON-NLS - break; - default: - break; + + String msg_from = getAttributeDisplayString(artifact, TSK_EMAIL_FROM); + String msg_to = getAttributeDisplayString(artifact, TSK_EMAIL_TO); + String date = getAttributeDisplayString(artifact, TSK_DATETIME_SENT); + + if (msg_from.isEmpty()) { + msg_from = getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_FROM); + } + if (msg_to.isEmpty()) { + msg_to = getAttributeDisplayString(artifact, TSK_PHONE_NUMBER_TO); + } + if (date.isEmpty()) { + date = getAttributeDisplayString(artifact, TSK_DATETIME); + } + + sheetSet.put(new NodeProperty<>("From", Bundle.MessageNode_Node_Property_From(), "", + msg_from)); //NON-NLS + sheetSet.put(new NodeProperty<>("To", Bundle.MessageNode_Node_Property_To(), "", + msg_to)); //NON-NLS + sheetSet.put(new NodeProperty<>("Date", Bundle.MessageNode_Node_Property_Date(), "", + date)); //NON-NLS + return sheet; } @@ -138,16 +141,16 @@ class MessageNode extends BlackboardArtifactNode { public String getSourceName() { return getDisplayName(); } - + String getThreadID() { return threadID; } - + @Override public Action getPreferredAction() { return preferredAction; } - + private int getAttachmentsCount() throws TskCoreException { final BlackboardArtifact artifact = getArtifact(); int attachmentsCount; @@ -158,8 +161,7 @@ class MessageNode extends BlackboardArtifactNode { try { MessageAttachments msgAttachments = BlackboardJsonAttrUtil.fromAttribute(attachmentsAttr, MessageAttachments.class); return msgAttachments.getAttachmentsCount(); - } - catch (BlackboardJsonAttrUtil.InvalidJsonException ex) { + } catch (BlackboardJsonAttrUtil.InvalidJsonException ex) { logger.log(Level.WARNING, String.format("Unable to parse json for MessageAttachments object in artifact: %s", artifact.getName()), ex); return 0; } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/ContactArtifactViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/ContactArtifactViewer.java index 86a16378f1..734a7ec180 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/ContactArtifactViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/ContactArtifactViewer.java @@ -288,7 +288,7 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac * section panel. * * @param sectionAttributesList List of attributes to display. - * @param sectionLabel Section name label. + * @param sectionHeader Section name label. * @param contactPanelLayout Panel layout. * @param contactPanelConstraints Layout constraints. * @@ -326,11 +326,9 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac } /** - * Kicks off a search for personas, based in the given list of attributes. + * Initiates a search for Personas for the accounts associated with the + * Contact. * - * @param accountAttributesList a list of account identifying attributes. - * - * @throws CentralRepoException */ @NbBundle.Messages({ "ContactArtifactViewer_persona_header=Persona", @@ -338,12 +336,6 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac "ContactArtifactViewer_cr_disabled_message=Enable Central Repository to view, create and edit personas.", "ContactArtifactViewer_persona_unknown=Unknown" }) - - /** - * Initiates a search for Personas for the accounts associated with the - * Contact. - * - */ private void initiatePersonasSearch() { // add a section header @@ -425,6 +417,7 @@ public class ContactArtifactViewer extends javax.swing.JPanel implements Artifac * Displays the given persona in the persona panel. * * @param persona Persona to display. + * @param matchNumber Number of matches. * @param missingAccountsList List of contact accounts this persona may be * missing. * @param gridBagLayout Layout to use. diff --git a/Core/src/org/sleuthkit/autopsy/core/Installer.java b/Core/src/org/sleuthkit/autopsy/core/Installer.java index 2d2357374a..3bc6d6a911 100644 --- a/Core/src/org/sleuthkit/autopsy/core/Installer.java +++ b/Core/src/org/sleuthkit/autopsy/core/Installer.java @@ -33,6 +33,8 @@ import java.util.logging.Handler; import java.util.logging.Level; import javafx.application.Platform; import javafx.embed.swing.JFXPanel; +import net.sf.sevenzipjbinding.SevenZip; +import net.sf.sevenzipjbinding.SevenZipNativeInitializationException; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.openide.modules.InstalledFileLocator; @@ -336,8 +338,8 @@ public class Installer extends ModuleInstall { } /** - * Make a folder in the config directory for object detection classifiers if one does not - * exist. + * Make a folder in the config directory for object detection classifiers if + * one does not exist. */ private static void ensureClassifierFolderExists() { File objectDetectionClassifierDir = new File(PlatformUtil.getObjectDetectionClassifierPath()); @@ -381,6 +383,7 @@ public class Installer extends ModuleInstall { ensureClassifierFolderExists(); ensureOcrLanguagePacksFolderExists(); initJavaFx(); + initializeSevenZip(); for (ModuleInstall mi : packageInstallers) { try { mi.restored(); @@ -393,8 +396,21 @@ public class Installer extends ModuleInstall { logger.log(Level.INFO, "Autopsy Core restore completed"); //NON-NLS preloadJython(); } - - + + /** + * Initializes 7zip-java bindings. We are performing initialization once + * because we encountered issues related to file locking when initialization + * was performed closer to where the bindings are used. See JIRA-6528. + */ + private void initializeSevenZip() { + try { + SevenZip.initSevenZipFromPlatformJAR(); + logger.log(Level.INFO, "7zip-java bindings loaded"); //NON-NLS + } catch (SevenZipNativeInitializationException e) { + logger.log(Level.SEVERE, "Error loading 7zip-java bindings", e); //NON-NLS + } + } + /** * Runs an initial load of the Jython modules to speed up subsequent loads. */ @@ -403,13 +419,12 @@ public class Installer extends ModuleInstall { try { JythonModuleLoader.getIngestModuleFactories(); JythonModuleLoader.getGeneralReportModules(); - } - catch (Exception ex) { + } catch (Exception ex) { // This is a firewall exception to ensure that any possible exception caused // by this initial load of the Jython modules are caught and logged. logger.log(Level.SEVERE, "There was an error while doing an initial load of python plugins.", ex); } - + }; new Thread(loader).start(); } diff --git a/Core/src/org/sleuthkit/autopsy/core/layer.xml b/Core/src/org/sleuthkit/autopsy/core/layer.xml index 2b54236d77..288c25ff3a 100644 --- a/Core/src/org/sleuthkit/autopsy/core/layer.xml +++ b/Core/src/org/sleuthkit/autopsy/core/layer.xml @@ -450,6 +450,7 @@ + diff --git a/Core/src/org/sleuthkit/autopsy/core/personasWsmode.xml b/Core/src/org/sleuthkit/autopsy/core/personasWsmode.xml new file mode 100644 index 0000000000..2ee8947b65 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/core/personasWsmode.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java index f1baac477a..370b4f2809 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java @@ -83,13 +83,19 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi private static final Set CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.DATA_SOURCE_ADDED, Case.Events.CURRENT_CASE); + /** + * RefreshThrottler is used to limit the number of refreshes performed when + * CONTENT_CHANGED and DATA_ADDED ingest module events are received. + */ + private final RefreshThrottler refreshThrottler; + /** * Create the base expression used as the where clause in the queries for * files by mime type. Filters out certain kinds of files and directories, * and known/slack files based on user preferences. * * @return The base expression to be used in the where clause of queries for - * files by mime type. + * files by mime type. */ private String createBaseWhereExpr() { return "(dir_type = " + TskData.TSK_FS_NAME_TYPE_ENUM.REG.getValue() + ")" @@ -108,6 +114,7 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi private void removeListeners() { deleteObservers(); IngestManager.getInstance().removeIngestJobEventListener(pcl); + refreshThrottler.unregisterEventListener(); Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, pcl); } @@ -155,32 +162,20 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi this.typesRoot = typesRoot; this.pcl = (PropertyChangeEvent evt) -> { String eventType = evt.getPropertyName(); - if (eventType.equals(IngestManager.IngestModuleEvent.CONTENT_CHANGED.toString()) - || eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) + if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString()) || eventType.equals(Case.Events.DATA_SOURCE_ADDED.toString())) { - /** - * Checking for a current case is a stop gap measure until a - * different way of handling the closing of cases is worked out. - * Currently, remote events may be received for a case that is - * already closed. - */ - try { - Case.getCurrentCaseThrows(); - typesRoot.updateShowCounts(); - populateHashMap(); - } catch (NoCurrentCaseException notUsed) { - /** - * Case is closed, do nothing. - */ - } + + refreshMimeTypes(); } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) { if (evt.getNewValue() == null) { removeListeners(); } } }; + refreshThrottler = new RefreshThrottler(new FileTypesByMimeTypeRefresher()); IngestManager.getInstance().addIngestJobEventListener(INGEST_JOB_EVENTS_OF_INTEREST, pcl); + refreshThrottler.registerForIngestModuleEvents(); Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, pcl); populateHashMap(); } @@ -201,7 +196,7 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi * @param node the Node which you wish to check. * * @return True if originNode is an instance of ByMimeTypeNode and is empty, - * false otherwise. + * false otherwise. */ public static boolean isEmptyMimeTypeNode(Node node) { boolean isEmptyMimeNode = false; @@ -212,6 +207,41 @@ public final class FileTypesByMimeType extends Observable implements AutopsyVisi } + private void refreshMimeTypes() { + /** + * Checking for a current case is a stop gap measure until a different + * way of handling the closing of cases is worked out. Currently, remote + * events may be received for a case that is already closed. + */ + try { + Case.getCurrentCaseThrows(); + typesRoot.updateShowCounts(); + populateHashMap(); + } catch (NoCurrentCaseException notUsed) { + /** + * Case is closed, do nothing. + */ + } + } + + /** + * Responsible for updating the 'By Mime Type' view in the UI. See + * RefreshThrottler for more details. + */ + private class FileTypesByMimeTypeRefresher implements RefreshThrottler.Refresher { + + @Override + public void refresh() { + refreshMimeTypes(); + } + + @Override + public boolean isRefreshRequired(PropertyChangeEvent evt) { + return true; + } + + } + /** * Class which represents the root node of the "By MIME Type" tree, will * have children of each media type present in the database or no children diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java index 7007c7f025..21799e19fa 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java @@ -78,7 +78,7 @@ final class MapWaypoint extends KdTree.XYZPoint implements org.jxmapviewer.viewe artifactTypesToColors.put(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH.getTypeID(), Color.GREEN); artifactTypesToColors.put(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACK.getTypeID(), Color.ORANGE); artifactTypesToColors.put(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT.getTypeID(), Color.ORANGE); - artifactTypesToColors.put(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF.getTypeID(), Color.CYAN); + artifactTypesToColors.put(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF.getTypeID(), Color.MAGENTA); } private final Waypoint dataModelWaypoint; diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java index fca75c2c56..a6df94206e 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java @@ -139,9 +139,10 @@ class SevenZipExtractor { } SevenZipExtractor(IngestJobContext context, FileTypeDetector fileTypeDetector, String moduleDirRelative, String moduleDirAbsolute) throws SevenZipNativeInitializationException { - if (!SevenZip.isInitializedSuccessfully() && (SevenZip.getLastInitializationException() == null)) { - SevenZip.initSevenZipFromPlatformJAR(); + if (!SevenZip.isInitializedSuccessfully()) { + throw new SevenZipNativeInitializationException("SevenZip has not been previously initialized."); } + this.context = context; this.fileTypeDetector = fileTypeDetector; this.moduleDirRelative = moduleDirRelative; diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/Bundle.properties-MERGED index 45adf1bd1e..5d005fab85 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/Bundle.properties-MERGED @@ -6,6 +6,7 @@ FilesIdentifierIngestModule.indexError.message=Failed to index interesting file FilesSet.rule.dateRule.toString=(modified within {0} day(s)) FilesSetDefsPanel.bytes=Bytes FilesSetDefsPanel.cancelImportMsg=Cancel import +FilesSetDefsPanel.cancelNewSetMsg=Cancel # {0} - file name FilesSetDefsPanel.exportButtonActionPerformed.fileExistPrompt=File {0} exists, overwrite? FilesSetDefsPanel.gigaBytes=Gigabytes @@ -23,10 +24,12 @@ FilesSetDefsPanel.interesting.fileExtensionFilterLbl=Autopsy Interesting File Se FilesSetDefsPanel.interesting.importButtonAction.featureName=Interesting Files Set Import FilesSetDefsPanel.interesting.importOwConflict=Import Interesting files set conflict FilesSetDefsPanel.interesting.importSetButton.text=Import Set +FilesSetDefsPanel.interesting.newOwConflict=Interesting files set conflict # {0} - FilesSet name -FilesSetDefsPanel.interesting.overwriteSetPrompt=Interesting files set <{0}> already exists locally, overwrite? +FilesSetDefsPanel.interesting.overwriteSetPrompt=Interesting files set "{0}" already exists locally, overwrite? # {0} - FilesSet name -FilesSetDefsPanel.interesting.standardFileConflict=A standard interesting file set already exists with the name <{0}>. Would you like to create a custom version of this file set? +# {1} - New FilesSet name +FilesSetDefsPanel.interesting.standardFileConflict=A standard interesting file set already exists with the name "{0}." Would you like to rename the set to "{1}?" FilesSetDefsPanel.Interesting.Title=Global Interesting Items Settings FilesSetDefsPanel.kiloBytes=Kilobytes FilesSetDefsPanel.loadError=Error loading interesting files sets from file. @@ -144,4 +147,4 @@ FilesSetDefsPanel.pathLabel.text=Path Substring: FilesSetDefsPanel.mimeTypeLabel.text=MIME Type: FilesSetDefsPanel.fileSizeLabel.text=File Size: # {0} - filesSetName -StandardInterestingFileSetsLoader.customSuffixed={0} (Custom) \ No newline at end of file +StandardInterestingFileSetsLoader.customSuffixed={0} (Custom) diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetDefsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetDefsPanel.java index 0214223348..7e0b57a412 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetDefsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetDefsPanel.java @@ -41,6 +41,7 @@ import javax.swing.filechooser.FileNameExtensionFilter; import org.apache.commons.lang3.tuple.Pair; import org.netbeans.spi.options.OptionsPanelController; import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.corecomponents.OptionsPanel; import org.sleuthkit.autopsy.coreutils.Logger; @@ -424,15 +425,6 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp option = JOptionPane.showConfirmDialog(this, panel, NbBundle.getMessage(FilesSetPanel.class, filterDialogTitle), JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE); } while (option == JOptionPane.OK_OPTION && !panel.isValidDefinition()); - // While adding new ruleset(selectedSet == null), if rule set with same name already exists, do not add to the filesSets hashMap. - // In case of editing an existing ruleset(selectedSet != null), following check is not performed. - if (this.filesSets.containsKey(panel.getFilesSetName()) && shouldCreateNew) { - MessageNotifyUtil.Message.error(NbBundle.getMessage(this.getClass(), - "FilesSetDefsPanel.doFileSetsDialog.duplicateRuleSet.text", - panel.getFilesSetName())); - return; - } - if (option == JOptionPane.OK_OPTION) { Map rules = new HashMap<>(); if (selectedSet != null) { @@ -450,10 +442,16 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp rules ); - if (shouldCreateNew) { - this.replaceFilesSet(null, filesSet, null); - } else { - this.replaceFilesSet(selectedSet, filesSet, null); + Pair result = handleConflict(filesSet, false); + option = result.getRight(); + FilesSet toAddOrUpdate = result.getLeft(); + + if (result.getRight() == JOptionPane.OK_OPTION) { + if (shouldCreateNew) { + this.replaceFilesSet(null, toAddOrUpdate, null); + } else { + this.replaceFilesSet(selectedSet, toAddOrUpdate, null); + } } } } @@ -519,10 +517,7 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp * * @param oldSet A set to replace, null if the new set is not a * replacement. - * @param name The name of the files set. - * @param description The description of the files set. - * @param ignoresKnownFiles Whether or not the files set ignores known - * files. + * @param newSet The new set of rules. * @param rules The set membership rules for the set. If null, * the rules in the new set will be used. */ @@ -534,7 +529,7 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp } FilesSet setToAdd = newSet; - + // Make the new/edited set definition and add it to the working copy of // the files set definitions. if (rules != null) { @@ -1120,16 +1115,8 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp this.doFileSetsDialog(this.setsList.getSelectedValue(), true); firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); }//GEN-LAST:event_copySetButtonActionPerformed + @NbBundle.Messages({ - "FilesSetDefsPanel.yesOwMsg=Yes, overwrite", - "FilesSetDefsPanel.yesStandardFileConflictCreate=Yes, create", - "FilesSetDefsPanel.noSkipMsg=No, skip", - "FilesSetDefsPanel.cancelImportMsg=Cancel import", - "# {0} - FilesSet name", - "FilesSetDefsPanel.interesting.overwriteSetPrompt=Interesting files set <{0}> already exists locally, overwrite?", - "# {0} - FilesSet name", - "FilesSetDefsPanel.interesting.standardFileConflict=A standard interesting file set already exists with the name <{0}>. Would you like to create a custom version of this file set?", - "FilesSetDefsPanel.interesting.importOwConflict=Import Interesting files set conflict", "FilesSetDefsPanel.interesting.failImportMsg=Interesting files set not imported", "FilesSetDefsPanel.interesting.fileExtensionFilterLbl=Autopsy Interesting File Set File (xml)", "FilesSetDefsPanel.interesting.importButtonAction.featureName=Interesting Files Set Import" @@ -1204,7 +1191,7 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp FilesSet selectedSet = null; for (FilesSet set : importedSets) { - Pair conflictResult = handleConflict(set); + Pair conflictResult = handleConflict(set, true); int choice = conflictResult.getRight(); FilesSet resultingFilesSet = conflictResult.getLeft(); @@ -1222,65 +1209,195 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp /** * Handles any possible conflicts that may arise from importing a files set. * - * @param set The set to potentially import. + * @param set The set to potentially import. + * @param isImport The set with which to handle the conflict is being + * imported, otherwise this is a set to be added from the + * "New Set" button. * * @return A pair of the files set to be imported (or null if none) and the * integer corresponding to the JOptionPane choice of the - * yes_no_cancel_option. + * JOptionPane.YES_NO_CANCEL option. */ - private Pair handleConflict(FilesSet set) { + private Pair handleConflict(FilesSet set, boolean isImport) { FilesSet conflict = this.filesSets.get(set.getName()); // if no conflict, return the files set as is with the option to proceed if (conflict == null) { return Pair.of(set, JOptionPane.OK_OPTION); - } else if (conflict.isStandardSet()) { - // if there is a conflict and the conflicting files set is a standard files set, - // see if allowing a custom files set is okay. - Object[] options = { - Bundle.FilesSetDefsPanel_yesStandardFileConflictCreate(), - Bundle.FilesSetDefsPanel_noSkipMsg(), - Bundle.FilesSetDefsPanel_cancelImportMsg() - }; - - int conflictChoice = JOptionPane.showOptionDialog(this, - Bundle.FilesSetDefsPanel_interesting_standardFileConflict(set.getName()), - Bundle.FilesSetDefsPanel_interesting_importOwConflict(), - JOptionPane.YES_NO_CANCEL_OPTION, - JOptionPane.QUESTION_MESSAGE, - null, - options, - options[0]); - - // if it is okay, try again to see if there is a conflict. - if (conflictChoice == JOptionPane.OK_OPTION) { - return handleConflict(StandardInterestingFilesSetsLoader.getAsCustomFileSet(set)); - } - - return Pair.of(null, conflictChoice); - } else { - // if there is a conflict, see if it is okay to overwrite. - Object[] options = { - Bundle.FilesSetDefsPanel_yesOwMsg(), - Bundle.FilesSetDefsPanel_noSkipMsg(), - Bundle.FilesSetDefsPanel_cancelImportMsg() - }; - - int conflictChoice = JOptionPane.showOptionDialog(this, - Bundle.FilesSetDefsPanel_interesting_overwriteSetPrompt(set.getName()), - Bundle.FilesSetDefsPanel_interesting_importOwConflict(), - JOptionPane.YES_NO_CANCEL_OPTION, - JOptionPane.QUESTION_MESSAGE, - null, - options, - options[0]); - - if (conflictChoice == JOptionPane.OK_OPTION) { - // if so, just return the files set to be placed in the map overwriting what is currently present. - return Pair.of(set, conflictChoice); - } - - return Pair.of(null, conflictChoice); } + + if (isImport) { + if (conflict.isStandardSet()) { + return onImportStandardSetConflict(set); + } else { + return onImportConflict(set); + } + } else { + if (conflict.isStandardSet()) { + return onNewEditSetStandardSetConflict(set); + } else { + return onNewEditSetConflict(set); + } + } + + } + + /** + * When a user imports a files set and the files set name collides with a + * pre-existing files set (not a standard files set), the user is prompted + * for how they would like that handled (overwrite, skip, or cancel whole + * operation) + * + * @param set The set to be imported. + * + * @return a pair of the files set and the JOptionPane.YES_NO_CANCEL option + */ + @Messages({ + "FilesSetDefsPanel.yesOwMsg=Yes, overwrite", + "FilesSetDefsPanel.noSkipMsg=No, skip", + "FilesSetDefsPanel.cancelImportMsg=Cancel import", + "# {0} - FilesSet name", + "FilesSetDefsPanel.interesting.overwriteSetPrompt=Interesting files set \"{0}\" already exists locally, overwrite?", + "FilesSetDefsPanel.interesting.importOwConflict=Import Interesting files set conflict",}) + private Pair onImportConflict(FilesSet set) { + // if there is a conflict, see if it is okay to overwrite. + Object[] options = { + Bundle.FilesSetDefsPanel_yesOwMsg(), + Bundle.FilesSetDefsPanel_noSkipMsg(), + Bundle.FilesSetDefsPanel_cancelImportMsg() + }; + int conflictChoice = JOptionPane.showOptionDialog(this, + Bundle.FilesSetDefsPanel_interesting_overwriteSetPrompt(set.getName()), + Bundle.FilesSetDefsPanel_interesting_importOwConflict(), + JOptionPane.YES_NO_CANCEL_OPTION, + JOptionPane.QUESTION_MESSAGE, + null, + options, + options[0]); + + if (conflictChoice == JOptionPane.OK_OPTION) { + // if so, just return the files set to be placed in the map overwriting what is currently present. + return Pair.of(set, conflictChoice); + } + + return Pair.of(null, conflictChoice); + } + + /** + * When a user imports a files set and the files set name collides with a + * pre-existing standard files set, the user is prompted for how they would + * like that handled (create files set with a " custom" suffix, skip, or + * cancel whole operation) + * + * @param set The set to be imported. + * + * @return a pair of the files set and the JOptionPane.YES_NO_CANCEL option + */ + @Messages({ + "FilesSetDefsPanel.yesStandardFileConflictCreate=Yes, create", + "# {0} - FilesSet name", + "# {1} - New FilesSet name", + "FilesSetDefsPanel.interesting.standardFileConflict=A standard interesting file set already exists with the name \"{0}.\" Would you like to rename your set to \"{1}?\"",}) + private Pair onImportStandardSetConflict(FilesSet set) { + // if there is a conflict and the conflicting files set is a standard files set, + // see if allowing a custom files set is okay. + Object[] options = { + Bundle.FilesSetDefsPanel_yesStandardFileConflictCreate(), + Bundle.FilesSetDefsPanel_noSkipMsg(), + Bundle.FilesSetDefsPanel_cancelImportMsg() + }; + + String setName = set.getName(); + String customSetName = Bundle.StandardInterestingFileSetsLoader_customSuffixed(set.getName()); + + int conflictChoice = JOptionPane.showOptionDialog(this, + Bundle.FilesSetDefsPanel_interesting_standardFileConflict(setName, customSetName), + Bundle.FilesSetDefsPanel_interesting_importOwConflict(), + JOptionPane.YES_NO_CANCEL_OPTION, + JOptionPane.QUESTION_MESSAGE, + null, + options, + options[0]); + + // if it is okay to create with custom prefix, try again to see if there is a conflict. + if (conflictChoice == JOptionPane.OK_OPTION) { + return handleConflict(StandardInterestingFilesSetsLoader.getAsCustomFileSet(set), true); + } + + return Pair.of(null, conflictChoice); + } + + /** + * When a user creates a files set or edits a files set and the files set + * name collides with a pre-existing files set (not a standard files set), + * the user is prompted for how they would like that handled (overwrite or + * cancel whole operation) + * + * @param set The set to be added. + * + * @return a pair of the files set and the JOptionPane.YES_NO_CANCEL option + */ + @Messages({ + "FilesSetDefsPanel.cancelNewSetMsg=Cancel", + "FilesSetDefsPanel.interesting.newOwConflict=Interesting files set conflict",}) + private Pair onNewEditSetConflict(FilesSet set) { + // if there is a conflict, see if it is okay to overwrite. + Object[] options = { + Bundle.FilesSetDefsPanel_yesOwMsg(), + Bundle.FilesSetDefsPanel_cancelNewSetMsg() + }; + int conflictChoice = JOptionPane.showOptionDialog(this, + Bundle.FilesSetDefsPanel_interesting_overwriteSetPrompt(set.getName()), + Bundle.FilesSetDefsPanel_interesting_newOwConflict(), + JOptionPane.OK_CANCEL_OPTION, + JOptionPane.QUESTION_MESSAGE, + null, + options, + options[0]); + + if (conflictChoice == JOptionPane.OK_OPTION) { + // if so, just return the files set to be placed in the map overwriting what is currently present. + return Pair.of(set, conflictChoice); + } + + return Pair.of(null, conflictChoice); + } + + /** + * When a user creates a files set and the files set name collides with a + * pre-existing standard files set, the user is prompted for how they would + * like that handled (create files set with a " custom" suffix or cancel + * whole operation) + * + * @param set The set to be adedd. + * + * @return a pair of the files set and the JOptionPane.YES_NO_CANCEL option + */ + private Pair onNewEditSetStandardSetConflict(FilesSet set) { + // if there is a conflict and the conflicting files set is a standard files set, + // see if allowing a custom files set is okay. + Object[] options = { + Bundle.FilesSetDefsPanel_yesStandardFileConflictCreate(), + Bundle.FilesSetDefsPanel_cancelNewSetMsg() + }; + + String setName = set.getName(); + String customSetName = Bundle.StandardInterestingFileSetsLoader_customSuffixed(set.getName()); + + int conflictChoice = JOptionPane.showOptionDialog(this, + Bundle.FilesSetDefsPanel_interesting_standardFileConflict(setName, customSetName), + Bundle.FilesSetDefsPanel_interesting_newOwConflict(), + JOptionPane.OK_CANCEL_OPTION, + JOptionPane.QUESTION_MESSAGE, + null, + options, + options[0]); + + // if it is okay to create with custom prefix, try again to see if there is a conflict. + if (conflictChoice == JOptionPane.OK_OPTION) { + return handleConflict(StandardInterestingFilesSetsLoader.getAsCustomFileSet(set), false); + } + + return Pair.of(null, conflictChoice); } @NbBundle.Messages({"FilesSetDefsPanel.interesting.exportButtonAction.featureName=Interesting Files Set Export", diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemsFilesSetSettings.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemsFilesSetSettings.java index 232ceea6df..6069aad3f6 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemsFilesSetSettings.java +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemsFilesSetSettings.java @@ -26,6 +26,7 @@ import java.io.Serializable; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -51,6 +52,9 @@ import org.sleuthkit.autopsy.modules.interestingitems.FilesSetsManager.FilesSets import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; +import java.util.Comparator; +import java.util.function.Function; +import java.util.stream.Collectors; class InterestingItemsFilesSetSettings implements Serializable { @@ -536,6 +540,34 @@ class InterestingItemsFilesSetSettings implements Serializable { } return true; } + + + /** + * Generates an alphabetically sorted list based on the provided collection and Function to retrieve a string field from each object. + * @param itemsToSort The items to be sorted into the newly generated list. + * @param getName The method to retrieve the given field from the object. + * @return The newly generated list sorted alphabetically by the given field. + */ + private static List sortOnField(Collection itemsToSort, final Function getName) { + Comparator comparator = (a,b) -> { + String aName = getName.apply(a); + String bName = getName.apply(b); + if (aName == null) { + aName = ""; + } + + if (bName == null) { + bName = ""; + } + + return aName.compareToIgnoreCase(bName); + }; + + return itemsToSort.stream() + .sorted(comparator) + .collect(Collectors.toList()); + } + /** * Write the FilesSets to a file as an xml. @@ -555,7 +587,12 @@ class InterestingItemsFilesSetSettings implements Serializable { Element rootElement = doc.createElement(FILE_SETS_ROOT_TAG); doc.appendChild(rootElement); // Add the interesting files sets to the document. - for (FilesSet set : interestingFilesSets) { + + List sortedFilesSets = sortOnField( + interestingFilesSets, + filesSet -> filesSet == null ? null : filesSet.getName()); + + for (FilesSet set : sortedFilesSets) { // Add the files set element and its attributes. Element setElement = doc.createElement(FILE_SET_TAG); setElement.setAttribute(NAME_ATTR, set.getName()); @@ -565,7 +602,12 @@ class InterestingItemsFilesSetSettings implements Serializable { setElement.setAttribute(VERSION_NUMBER, Integer.toString(set.getVersionNumber())); // Add the child elements for the set membership rules. // All conditions of a rule will be written as a single element in the xml - for (FilesSet.Rule rule : set.getRules().values()) { + + List sortedRules = sortOnField( + set.getRules().values(), + rule -> rule == null ? null : rule.getName()); + + for (FilesSet.Rule rule : sortedRules) { // Add a rule element with the appropriate name Condition // type tag. Element ruleElement; diff --git a/Core/src/org/sleuthkit/autopsy/report/infrastructure/ReportVisualPanel1.java b/Core/src/org/sleuthkit/autopsy/report/infrastructure/ReportVisualPanel1.java index b9d9fc8b34..79680178e1 100644 --- a/Core/src/org/sleuthkit/autopsy/report/infrastructure/ReportVisualPanel1.java +++ b/Core/src/org/sleuthkit/autopsy/report/infrastructure/ReportVisualPanel1.java @@ -151,6 +151,7 @@ final class ReportVisualPanel1 extends JPanel implements ListSelectionListener { } modulesJList.getSelectionModel().addListSelectionListener(this); + modulesJList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); modulesJList.setCellRenderer(new ModuleCellRenderer()); modulesJList.setListData(modules.toArray(new ReportModule[modules.size()])); modulesJList.setSelectedIndex(selectedIndex); diff --git a/Core/src/org/sleuthkit/autopsy/tags/TagUtils.java b/Core/src/org/sleuthkit/autopsy/tags/TagUtils.java index 3b63168a07..de93bed26e 100755 --- a/Core/src/org/sleuthkit/autopsy/tags/TagUtils.java +++ b/Core/src/org/sleuthkit/autopsy/tags/TagUtils.java @@ -19,6 +19,9 @@ package org.sleuthkit.autopsy.tags; import java.util.logging.Level; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.casemodule.services.TagsManager; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TagSet; @@ -52,12 +55,13 @@ public final class TagUtils { public static String getDecoratedTagDisplayName(TagName tagName) { String displayName = tagName.getDisplayName(); try { - TagSet tagSet = tagName.getTagSet(); + TagsManager tagsManager = Case.getCurrentCaseThrows().getServices().getTagsManager(); + TagSet tagSet = tagsManager.getTagSet(tagName); if (tagSet != null) { displayName = tagSet.getName() + ": " + displayName; } - } catch (TskCoreException ex) { - logger.log(Level.WARNING, String.format("Failed to get TagSet for TagName (%d)", tagName.getId())); + } catch (NoCurrentCaseException | TskCoreException ex) { + logger.log(Level.SEVERE, String.format("Failed to get TagSet for TagName '%s' (ID=%d)", tagName.getDisplayName(), tagName.getId())); } if (tagName.getKnownStatus() == TskData.FileKnown.BAD) { diff --git a/Core/src/org/sleuthkit/autopsy/textextractors/TextFileExtractor.java b/Core/src/org/sleuthkit/autopsy/textextractors/TextFileExtractor.java index 9c71b14ecc..678d62b487 100644 --- a/Core/src/org/sleuthkit/autopsy/textextractors/TextFileExtractor.java +++ b/Core/src/org/sleuthkit/autopsy/textextractors/TextFileExtractor.java @@ -62,7 +62,7 @@ public final class TextFileExtractor implements TextExtractor { encoding = StandardCharsets.UTF_8; } } catch (TskCoreException | IOException ex) { - logger.log(Level.SEVERE, String.format("Error detecting the " + logger.log(Level.WARNING, String.format("Error detecting the " + "encoding for %s (objID=%d)", file.getName(), file.getId()), ex); encoding = StandardCharsets.UTF_8; } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java index 9bc02f491c..0c80cb5315 100755 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java @@ -73,6 +73,7 @@ import org.sleuthkit.autopsy.coreutils.History; import org.sleuthkit.autopsy.coreutils.LoggedTask; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; +import org.sleuthkit.autopsy.coreutils.ThreadUtils; import org.sleuthkit.autopsy.events.AutopsyEvent; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; @@ -375,17 +376,26 @@ public class TimeLineController { } } + /** - * "Shut down" Timeline. Remove all the case and ingest listers. Close the - * timeline window. + * Shuts down the task executor in charge of handling case events. + */ + void shutDownTimeLineListeners() { + ThreadUtils.shutDownTaskExecutor(executor); + } + + /** + * "Shut down" Timeline. Close the timeline window. */ @ThreadConfined(type = ThreadConfined.ThreadType.AWT) - public void shutDownTimeLine() { + public void shutDownTimeLineGui() { if (topComponent != null) { topComponent.close(); topComponent = null; } } + + /** * Add the case and ingest listeners, prompt for rebuilding the database if diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineModule.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineModule.java index a2261b07b0..eedf2bc52c 100755 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineModule.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineModule.java @@ -95,7 +95,8 @@ public class TimeLineModule { */ synchronized (controllerLock) { if (controller != null) { - SwingUtilities.invokeLater(controller::shutDownTimeLine); + controller.shutDownTimeLineListeners(); + SwingUtilities.invokeLater(controller::shutDownTimeLineGui); } controller = null; } diff --git a/Experimental/nbproject/project.xml b/Experimental/nbproject/project.xml index 166c73f0a5..6e1fe0eaef 100644 --- a/Experimental/nbproject/project.xml +++ b/Experimental/nbproject/project.xml @@ -135,7 +135,7 @@ 10 - 10.19 + 10.20 diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java index 889dffd071..93e6e5956c 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2015 Basis Technology Corp. + * Copyright 2015-2020 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -71,16 +71,26 @@ public final class AutopsyManifestFileParser implements ManifestFileParser { Date dateFileCreated = new Date(attrs.creationTime().toMillis()); Document doc = this.createManifestDOM(filePath); XPath xpath = XPathFactory.newInstance().newXPath(); + XPathExpression expr = xpath.compile(CASE_NAME_XPATH); String caseName = (String) expr.evaluate(doc, XPathConstants.STRING); + if (caseName.isEmpty()) { + throw new ManifestFileParserException("Case name not found, manifest is invalid"); + } + expr = xpath.compile(DEVICE_ID_XPATH); String deviceId = (String) expr.evaluate(doc, XPathConstants.STRING); if (deviceId.isEmpty()) { deviceId = UUID.randomUUID().toString(); } + expr = xpath.compile(DATA_SOURCE_NAME_XPATH); String dataSourceName = (String) expr.evaluate(doc, XPathConstants.STRING); + if (dataSourceName.isEmpty()) { + throw new ManifestFileParserException("Data source path not found, manifest is invalid"); + } Path dataSourcePath = filePath.getParent().resolve(dataSourceName); + return new Manifest(filePath, dateFileCreated, caseName, deviceId, dataSourcePath, new HashMap<>()); } catch (Exception ex) { throw new ManifestFileParserException(String.format("Error parsing manifest %s", filePath), ex); diff --git a/ImageGallery/nbproject/project.xml b/ImageGallery/nbproject/project.xml index 4ef4c1fde1..501d7a9ea3 100644 --- a/ImageGallery/nbproject/project.xml +++ b/ImageGallery/nbproject/project.xml @@ -127,7 +127,7 @@ 10 - 10.19 + 10.20 diff --git a/InternalPythonModules/android/cachelocation.py b/InternalPythonModules/android/cachelocation.py index 3697fb44b0..683370dfad 100644 --- a/InternalPythonModules/android/cachelocation.py +++ b/InternalPythonModules/android/cachelocation.py @@ -42,6 +42,7 @@ from org.sleuthkit.datamodel import TskCoreException import traceback import general import struct +import os """ Parses cache files that Android maintains for Wifi and cell towers. Adds GPS points to blackboard. @@ -79,35 +80,39 @@ class CacheLocationAnalyzer(general.AndroidComponentAnalyzer): # code to parse the cache.wifi and cache.cell taken from https://forensics.spreitzenbarth.de/2011/10/28/decoding-cache-cell-and-cache-wifi-files/ cacheFile = open(str(file), 'rb') (version, entries) = struct.unpack('>hh', cacheFile.read(4)) - i = 0 - while i < entries: - key = cacheFile.read(struct.unpack('>h', cacheFile.read(2))[0]) - (accuracy, confidence, latitude, longitude, readtime) = struct.unpack('>iiddQ', cacheFile.read(32)) - timestamp = readtime/1000 - i = i + 1 + # Check the number of entries * 32 (entry record size) to see if it is bigger then the file, this is a indication the file is malformed or corrupted + if ((entries * 32) < abstractFile.getSize()): + i = 0 + self._logger.log(Level.INFO, "Number of Entries is " + str(entries) + " File size is " + str(abstractFile.getSize())) + while i < entries: + key = cacheFile.read(struct.unpack('>h', cacheFile.read(2))[0]) + (accuracy, confidence, latitude, longitude, readtime) = struct.unpack('>iiddQ', cacheFile.read(32)) + timestamp = readtime/1000 + i = i + 1 - attributes = ArrayList() - artifact = abstractFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK) - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE, general.MODULE_NAME, latitude)) - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE, general.MODULE_NAME, longitude)) - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, general.MODULE_NAME, timestamp)) - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME, general.MODULE_NAME, - abstractFile.getName() + " Location History")) - - artifact.addAttributes(attributes) - #Not storing these for now. - # artifact.addAttribute(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_VALUE.getTypeID(), AndroidModuleFactorymodule.moduleName, accuracy)) - # artifact.addAttribute(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT.getTypeID(), AndroidModuleFactorymodule.moduleName, confidence)) - try: - # index the artifact for keyword search - blackboard = Case.getCurrentCase().getSleuthkitCase().getBlackboard() - blackboard.postArtifact(artifact, general.MODULE_NAME) - except Blackboard.BlackboardException as ex: - self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + str(artifact.getArtifactID()), ex) - self._logger.log(Level.SEVERE, traceback.format_exc()) - MessageNotifyUtil.Notify.error("Failed to index GPS trackpoint artifact for keyword search.", artifact.getDisplayName()) - cacheFile.close() + attributes = ArrayList() + artifact = abstractFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK) + attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE, general.MODULE_NAME, latitude)) + attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE, general.MODULE_NAME, longitude)) + attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, general.MODULE_NAME, timestamp)) + attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME, general.MODULE_NAME, + abstractFile.getName() + " Location History")) + artifact.addAttributes(attributes) + #Not storing these for now. + # artifact.addAttribute(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_VALUE.getTypeID(), AndroidModuleFactorymodule.moduleName, accuracy)) + # artifact.addAttribute(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT.getTypeID(), AndroidModuleFactorymodule.moduleName, confidence)) + try: + # index the artifact for keyword search + blackboard = Case.getCurrentCase().getSleuthkitCase().getBlackboard() + blackboard.postArtifact(artifact, general.MODULE_NAME) + except Blackboard.BlackboardException as ex: + self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + str(artifact.getArtifactID()), ex) + self._logger.log(Level.SEVERE, traceback.format_exc()) + MessageNotifyUtil.Notify.error("Failed to index GPS trackpoint artifact for keyword search.", artifact.getDisplayName()) + cacheFile.close() + else: + self._logger.log(Level.WARNING, "Number of entries in file exceeds file size of file " + os.path.join(abstractFile.getParentPath(), abstractFile.getName())) except Exception as ex: self._logger.log(Level.SEVERE, "Error parsing Cached GPS locations to blackboard", ex) self._logger.log(Level.SEVERE, traceback.format_exc()) diff --git a/KeywordSearch/nbproject/project.xml b/KeywordSearch/nbproject/project.xml index 10e1e76bfe..bd5072a4be 100644 --- a/KeywordSearch/nbproject/project.xml +++ b/KeywordSearch/nbproject/project.xml @@ -119,7 +119,7 @@ 10 - 10.19 + 10.20 diff --git a/RecentActivity/nbproject/project.xml b/RecentActivity/nbproject/project.xml index f6465f00a5..3073bf314b 100644 --- a/RecentActivity/nbproject/project.xml +++ b/RecentActivity/nbproject/project.xml @@ -60,7 +60,7 @@ 10 - 10.19 + 10.20 diff --git a/Testing/nbproject/project.xml b/Testing/nbproject/project.xml index 2ebb94a835..2f8d76f7a1 100644 --- a/Testing/nbproject/project.xml +++ b/Testing/nbproject/project.xml @@ -47,7 +47,7 @@ 10 - 10.19 + 10.20 diff --git a/docs/doxygen-user/Doxyfile b/docs/doxygen-user/Doxyfile index 0f985042a3..eaa570e291 100644 --- a/docs/doxygen-user/Doxyfile +++ b/docs/doxygen-user/Doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = "Autopsy User Documentation" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 4.15.0 +PROJECT_NUMBER = 4.16.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a @@ -1025,7 +1025,7 @@ GENERATE_HTML = YES # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_OUTPUT = 4.15.0 +HTML_OUTPUT = 4.16.0 # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). diff --git a/docs/doxygen-user/auto_ingest_administration.dox b/docs/doxygen-user/auto_ingest_administration.dox index 158b2fb4e3..da95ffa997 100644 --- a/docs/doxygen-user/auto_ingest_administration.dox +++ b/docs/doxygen-user/auto_ingest_administration.dox @@ -57,7 +57,7 @@ If you right-click on a case, you can open it, see the log, delete the case, or Note that you can select multiple cases at once to delete. If you choose to delete a case (or cases), you'll see the following confirmation dialog: -\image html case_delete_confirm.png +\image html AutoIngest/case_delete_confirm.png \section auto_ingest_admin_health_monitor Health Monitor diff --git a/docs/doxygen-user/images/InterestingFiles/if_create_set.png b/docs/doxygen-user/images/InterestingFiles/if_create_set.png new file mode 100644 index 0000000000..6302a6026c Binary files /dev/null and b/docs/doxygen-user/images/InterestingFiles/if_create_set.png differ diff --git a/docs/doxygen-user/images/InterestingFiles/if_export.png b/docs/doxygen-user/images/InterestingFiles/if_export.png new file mode 100644 index 0000000000..7743689db4 Binary files /dev/null and b/docs/doxygen-user/images/InterestingFiles/if_export.png differ diff --git a/docs/doxygen-user/images/InterestingFiles/if_new_rule.png b/docs/doxygen-user/images/InterestingFiles/if_new_rule.png new file mode 100644 index 0000000000..826b2b1203 Binary files /dev/null and b/docs/doxygen-user/images/InterestingFiles/if_new_rule.png differ diff --git a/docs/doxygen-user/images/InterestingFiles/if_official_rule_details.png b/docs/doxygen-user/images/InterestingFiles/if_official_rule_details.png new file mode 100644 index 0000000000..669a5d4dca Binary files /dev/null and b/docs/doxygen-user/images/InterestingFiles/if_official_rule_details.png differ diff --git a/docs/doxygen-user/images/InterestingFiles/ingest.png b/docs/doxygen-user/images/InterestingFiles/ingest.png index aadf558768..9dedfd045b 100644 Binary files a/docs/doxygen-user/images/InterestingFiles/ingest.png and b/docs/doxygen-user/images/InterestingFiles/ingest.png differ diff --git a/docs/doxygen-user/images/InterestingFiles/main.png b/docs/doxygen-user/images/InterestingFiles/main.png index 67a27cdb7e..fd74aa70a5 100644 Binary files a/docs/doxygen-user/images/InterestingFiles/main.png and b/docs/doxygen-user/images/InterestingFiles/main.png differ diff --git a/docs/doxygen-user/interesting_files.dox b/docs/doxygen-user/interesting_files.dox index 7e4147a66f..e7f4305822 100644 --- a/docs/doxygen-user/interesting_files.dox +++ b/docs/doxygen-user/interesting_files.dox @@ -15,7 +15,7 @@ This module allows you to make sets of rules that will be run against each file \section interesting_files_config Configuration -To create and edit your rule sets, go to "Tools", "Options" and then select the "Interesting Files" tab. The area on the left side will show you a list of all the rule sets that are currently available. Selecting a rule set will display its description and information about each of its rules on the right side of the panel. +To create and edit your rule sets, go to "Tools", "Options" and then select the "Interesting Files" tab. The area on the left side will show you a list of all the rule sets that are currently available. This will include the official rule sets that are included with Autopsy and any rule sets that you create. Selecting a rule set will display its description and information about each of its rules on the right side of the panel. \image html InterestingFiles/main.png @@ -31,6 +31,8 @@ The buttons on the bottom of the left side of the panel control the rule sets.
  • Export Set - Exports the selected rule set in a format that can be shared with other Autopsy users. +Note that the predefined rule sets can not be deleted or edited. If you believe you have additions that would be useful to the community, see the \ref update_interesting_files_page page for instructions on submitting updates. + Selecting a rule set will display its description, whether it ignores known files, and the rules contained in the set. Selecting a rule will display the conditions for that rule in the "Rule Details" section. The buttons under the list of rules allow you to create new rules and edit or delete existing rules. Selecting "New Rule" will bring up a new window to create the rule. diff --git a/docs/doxygen-user/main.dox b/docs/doxygen-user/main.dox index 424a0323d1..63114401bd 100644 --- a/docs/doxygen-user/main.dox +++ b/docs/doxygen-user/main.dox @@ -93,7 +93,9 @@ The following topics are available here: - \ref object_detection_page - \ref volatility_dsp_page -- \subpage translations_page +- Community Contributions + - \subpage translations_page + - \subpage update_interesting_files_page If the topic you need is not listed, then you can: diff --git a/docs/doxygen-user/photorec_carver.dox b/docs/doxygen-user/photorec_carver.dox index d5ef67dd0b..3560f12fbb 100644 --- a/docs/doxygen-user/photorec_carver.dox +++ b/docs/doxygen-user/photorec_carver.dox @@ -22,7 +22,7 @@ Ingest Settings ------ The run-time setting for this module allows you to choose whether to keep corrupted files. -\image html photo_rec_settings.png +\image html photo_rec_settings.PNG Also note that the "Run ingest modules on" selection needs to include unallocated space for this module to run. diff --git a/docs/doxygen-user/tagging.dox b/docs/doxygen-user/tagging.dox index ada9f5d0c5..e4bccc77a6 100644 --- a/docs/doxygen-user/tagging.dox +++ b/docs/doxygen-user/tagging.dox @@ -22,7 +22,7 @@ At this point there are three options: - New tag -- Create a new tag and add it to the file/result -\image html tagging_new_tag.png +\image html tagging_new_tag.PNG There are several default tag names: - Bookmark - Default tag for marking files of interest diff --git a/docs/doxygen-user/triage.dox b/docs/doxygen-user/triage.dox index 3750f92434..47ac724105 100644 --- a/docs/doxygen-user/triage.dox +++ b/docs/doxygen-user/triage.dox @@ -56,7 +56,7 @@ VHD is a file format used by Microsoft Virtual Machines that is readable by Wind To create a sparse VHD, check the box for "Make a VHD image..." when selecting the disk to analyze. -\image html createVHD.png +\image html triage/createVHD.png \section triage_scenarios Scenarios diff --git a/docs/doxygen-user/ui_quick_search.dox b/docs/doxygen-user/ui_quick_search.dox index 901427243e..be960973e9 100644 --- a/docs/doxygen-user/ui_quick_search.dox +++ b/docs/doxygen-user/ui_quick_search.dox @@ -5,12 +5,12 @@ The user interface quick search feature allows you to search within the data on How to use it ----- In order to use the search you need to select any item in the area you wish to search, and start typing. If user interface quick search is available in the area you have selected a search field will appear in the bottom left hand corner of the area. As you type the string you are searching for it will auto-update to select one of the results which matches your string. You can switch between the results which match the string you have typed with the up and down keys. The search does not support the use of regular expressions but will match against any sub-sting in the fields it searches, not just at the beginning of the field. -\image html quick_search_result.PNG +\image html quick_search_result.png Configuration ----- By default the search will match against the data in all fields which are in the currently selected area. The search will also ignore case by default. If you want to change either of these default behaviors you can click the magnifying glass with the down arrow icon and configure which columns will be searched as well as if the search should ignore case. -\image html quick_search_configuration.PNG +\image html quick_search_configuration.png Where it can be used ----- diff --git a/docs/doxygen-user/updating_interesting_file_sets.dox b/docs/doxygen-user/updating_interesting_file_sets.dox new file mode 100644 index 0000000000..dfacd38f1d --- /dev/null +++ b/docs/doxygen-user/updating_interesting_file_sets.dox @@ -0,0 +1,26 @@ +/*! \page update_interesting_files_page Updating the Official Interesting File Sets + +The \ref interesting_files_identifier_page contains several official rule sets. You can select a rule set to display the rules it contains in the middle of the right side of the panel. + +\image html InterestingFiles/if_official_rule_details.png + +If you have one or more rules that you think should be included in an official rule set you can submit your new rules using the process below. Consult the \ref interesting_files_config section for general instructions on creating and editing interesting file sets. + +
      +
    1. Create a new interesting file set. Give it a name similar to the set you wish to update to make it clear which set your new rules belong to. Do not copy the existing rule set. + +\image html InterestingFiles/if_create_set.png + +
    2. Create your rule(s). Make sure each rule has a "Rule Name" that identifies the application it is detecting. Click the "Apply" button on the main panel when done. + +\image html InterestingFiles/if_new_rule.png + +
    3. Export the set as XML. + +\image html InterestingFiles/if_export.png + +
    4. Create an Autopsy Github issue that identifes the set to update and what applications were added, and attach the XML. Go to: https://github.com/sleuthkit/autopsy/issues +
    + + +*/ \ No newline at end of file diff --git a/docs/doxygen-user/volatility_dsp.dox b/docs/doxygen-user/volatility_dsp.dox index 51b137e5fe..ff5349f3ce 100644 --- a/docs/doxygen-user/volatility_dsp.dox +++ b/docs/doxygen-user/volatility_dsp.dox @@ -14,7 +14,7 @@ If you have a disk image associated with your memory image, ingest the disk imag On the next screen, you can select your memory image and then adjust the settings to choose a profile and which Volatility plugins to run. -\image html volatility_dsp_config.png +\image html volatility_dsp_config.PNG Next you'll see the ingest module configuration panel. No ingest modules will be run when using the Volatility data source processor, so simply hit the "Next" button. When it finishes, you may have some non-critical errors. These frequently come from the data source processor being unable to find files in the original disk image. If you did not add the associated disk image before running the Volatility data source processor on the memory image, there will be a large number of these errors but the Volatility module output will still be available. diff --git a/docs/doxygen/Doxyfile b/docs/doxygen/Doxyfile index 993ee8ded2..261517d081 100644 --- a/docs/doxygen/Doxyfile +++ b/docs/doxygen/Doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = "Autopsy" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 4.15.0 +PROJECT_NUMBER = 4.16.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears a the top of each page and should give viewer a @@ -1066,7 +1066,7 @@ GENERATE_HTML = YES # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_OUTPUT = api-docs/4.15.0/ +HTML_OUTPUT = api-docs/4.16.0/ # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). diff --git a/nbproject/project.properties b/nbproject/project.properties index 390228bc4d..d0eec3ae02 100644 --- a/nbproject/project.properties +++ b/nbproject/project.properties @@ -4,7 +4,7 @@ app.title=Autopsy ### lowercase version of above app.name=${branding.token} ### if left unset, version will default to today's date -app.version=4.15.0 +app.version=4.16.0 ### build.type must be one of: DEVELOPMENT, RELEASE #build.type=RELEASE build.type=DEVELOPMENT diff --git a/release_scripts/diffscript.py b/release_scripts/diffscript.py new file mode 100644 index 0000000000..af3d995a88 --- /dev/null +++ b/release_scripts/diffscript.py @@ -0,0 +1,220 @@ +"""This script determines the updated, added, and deleted properties from the '.properties-MERGED' files +and generates a csv file containing the items changed. This script requires the python libraries: +gitpython and jproperties. As a consequence, it also requires git >= 1.7.0 and python >= 3.4. +""" + +from git import Repo +from typing import List, Dict, Tuple +import re +import csv +from jproperties import Properties +import sys + + +class ItemChange: + def __init__(self, rel_path: str, key: str, prev_val: str, cur_val: str): + """Describes the change that occurred for a particular key of a properties file. + + Args: + rel_path (str): The relative path of the properties file. + key (str): The key in the properties file. + prev_val (str): The previous value for the key. + cur_val (str): The current value for the key. + """ + self.rel_path = rel_path + self.key = key + self.prev_val = prev_val + self.cur_val = cur_val + if ItemChange.has_str_content(cur_val) and not ItemChange.has_str_content(prev_val): + self.type = 'ADDITION' + elif not ItemChange.has_str_content(cur_val) and ItemChange.has_str_content(prev_val): + self.type = 'DELETION' + else: + self.type = 'CHANGE' + + @staticmethod + def has_str_content(content: str): + """Determines whether or not the content is empty or None. + + Args: + content (str): The text. + + Returns: + bool: Whether or not it has content. + """ + return content is not None and len(content.strip()) > 0 + + @staticmethod + def get_headers() -> List[str]: + """Returns the csv headers to insert when serializing a list of ItemChange objects to csv. + + Returns: + List[str]: The column headers + """ + return ['Relative Path', 'Key', 'Change Type', 'Previous Value', 'Current Value'] + + def get_row(self) -> List[str]: + """Returns the list of values to be entered as a row in csv serialization. + + Returns: + List[str]: The list of values to be entered as a row in csv serialization. + """ + return [ + self.rel_path, + self.key, + self.type, + self.prev_val, + self.cur_val] + + +def get_entry_dict(diff_str: str) -> Dict[str, str]: + """Retrieves a dictionary mapping the properties represented in the string. + + Args: + diff_str (str): The string of the properties file. + + Returns: + Dict[str,str]: The mapping of keys to values in that properties file. + """ + props = Properties() + props.load(diff_str, "utf-8") + return props.properties + + +def get_item_change(rel_path: str, key: str, prev_val: str, cur_val: str) -> ItemChange: + """Returns an ItemChange object if the previous value is not equal to the current value. + + Args: + rel_path (str): The relative path for the properties file. + key (str): The key within the properties file for this potential change. + prev_val (str): The previous value. + cur_val (str): The current value. + + Returns: + ItemChange: The ItemChange object or None if values are the same. + """ + if (prev_val == cur_val): + return None + else: + return ItemChange(rel_path, key, prev_val, cur_val) + + +def get_changed(rel_path: str, a_str: str, b_str: str) -> List[ItemChange]: + """Given the relative path of the properties file that + + Args: + rel_path (str): The relative path for the properties file. + a_str (str): The string representing the original state of the file. + b_str (str): The string representing the current state of the file. + + Returns: + List[ItemChange]: The changes determined. + """ + print('Retrieving changes for {}...'.format(rel_path)) + a_dict = get_entry_dict(a_str) + b_dict = get_entry_dict(b_str) + all_keys = set().union(a_dict.keys(), b_dict.keys()) + mapped = map(lambda key: get_item_change( + rel_path, key, a_dict.get(key), b_dict.get(key)), all_keys) + return filter(lambda entry: entry is not None, mapped) + + +def get_text(blob) -> str: + return blob.data_stream.read().decode('utf-8') + + +def get_changed_from_diff(rel_path: str, diff) -> List[ItemChange]: + """Determines changes from a git python diff. + + Args: + rel_path (str): The relative path for the properties file. + diff: The git python diff. + + Returns: + List[ItemChange]: The changes in properties. + """ + # an item was added + if diff.change_type == 'A': + changes = get_changed(rel_path, '', get_text(diff.b_blob)) + # an item was deleted + elif diff.change_type == 'D': + changes = get_changed(rel_path, get_text(diff.a_blob), '') + # an item was modified + elif diff.change_type == 'M': + changes = get_changed(rel_path, get_text( + diff.a_blob), get_text(diff.b_blob)) + else: + changes = [] + + return changes + + +def get_rel_path(diff) -> str: + """Determines the relative path based on the git python. + + Args: + diff: The git python diff. + + Returns: + str: The determined relative path. + """ + if diff.b_path is not None: + return diff.b_path + elif diff.a_path is not None: + return diff.a_path + else: + return '' + + +def write_diff_to_csv(repo_path: str, output_path: str, commit_1_id: str, commit_2_id: str): + """Determines the changes made in '.properties-MERGED' files from one commit to another commit. + + Args: + repo_path (str): The local path to the git repo. + output_path (str): The output path for the csv file. + commit_1_id (str): The initial commit for the diff. + commit_2_id (str): The latest commit for the diff. + """ + repo = Repo(repo_path) + commit_1 = repo.commit(commit_1_id) + commit_2 = repo.commit(commit_2_id) + + diffs = commit_1.diff(commit_2) + with open(output_path, 'w', newline='') as csvfile: + writer = csv.writer(csvfile) + writer.writerow(ItemChange.get_headers()) + + for diff in diffs: + rel_path = get_rel_path(diff) + if not rel_path.endswith('.properties-MERGED'): + continue + + changes = get_changed_from_diff(rel_path, diff) + + for item_change in changes: + writer.writerow(item_change.get_row()) + + +def print_help(): + """Prints a quick help message. + """ + print("diffscript.py [path to repo] [csv output path] [commit for previous release] [commit for current release (optional; defaults to 'HEAD')]") + + +def main(): + if len(sys.argv) <= 3: + print_help() + sys.exit(1) + + repo_path = sys.argv[1] + output_path = sys.argv[2] + commit_1_id = sys.argv[3] + commit_2_id = sys.argv[4] if len(sys.argv) > 4 else 'HEAD' + + write_diff_to_csv(repo_path, output_path, commit_1_id, commit_2_id) + + sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/thirdparty/InterestingFileSetRules/Encryption Programs.xml b/thirdparty/InterestingFileSetRules/Encryption Programs.xml index 1d12455893..5ad59eee3b 100644 --- a/thirdparty/InterestingFileSetRules/Encryption Programs.xml +++ b/thirdparty/InterestingFileSetRules/Encryption Programs.xml @@ -1,23 +1,25 @@ - - Cryptomator.exe - gdbus.exe - GFileEncryption.exe - EncFSMP.exe - Tor Browser - Desktop-Bridge.exe - cexpertcmd.exe - Encrypto.exe + aescrypt.exe - certainsafe.exe - Folder Lock.exe - BitLockerDeviceEncryption.exe - VeraCrypt.exe - cexpert_gui.exe - gpg.exe AxCrypt.exe - Tutanota Desktop.exe + BitLockerDeviceEncryption.exe + certainsafe.exe + cexpertcmd.exe + cexpert_gui.exe + Cryptomator.exe + ecryptfs + EncFSMP.exe + Encrypto.exe + Folder Lock.exe + GFileEncryption.exe + gpg.exe + gdbus.exe KeePass.exe + luks + Desktop-Bridge.exe + Tor Browser + Tutanota Desktop.exe + VeraCrypt.exe diff --git a/thunderbirdparser/nbproject/project.xml b/thunderbirdparser/nbproject/project.xml index 5f285136a0..106d3d64a3 100644 --- a/thunderbirdparser/nbproject/project.xml +++ b/thunderbirdparser/nbproject/project.xml @@ -45,7 +45,7 @@ 10 - 10.19 + 10.20