diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/Bundle.properties b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/Bundle.properties index 4481e95ab3..2d18364283 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/Bundle.properties @@ -9,7 +9,6 @@ FileTypeIdModuleFactory.createFileIngestModule.exception.msg=Expected settings a FileTypeIdIngestJobSettingsPanel.skipKnownCheckBox.toolTipText=Depending on how many files have known hashes, checking this box will improve the speed of file type identification. FileTypeIdIngestJobSettingsPanel.skipKnownCheckBox.text=Skip known files (NSRL) FileTypeIdGlobalSettingsPanel.hexPrefixLabel.text=0x -FileTypeIdGlobalSettingsPanel.saveTypeButton.text=Save Type FileTypeIdGlobalSettingsPanel.deleteTypeButton.text=DeleteType FileTypeIdGlobalSettingsPanel.newTypeButton.text=New Type FileTypeIdGlobalSettingsPanel.offsetTextField.text= @@ -21,3 +20,4 @@ FileTypeIdGlobalSettingsPanel.signatureTypeLabel.text=Signature Type FileTypeIdGlobalSettingsPanel.mimeTypeTextField.text= FileTypeIdGlobalSettingsPanel.signatureLabel.text=Signature FileTypeIdGlobalSettingsPanel.mimeTypeLabel.text=Mime Type +FileTypeIdGlobalSettingsPanel.saveTypeButton.text=Save Type diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdGlobalSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdGlobalSettingsPanel.form index fd6d2250d3..49ae296a57 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdGlobalSettingsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdGlobalSettingsPanel.form @@ -35,7 +35,6 @@ - @@ -43,18 +42,17 @@ - - + + - - + @@ -78,22 +76,17 @@ - - - - - - - - - - - + + + + + + - + @@ -141,14 +134,14 @@ - + - + @@ -167,6 +160,9 @@ + + + @@ -223,6 +219,9 @@ + + + @@ -230,6 +229,9 @@ + + + @@ -237,6 +239,9 @@ + + + @@ -248,13 +253,12 @@ - - - - - + + + + diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdGlobalSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdGlobalSettingsPanel.java index e9b0060653..aaafa786eb 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdGlobalSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdGlobalSettingsPanel.java @@ -18,23 +18,30 @@ */ package org.sleuthkit.autopsy.modules.filetypeid; -import java.util.HashMap; +import java.util.Map; +import javax.swing.DefaultComboBoxModel; import javax.swing.DefaultListModel; +import javax.swing.JOptionPane; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; +import org.openide.util.NbBundle; import org.sleuthkit.autopsy.corecomponents.OptionsPanel; import org.sleuthkit.autopsy.ingest.IngestModuleGlobalSettingsPanel; import org.sleuthkit.autopsy.modules.filetypeid.FileType.Signature; -import org.sleuthkit.autopsy.modules.filetypeid.UserDefinedFileTypesManager.UserDefinedFileTypesException; /** * A panel to allow a user to make custom file type definitions. */ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPanel implements OptionsPanel { - private final HashMap fileTypes = new HashMap<>(); - private final HashMap changedFileTypes = new HashMap<>(); - private final HashMap deletedFileTypes = new HashMap<>(); + private static final String RAW_SIGNATURE_TYPE_COMBO_BOX_ITEM = "Bytes (Hex)"; // RJCTODO: Bundle + private static final String ASCII_SIGNATURE_TYPE_COMBO_BOX_ITEM = "String (ASCII)"; // RJCTODO: Bundle + + /** + * This mapping of file type names to file types is used to hold the types + * displayed in the file types list component. + */ + private Map fileTypes; /** * Creates a panel to allow a user to make custom file type definitions. @@ -56,19 +63,38 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane */ @Override public void load() { - fileTypes.clear(); + /** + * Get the user-defined file types and set up a list model for the file + * types list component. + */ + this.fileTypes = UserDefinedFileTypesManager.getInstance().getUserDefinedFileTypes(); DefaultListModel fileTypesListModel = new DefaultListModel<>(); - for (FileType fileType : UserDefinedFileTypesManager.getInstance().getFileTypes()) { - fileTypes.put(fileType.getTypeName(), fileType); + for (FileType fileType : this.fileTypes.values()) { fileTypesListModel.addElement(fileType.getTypeName()); } - typesList.setModel(fileTypesListModel); + this.typesList.setModel(fileTypesListModel); - typesList.addListSelectionListener(new TypesListSelectionListener()); + /** + * Add a selection listener to populate the file type details + * display/edit components. + */ + this.typesList.addListSelectionListener(new TypesListSelectionListener()); + /** + * If there is at least one user-defined file type, select it the file + * types list component. + */ if (!fileTypesListModel.isEmpty()) { - typesList.setSelectedIndex(0); + this.typesList.setSelectedIndex(0); } + + /** + * Make a model for the signature type combo box component. + */ + DefaultComboBoxModel sigTypeComboBoxModel = new DefaultComboBoxModel<>(); + sigTypeComboBoxModel.addElement(FileTypeIdGlobalSettingsPanel.RAW_SIGNATURE_TYPE_COMBO_BOX_ITEM); + sigTypeComboBoxModel.addElement(FileTypeIdGlobalSettingsPanel.ASCII_SIGNATURE_TYPE_COMBO_BOX_ITEM); + this.signatureTypeComboBox.setModel(sigTypeComboBoxModel); } /** @@ -77,10 +103,8 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane @Override public void store() { try { - UserDefinedFileTypesManager fileTypesManager = UserDefinedFileTypesManager.getInstance(); - fileTypesManager.addFileTypes(changedFileTypes.values()); - fileTypesManager.deleteFileTypes(deletedFileTypes.values()); - } catch (UserDefinedFileTypesException ex) { + UserDefinedFileTypesManager.getInstance().setUserDefinedFileTypes(this.fileTypes); + } catch (UserDefinedFileTypesManager.UserDefinedFileTypesException ex) { // RJCTODO } } @@ -90,22 +114,36 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane @Override public void valueChanged(ListSelectionEvent e) { if (e.getValueIsAdjusting() == false) { - if (typesList.getSelectedIndex() == -1) { - deleteTypeButton.setEnabled(false); - + if (FileTypeIdGlobalSettingsPanel.this.typesList.getSelectedIndex() == -1) { + FileTypeIdGlobalSettingsPanel.this.deleteTypeButton.setEnabled(false); } else { - String typeName = (String) typesList.getSelectedValue(); - FileType fileType = fileTypes.get(typeName); + String typeName = FileTypeIdGlobalSettingsPanel.this.typesList.getSelectedValue(); + FileType fileType = FileTypeIdGlobalSettingsPanel.this.fileTypes.get(typeName); Signature signature = fileType.getSignature(); - mimeTypeTextField.setText(typeName); - offsetTextField.setText(Long.toString(signature.getOffset())); - deleteTypeButton.setEnabled(true); + FileTypeIdGlobalSettingsPanel.this.mimeTypeTextField.setText(typeName); + FileType.Signature.Type sigType = fileType.getSignature().getType(); + FileTypeIdGlobalSettingsPanel.this.signatureTypeComboBox.setSelectedItem(sigType == FileType.Signature.Type.RAW ? FileTypeIdGlobalSettingsPanel.RAW_SIGNATURE_TYPE_COMBO_BOX_ITEM : FileTypeIdGlobalSettingsPanel.ASCII_SIGNATURE_TYPE_COMBO_BOX_ITEM); + FileTypeIdGlobalSettingsPanel.this.offsetTextField.setText(Long.toString(signature.getOffset())); + FileTypeIdGlobalSettingsPanel.this.postHitCheckBox.setSelected(fileType.alertOnMatch()); + FileTypeIdGlobalSettingsPanel.this.deleteTypeButton.setEnabled(true); } } } } - // RJCTODO: Consider fixing OptinsPanel interface or writing story + /** + * RJCTODO + */ + private void clearTypeDetailsComponents() { + this.typesList.setSelectedIndex(-1); + this.mimeTypeTextField.setText(""); //NON-NLS // RJCTODO: Default name? + this.signatureTypeComboBox.setSelectedItem(FileTypeIdGlobalSettingsPanel.RAW_SIGNATURE_TYPE_COMBO_BOX_ITEM); + this.signatureTextField.setText(""); //NON-NLS + this.offsetTextField.setText(""); //NON-NLS + this.postHitCheckBox.setSelected(false); + } + + // RJCTODO: Consider fixing OptionsPanel interface or writing story /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always @@ -117,7 +155,7 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane buttonGroup1 = new javax.swing.ButtonGroup(); typesScrollPane = new javax.swing.JScrollPane(); - typesList = new javax.swing.JList(); + typesList = new javax.swing.JList(); jSeparator1 = new javax.swing.JSeparator(); mimeTypeLabel = new javax.swing.JLabel(); mimeTypeTextField = new javax.swing.JTextField(); @@ -129,7 +167,7 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane deleteTypeButton = new javax.swing.JButton(); saveTypeButton = new javax.swing.JButton(); hexPrefixLabel = new javax.swing.JLabel(); - signatureTypeComboBox = new javax.swing.JComboBox(); + signatureTypeComboBox = new javax.swing.JComboBox(); signatureLabel = new javax.swing.JLabel(); jScrollPane2 = new javax.swing.JScrollPane(); jTextArea1 = new javax.swing.JTextArea(); @@ -152,15 +190,28 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane offsetTextField.setText(org.openide.util.NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.offsetTextField.text")); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(newTypeButton, org.openide.util.NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.newTypeButton.text")); // NOI18N + newTypeButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + newTypeButtonActionPerformed(evt); + } + }); org.openide.awt.Mnemonics.setLocalizedText(deleteTypeButton, org.openide.util.NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.deleteTypeButton.text")); // NOI18N + deleteTypeButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + deleteTypeButtonActionPerformed(evt); + } + }); org.openide.awt.Mnemonics.setLocalizedText(saveTypeButton, org.openide.util.NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.saveTypeButton.text")); // NOI18N + saveTypeButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + saveTypeButtonActionPerformed(evt); + } + }); org.openide.awt.Mnemonics.setLocalizedText(hexPrefixLabel, org.openide.util.NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.hexPrefixLabel.text")); // NOI18N - signatureTypeComboBox.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Bytes (Hex)", "String", " " })); - org.openide.awt.Mnemonics.setLocalizedText(signatureLabel, org.openide.util.NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.signatureLabel.text")); // NOI18N jTextArea1.setEditable(false); @@ -190,25 +241,23 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addGap(37, 37, 37) - .addComponent(saveTypeButton) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addComponent(saveTypeButton)) .addGroup(layout.createSequentialGroup() .addGap(18, 18, 18) .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 13, javax.swing.GroupLayout.PREFERRED_SIZE) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) + .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() .addComponent(signatureTypeLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(signatureTypeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 82, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(0, 0, Short.MAX_VALUE)) + .addComponent(signatureTypeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 82, javax.swing.GroupLayout.PREFERRED_SIZE)) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addComponent(signatureLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(hexPrefixLabel) .addGap(2, 2, 2)) .addGroup(layout.createSequentialGroup() @@ -224,15 +273,12 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(mimeTypeTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 181, javax.swing.GroupLayout.PREFERRED_SIZE)))) .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGap(6, 6, 6) - .addComponent(jScrollPane2, javax.swing.GroupLayout.PREFERRED_SIZE, 260, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGroup(layout.createSequentialGroup() - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(postHitCheckBox))) - .addGap(0, 0, Short.MAX_VALUE))) - .addContainerGap()))) + .addGap(6, 6, 6) + .addComponent(jScrollPane2, javax.swing.GroupLayout.PREFERRED_SIZE, 260, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(layout.createSequentialGroup() + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(postHitCheckBox))))) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -270,13 +316,50 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane .addComponent(newTypeButton) .addComponent(deleteTypeButton))) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(saveTypeButton)))) .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 260, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap()) ); }// //GEN-END:initComponents + private void newTypeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_newTypeButtonActionPerformed + this.clearTypeDetailsComponents(); + // RJCTODO: Default name? + }//GEN-LAST:event_newTypeButtonActionPerformed + + private void deleteTypeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteTypeButtonActionPerformed + String typeName = this.typesList.getSelectedValue(); + this.fileTypes.remove(typeName); + this.clearTypeDetailsComponents(); + this.typesList.setSelectedIndex(-1); + }//GEN-LAST:event_deleteTypeButtonActionPerformed + + private void saveTypeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_saveTypeButtonActionPerformed + try { + String typeName = this.mimeTypeTextField.getText(); + if (typeName.isEmpty()) { + JOptionPane.showMessageDialog(null, "MIME type name cannot be empty.", "Missing MIME Type", JOptionPane.ERROR_MESSAGE); // RJCTODO: Bundle + return; + } + + String sigString = this.signatureTextField.getText(); + if (sigString.isEmpty()) { + JOptionPane.showMessageDialog(null, "Signature cannot be empty.", "Missing Signature", JOptionPane.ERROR_MESSAGE); // RJCTODO: Bundle + return; + } + + long offset = Long.parseUnsignedLong(this.offsetTextField.getText()); + + FileType.Signature.Type sigType = this.signatureTypeComboBox.getSelectedItem() == FileTypeIdGlobalSettingsPanel.RAW_SIGNATURE_TYPE_COMBO_BOX_ITEM ? FileType.Signature.Type.RAW : FileType.Signature.Type.ASCII; + FileType.Signature signature = new FileType.Signature(new byte[1], offset, sigType); // RJCTODO: + FileType fileType = new FileType(typeName, signature, this.postHitCheckBox.isSelected()); + this.fileTypes.put(typeName, fileType); + } catch (NumberFormatException ex) { + JOptionPane.showMessageDialog(null, "Offset is not a positive integer.", "Invalid Offset", JOptionPane.ERROR_MESSAGE); // RJCTODO: Bundle + } + }//GEN-LAST:event_saveTypeButtonActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.ButtonGroup buttonGroup1; @@ -294,9 +377,9 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane private javax.swing.JButton saveTypeButton; private javax.swing.JLabel signatureLabel; private javax.swing.JTextField signatureTextField; - private javax.swing.JComboBox signatureTypeComboBox; + private javax.swing.JComboBox signatureTypeComboBox; private javax.swing.JLabel signatureTypeLabel; - private javax.swing.JList typesList; + private javax.swing.JList typesList; private javax.swing.JScrollPane typesScrollPane; // End of variables declaration//GEN-END:variables diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/UserDefinedFileTypeIdentifier.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/UserDefinedFileTypeIdentifier.java index f7dac840ca..c93cc9a547 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/UserDefinedFileTypeIdentifier.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/UserDefinedFileTypeIdentifier.java @@ -18,8 +18,7 @@ */ package org.sleuthkit.autopsy.modules.filetypeid; -import java.util.ArrayList; -import java.util.List; +import java.util.Map; import org.sleuthkit.datamodel.AbstractFile; /** @@ -27,16 +26,14 @@ import org.sleuthkit.datamodel.AbstractFile; */ final class UserDefinedFileTypeIdentifier { - private final List fileTypes; + private final Map fileTypes; /** * Creates an object that can do file type identification for user-defined * file types. */ UserDefinedFileTypeIdentifier() { - this.fileTypes = new ArrayList<>(); - List fileTypeDefs = UserDefinedFileTypesManager.getInstance().getFileTypes(); - this.fileTypes.addAll(fileTypeDefs); + this.fileTypes = UserDefinedFileTypesManager.getInstance().getFileTypes(); } /** @@ -48,7 +45,7 @@ final class UserDefinedFileTypeIdentifier { */ FileType identify(final AbstractFile file) { FileType type = null; - for (FileType fileType : this.fileTypes) { + for (FileType fileType : this.fileTypes.values()) { if (fileType.matches(file)) { type = fileType; break; diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/UserDefinedFileTypesManager.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/UserDefinedFileTypesManager.java index c3f177a4c9..1266d3560f 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/UserDefinedFileTypesManager.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/UserDefinedFileTypesManager.java @@ -62,6 +62,13 @@ final class UserDefinedFileTypesManager { private static final String ASCII_ENCODING = "US-ASCII"; //NON-NLS private static UserDefinedFileTypesManager instance; + /** + * Predefined file types are stored in this mapping of file type names to + * file types. Access to this map is guarded by the intrinsic lock of the + * user-defined file types manager for thread-safety. + */ + private final Map predefinedFileTypes = new HashMap<>(); + /** * User-defined file types to be persisted to the user-defined file type * definitions file are stored in this mapping of file type names to file @@ -96,6 +103,10 @@ final class UserDefinedFileTypesManager { * (e.g., MIME type) and signatures. */ private UserDefinedFileTypesManager() { + /** + * Load the predefined types first so that they can be overwritten by + * any user-defined types with the same names. + */ loadPredefinedFileTypes(); loadUserDefinedFileTypes(); } @@ -109,14 +120,16 @@ final class UserDefinedFileTypesManager { /** * Create a file type that should match $MBR in Small2 image. */ - this.fileTypes.put("predefinedRAW", new FileType("predefinedRAW", new Signature(new byte[]{(byte) 0x66, (byte) 0x73, (byte) 0x00}, 8L, FileType.Signature.Type.RAW), true)); + FileType fileType = new FileType("predefinedRAW", new Signature(new byte[]{(byte) 0x66, (byte) 0x73, (byte) 0x00}, 8L, FileType.Signature.Type.RAW), true); + this.addPredefinedFileType(fileType); /** * Create a file type that should match test.txt in the Small2 image. */ // RJCTODO: Remove test type try { - this.fileTypes.put("predefinedASCII", new FileType("predefinedASCII", new Signature("hello".getBytes(UserDefinedFileTypesManager.ASCII_ENCODING), 0L, FileType.Signature.Type.ASCII), true)); + fileType = new FileType("predefinedASCII", new Signature("hello".getBytes(UserDefinedFileTypesManager.ASCII_ENCODING), 0L, FileType.Signature.Type.ASCII), true); + this.addPredefinedFileType(fileType); } catch (UnsupportedEncodingException ex) { UserDefinedFileTypesManager.logger.log(Level.SEVERE, "Unable to create 'predefinedASCII' predefined file type definition", ex); //NON-NLS } @@ -144,16 +157,28 @@ final class UserDefinedFileTypesManager { // catch (IndexOutOfBoundsException e) { // // do nothing // } - this.fileTypes.put("text/xml", new FileType("text/xml", new Signature(" getFileTypes() { + synchronized Map getFileTypes() { /** * It is safe to return references to the internal file type objects * because they are immutable. */ - return new ArrayList<>(this.fileTypes.values()); + return new HashMap<>(this.fileTypes); } /** - * Adds a new user-defined file type, overwriting any existing file type - * with the same type name. + * Gets the user-defined file types. * - * @param fileType The file type to add. - * @throws - * org.sleuthkit.autopsy.modules.filetypeid.UserDefinedFileTypesManager.UserDefinedFileTypesException + * @return A mapping of file type names to file types, possibly empty. */ - synchronized void addFileType(FileType fileType) throws UserDefinedFileTypesException { - this.addFileTypes(Collections.singletonList(fileType)); - } - - /** - * Adds a collection of new user-defined file types, overwriting any - * existing file types with the same type names. - * - * @param newFileTypes The file types to add. - * @throws - * org.sleuthkit.autopsy.modules.filetypeid.UserDefinedFileTypesManager.UserDefinedFileTypesException - */ - synchronized void addFileTypes(Collection newFileTypes) throws UserDefinedFileTypesException { + synchronized Map getUserDefinedFileTypes() { /** - * It is safe to hold references to client-constructed file type objects + * It is safe to return references to the internal file type objects * because they are immutable. */ - for (FileType fileType : newFileTypes) { - this.userDefinedFileTypes.put(fileType.getTypeName(), fileType); - this.fileTypes.put(fileType.getTypeName(), fileType); - } - this.saveUserDefinedTypes(); + return new HashMap<>(this.userDefinedFileTypes); } /** - * Deletes a user-defined file type. + * Sets the user-defined file types. * - * @param fileType The file type to delete. + * @param newFileTypes A mapping of file type names to user-defined + * file types. * @throws * org.sleuthkit.autopsy.modules.filetypeid.UserDefinedFileTypesManager.UserDefinedFileTypesException */ - synchronized void deleteFileType(FileType fileType) throws UserDefinedFileTypesException { - this.deleteFileTypes(Collections.singletonList(fileType)); - } - - /** - * Deletes a set of user-defined file types. - * - * @param deletedFileTypes The file types to delete. - * @throws - * org.sleuthkit.autopsy.modules.filetypeid.UserDefinedFileTypesManager.UserDefinedFileTypesException - */ - synchronized void deleteFileTypes(Collection deletedFileTypes) throws UserDefinedFileTypesException { - for (FileType fileType : deletedFileTypes) { - this.userDefinedFileTypes.remove(fileType.getTypeName(), fileType); - this.fileTypes.remove(fileType.getTypeName(), fileType); - } - this.saveUserDefinedTypes(); - } - - /** - * Persists the user-defined file type definitions. - * - * @throws - * org.sleuthkit.autopsy.modules.filetypeid.UserDefinedFileTypesManager.UserDefinedFileTypesException - */ - private void saveUserDefinedTypes() throws UserDefinedFileTypesException { - String filePath = UserDefinedFileTypesManager.getFileTypeDefinitionsFilePath(UserDefinedFileTypesManager.USER_DEFINED_TYPE_DEFINITIONS_FILE); - UserDefinedFileTypesManager.writeFileTypes(this.userDefinedFileTypes.values(), filePath); - } - - /** - * Writes a set of file type definitions to a given file. - * - * @param fileTypes The file types. - * @param filePath The destination file. - * @throws UserDefinedFileTypesException - */ - private static void writeFileTypes(Collection fileTypes, String filePath) throws UserDefinedFileTypesException { + synchronized void setUserDefinedFileTypes(Map newFileTypes) throws UserDefinedFileTypesManager.UserDefinedFileTypesException { try { - UserDefinedFileTypesManager.XMLWriter.writeFileTypes(fileTypes, filePath); + /** + * Persist the user-defined file type definitions. + */ + String filePath = UserDefinedFileTypesManager.getFileTypeDefinitionsFilePath(UserDefinedFileTypesManager.USER_DEFINED_TYPE_DEFINITIONS_FILE); + UserDefinedFileTypesManager.XMLWriter.writeFileTypes(newFileTypes.values(), filePath); + } catch (ParserConfigurationException | IOException ex) { UserDefinedFileTypesManager.logger.log(Level.SEVERE, "Failed to write file types file", ex); throw new UserDefinedFileTypesManager.UserDefinedFileTypesException(ex.getLocalizedMessage()); // RJCTODO: Create a bundled message } + + /** + * Clear and reinitialize the user-defined file type map. It is safe to + * hold references to file type objects obtained for the caller because + * they are immutable. + */ + this.userDefinedFileTypes.clear(); + this.userDefinedFileTypes.putAll(newFileTypes); + + /** + * Clear and reinitialize the combined file type map, loading the + * predefined types first so that they can be overwritten by any + * user-defined types with the same names. + */ + this.fileTypes.clear(); + this.fileTypes.putAll(this.predefinedFileTypes); + this.fileTypes.putAll(this.userDefinedFileTypes); } /**