diff --git a/.gitignore b/.gitignore index 5e4866a207..dc88052299 100644 --- a/.gitignore +++ b/.gitignore @@ -77,3 +77,5 @@ Core/src/org/sleuthkit/autopsy/casemodule/docs/screenshot.png .DS_Store .*.swp Core/src/org/sleuthkit/autopsy/datamodel/ranges.csv + +thunderbirdparser/release/modules/ext diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java index dea544c416..9c66184b47 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java @@ -37,6 +37,7 @@ import javax.swing.JPanel; import javax.swing.JTextArea; import javax.swing.JToggleButton; import org.openide.util.Lookup; +import org.openide.util.NbBundle; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; import org.sleuthkit.autopsy.datasourceprocessors.RawDSProcessor; import org.sleuthkit.autopsy.coreutils.Logger; @@ -56,8 +57,11 @@ final class AddImageWizardSelectDspVisual extends JPanel { AddImageWizardSelectDspVisual(String lastDspUsed) { initComponents(); selectedDsp = lastDspUsed; + //if the last selected DSP was the Local Disk DSP and it would be disabled then we want to select a different DSP + if ((Case.getCurrentCase().getCaseType() == Case.CaseType.MULTI_USER_CASE) && selectedDsp.equals(LocalDiskDSProcessor.getType())) { + selectedDsp = ImageDSProcessor.getType(); + } createDataSourceProcessorButtons(); - //add actionlistner to listen for change } @@ -87,6 +91,7 @@ final class AddImageWizardSelectDspVisual extends JPanel { return selectedDsp; } + @NbBundle.Messages("AddImageWizardSelectDspVisual.multiUserWarning.text=This type of Data Source Processor is not available in multi-user mode") /** * Create the a button for each DataSourceProcessor that should exist as an * option. @@ -110,6 +115,7 @@ final class AddImageWizardSelectDspVisual extends JPanel { constraints.anchor = GridBagConstraints.LINE_START; Dimension spacerBlockDimension = new Dimension(6, 4); // Space between left edge and button, Space between rows for (String dspType : dspList) { + boolean shouldAddMultiUserWarning = false; constraints.weightx = 1; //Add a spacer Filler spacer = new Filler(spacerBlockDimension, spacerBlockDimension, spacerBlockDimension); @@ -120,6 +126,11 @@ final class AddImageWizardSelectDspVisual extends JPanel { //Add the button JToggleButton dspButton = createDspButton(dspType); dspButton.addActionListener(cbActionListener); + if ((Case.getCurrentCase().getCaseType() == Case.CaseType.MULTI_USER_CASE) && dspType.equals(LocalDiskDSProcessor.getType())){ + dspButton.setEnabled(false); //disable the button for local disk DSP when this is a multi user case + dspButton.setSelected(false); + shouldAddMultiUserWarning = true; + } jPanel1.add(dspButton); buttonGroup1.add(dspButton); gridBagLayout.setConstraints(dspButton, constraints); @@ -130,7 +141,14 @@ final class AddImageWizardSelectDspVisual extends JPanel { jPanel1.add(buttonTextSpacer); constraints.gridx++; //Add the text area serving as a label to the right of the button - JTextArea myLabel = new JTextArea(dspType); + + JTextArea myLabel = new JTextArea(); + if (shouldAddMultiUserWarning) { + myLabel.setText(dspType + " - " + NbBundle.getMessage(this.getClass(), "AddImageWizardSelectDspVisual.multiUserWarning.text")); + myLabel.setEnabled(false); //gray out the text + } else { + myLabel.setText(dspType); + } myLabel.setBackground(new Color(240, 240, 240));//matches background of panel myLabel.setEditable(false); myLabel.setWrapStyleWord(true); @@ -169,12 +187,7 @@ final class AddImageWizardSelectDspVisual extends JPanel { } } dspList.add(ImageDSProcessor.getType()); - if (Case.getCurrentCase().getCaseType() != Case.CaseType.MULTI_USER_CASE) { - dspList.add(LocalDiskDSProcessor.getType()); - } else { - // remove LocalDiskDSProcessor from list of DSPs - datasourceProcessorsMap.remove(LocalDiskDSProcessor.getType()); - } + dspList.add(LocalDiskDSProcessor.getType()); dspList.add(LocalFilesDSProcessor.getType()); dspList.add(RawDSProcessor.getType()); // now add any addtional DSPs that haven't already been added diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index 05c2693128..05588f5d1f 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties @@ -140,8 +140,8 @@ CasePropertiesForm.updateCaseName.msgDlg.empty.msg=The caseName cannot be empty. CasePropertiesForm.updateCaseName.msgDlg.empty.title=Error CasePropertiesForm.updateCaseName.msgDlg.invalidSymbols.msg=The Case Name cannot contain any of this following symbol\: \\ / \: * ? " < > | CasePropertiesForm.updateCaseName.msgDlg.invalidSymbols.title=Error -CasePropertiesForm.updateCaseName.confMsg.msg=Are you sure want to update the case name from "{0}" to "{1}"? -CasePropertiesForm.updateCaseName.confMsg.title=Create directory +CasePropertiesForm.updateCaseName.confMsg.msg=Are you sure you want to update the case name from "{0}" to "{1}"? +CasePropertiesForm.updateCaseName.confMsg.title=Change Case Name CueBannerPanel.title.text=Open Recent Case GeneralFilter.rawImageDesc.text=Raw Images (*.img, *.dd, *.001, *.aa, *.raw, *.bin) GeneralFilter.encaseImageDesc.text=Encase Images (*.e01) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties index 82efd7df67..68605bb1b8 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties @@ -104,8 +104,6 @@ CasePropertiesForm.updateCaseName.msgDlg.empty.msg=\u30b1\u30fc\u30b9\u540d\u306 CasePropertiesForm.updateCaseName.msgDlg.empty.title=\u30a8\u30e9\u30fc CasePropertiesForm.updateCaseName.msgDlg.invalidSymbols.msg=\u30b1\u30fc\u30b9\u540d\u306b\u306f\u6b21\u306e\u8a18\u53f7\u3092\u542b\u3081\u307e\u305b\u3093\uff1a\\ / \: * ? " < > | CasePropertiesForm.updateCaseName.msgDlg.invalidSymbols.title=\u30a8\u30e9\u30fc -CasePropertiesForm.updateCaseName.confMsg.msg=\u30b1\u30fc\u30b9\u540d\u3092"{0}"\u304b\u3089"{1}"\u306b\u672c\u5f53\u306b\u66f4\u65b0\u3057\u307e\u3059\u304b\uff1f -CasePropertiesForm.updateCaseName.confMsg.title=\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u4f5c\u6210 CueBannerPanel.title.text=\u6700\u8fd1\u958b\u3044\u305f\u30b1\u30fc\u30b9\u3092\u958b\u304f GeneralFilter.rawImageDesc.text=\u30ed\u30fc\u30a4\u30e1\u30fc\u30b8(*.img, *.dd, *.001, *.aa, *.raw, *.bin) GeneralFilter.encaseImageDesc.text=\u30a8\u30f3\u30b1\u30fc\u30b9\u30a4\u30e1\u30fc\u30b8(*.e01) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index e3ab78bf64..5facca3088 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -568,7 +568,8 @@ public class Case { * * IMPORTANT: This method should not be called in the event dispatch thread * (EDT). - * @throws CaseActionException + * + * @throws CaseActionException */ @Messages({ "# {0} - exception message", "Case.closeException.couldNotCloseCase=Error closing case: {0}", @@ -637,6 +638,7 @@ public class Case { "# {0} - exception message", "Case.deleteException.couldNotDeleteCase=Could not delete case: {0}", "Case.progressIndicatorTitle.deletingCase=Deleting Case", "Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first", + "Case.progressMessage.checkingForOtherUser=Checking to see if another user has the case open...", "Case.progressMessage.deletingTextIndex=Deleting text index...", "Case.progressMessage.deletingCaseDatabase=Deleting case database...", "Case.exceptionMessage.cancelled=Cancelled by user" @@ -672,7 +674,7 @@ public class Case { * First, acquire an exclusive case directory lock. The case * cannot be deleted if another node has it open. */ - progressIndicator.start(Bundle.Case_progressMessage_acquiringLocks()); + progressIndicator.start(Bundle.Case_progressMessage_checkingForOtherUser()); try (CoordinationService.Lock dirLock = CoordinationService.getInstance().tryGetExclusiveLock(CategoryNode.CASES, metadata.getCaseDirectory())) { assert (null != dirLock); @@ -723,8 +725,9 @@ public class Case { } /** - * Sanitizes the case name for use as a PostgreSQL database name and in - * ActiveMQ event channel (topic) names. + * Cleans up the display name for a case to make a suitable case name for + * use in case direcotry paths, coordination service locks, PostgreSQL + * database names, Active MQ message message channels, etc. * * PostgreSQL: * http://www.postgresql.org/docs/9.4/static/sql-syntax-lexical.html 63 @@ -741,7 +744,7 @@ public class Case { * * @throws org.sleuthkit.autopsy.casemodule.Case.IllegalCaseNameException */ - static String sanitizeCaseName(String caseName) throws IllegalCaseNameException { + public static String displayNameToCaseName(String caseName) throws IllegalCaseNameException { String result; @@ -1604,7 +1607,7 @@ public class Case { "Case.progressIndicatorTitle.creatingCase=Creating Case", "Case.progressIndicatorCancelButton.label=Cancel", "Case.progressMessage.preparing=Preparing...", - "Case.progressMessage.acquiringLocks=Preparing to open case resources.
This may take time if another user is upgrading the case." + "Case.progressMessage.openingCaseResources=Preparing to open case resources.
This may take time if another user is upgrading the case." }) private void open(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException { /* @@ -1613,7 +1616,7 @@ public class Case { */ String caseName; try { - caseName = sanitizeCaseName(caseDisplayName); + caseName = displayNameToCaseName(caseDisplayName); } catch (IllegalCaseNameException ex) { throw new CaseActionException(Bundle.Case_exceptionMessage_wrapperMessage(Bundle.Case_exceptionMessage_illegalCaseName()), ex); } @@ -1652,7 +1655,7 @@ public class Case { * First, acquire an exclusive case name lock to prevent two * nodes from creating the same case at the same time. */ - progressIndicator.start(Bundle.Case_progressMessage_acquiringLocks()); + progressIndicator.start(Bundle.Case_progressMessage_openingCaseResources()); try (CoordinationService.Lock nameLock = Case.acquireExclusiveCaseNameLock(caseName)) { assert (null != nameLock); /* @@ -1872,7 +1875,7 @@ public class Case { * as long as this node has this case open, in order to prevent * deletion of the case by another node. */ - progressIndicator.start(Bundle.Case_progressMessage_acquiringLocks()); + progressIndicator.start(Bundle.Case_progressMessage_openingCaseResources()); acquireSharedCaseDirLock(caseMetadata.getCaseDirectory()); /* * Next, acquire an exclusive case resources lock to ensure only @@ -2131,6 +2134,7 @@ public class Case { * @param progressIndicator A progress indicator. */ @Messages({ + "Case.progressMessage.closingCaseResources=Preparing to close case resources.
This may take time if another user is upgrading the case.", "Case.progressMessage.notifyingCaseEventSubscribers=Notifying case event subscribers...", "Case.progressMessage.clearingTempDirectory=Clearing case temp directory...", "Case.progressMessage.closingCaseLevelServices=Closing case-level services...", @@ -2139,7 +2143,6 @@ public class Case { "Case.progressMessage.closingCaseDatabase=Closing case database...", "Case.progressMessage.tearingDownTskErrorReporting=Tearing down SleuthKit error reporting..." }) - private void close() throws CaseActionException { /* * Set up either a GUI progress indicator or a logging progress @@ -2172,7 +2175,7 @@ public class Case { * node at a time can create/open/upgrade/close the case * resources. */ - progressIndicator.start(Bundle.Case_progressMessage_acquiringLocks()); + progressIndicator.start(Bundle.Case_progressMessage_closingCaseResources()); try (CoordinationService.Lock resourcesLock = acquireExclusiveCaseResourcesLock(caseMetadata.getCaseName())) { assert (null != resourcesLock); close(progressIndicator); @@ -2406,7 +2409,7 @@ public class Case { * An exception to throw when a case name with invalid characters is * encountered. */ - final static class IllegalCaseNameException extends Exception { + public final static class IllegalCaseNameException extends Exception { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardPanel1.java b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardPanel1.java index 737cbe8839..8ceda8fc2a 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardPanel1.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardPanel1.java @@ -204,17 +204,27 @@ class NewCaseWizardPanel1 implements WizardDescriptor.ValidatingPanel | - if (!Case.isValidName(caseName)) { + if (!Case.isValidName(caseDisplayName)) { String errorMsg = NbBundle .getMessage(this.getClass(), "NewCaseWizardPanel1.validate.errMsg.invalidSymbols"); validationError(errorMsg); } else { + String caseName = ""; + try { + caseName = Case.displayNameToCaseName(caseDisplayName); + } catch (Case.IllegalCaseNameException ex) { + String errorMsg = NbBundle + .getMessage(this.getClass(), "NewCaseWizardPanel1.validate.errMsg.invalidSymbols"); + validationError(errorMsg); + } + + String caseDirPath = caseParentDir + caseName; + // check if the directory exist if (new File(caseDirPath).exists()) { // throw a warning to enter new data or delete the existing directory diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java b/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java index 6d57f3daac..ddc0ccd1d5 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java @@ -177,7 +177,7 @@ public class SingleUserCaseConverter { // Create sanitized names for PostgreSQL and Solr SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss"); //NON-NLS Date date = new Date(); - String dbName = Case.sanitizeCaseName(icd.getNewCaseName()) + "_" + dateFormat.format(date); //NON-NLS + String dbName = Case.displayNameToCaseName(icd.getNewCaseName()) + "_" + dateFormat.format(date); //NON-NLS icd.setPostgreSQLDbName(dbName); // Copy items to new hostname folder structure diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java index b80642ee43..19933f8247 100755 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -137,6 +137,8 @@ public class ImageUtils { OPEN_CV_LOADED = openCVLoadedTemp; SUPPORTED_IMAGE_EXTENSIONS.addAll(Arrays.asList(ImageIO.getReaderFileSuffixes())); SUPPORTED_IMAGE_EXTENSIONS.add("tec"); // Add JFIF .tec files + SUPPORTED_IMAGE_EXTENSIONS.removeIf("db"::equals); // remove db files + SUPPORTED_IMAGE_MIME_TYPES = new TreeSet<>(Arrays.asList(ImageIO.getReaderMIMETypes())); /* * special cases and variants that we support, but don't get registered @@ -160,7 +162,7 @@ public class ImageUtils { private static FileTypeDetector fileTypeDetector; /** - *Thread/Executor that saves generated thumbnails to disk in the background + * Thread/Executor that saves generated thumbnails to disk in the background */ private static final Executor imageSaver = Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder() @@ -297,12 +299,27 @@ public class ImageUtils { * @param content the content to generate a thumbnail for * @param iconSize the size (one side of a square) in pixels to generate * - * @return a thumbnail for the given image or a default one if there was a + * @return A thumbnail for the given image or a default one if there was a * problem making a thumbnail. */ public static BufferedImage getThumbnail(Content content, int iconSize) { if (content instanceof AbstractFile) { AbstractFile file = (AbstractFile) content; + if (ImageUtils.isGIF(file)) { + /* + * Intercepting the image reading code for GIFs here allows us + * to rescale easily, but we lose animations. + */ + try (BufferedInputStream bufferedReadContentStream = getBufferedReadContentStream(file);) { + final BufferedImage image = ImageIO.read(bufferedReadContentStream); + if (image != null) { + return ScalrWrapper.resizeHighQuality(image, iconSize, iconSize); + } + } catch (IOException iOException) { + LOGGER.log(Level.WARNING, "Failed to get thumbnail for " + getContentPathSafe(content), iOException); //NON-NLS + } + return DEFAULT_THUMBNAIL; + } Task thumbnailTask = newGetThumbnailTask(file, iconSize, true); thumbnailTask.run(); @@ -310,11 +327,22 @@ public class ImageUtils { return SwingFXUtils.fromFXImage(thumbnailTask.get(), null); } catch (InterruptedException | ExecutionException ex) { LOGGER.log(Level.WARNING, "Failed to get thumbnail for " + getContentPathSafe(content), ex); //NON-NLS - return DEFAULT_THUMBNAIL; } - } else { - return DEFAULT_THUMBNAIL; } + return DEFAULT_THUMBNAIL; + } + + /** + * Get a BufferedInputStream wrapped around a ReadContentStream for the + * given AbstractFile. + * + * @param file The AbstractFile to get a stream for. + * + * @return A BufferedInputStream wrapped around a ReadContentStream for the + * given AbstractFile + */ + private static BufferedInputStream getBufferedReadContentStream(AbstractFile file) { + return new BufferedInputStream(new ReadContentInputStream(file)); } /** @@ -334,12 +362,12 @@ public class ImageUtils { } /** - * Get the location of the cached thumbnail for a file with the given fileID - * as a java File. The returned File may not exist on disk yet. + * Get the location,as a java File, of the cached thumbnail for an file with + * the given fileID . The returned File may not exist on disk yet. * * @param fileID the fileID to get the cached thumbnail location for * - * @return a File object representing the location of the cached thumbnail. + * @return A File object representing the location of the cached thumbnail. * This file may not actually exist(yet). Returns null if there was * any problem getting the file, such as no case was open. */ @@ -542,33 +570,30 @@ public class ImageUtils { * @see #getImageHeight(org.sleuthkit.datamodel.AbstractFile) */ private static T getImageProperty(AbstractFile file, final String errorTemplate, PropertyExtractor propertyExtractor) throws IOException { - try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(file));) { - try (ImageInputStream input = ImageIO.createImageInputStream(inputStream)) { - if (input == null) { - IIOException iioException = new IIOException("Could not create ImageInputStream."); - LOGGER.log(Level.WARNING, errorTemplate + iioException.toString(), getContentPathSafe(file)); - throw iioException; - } - Iterator readers = ImageIO.getImageReaders(input); - - if (readers.hasNext()) { - ImageReader reader = readers.next(); - reader.setInput(input); - try { - - return propertyExtractor.extract(reader); - } catch (IOException ex) { - LOGGER.log(Level.WARNING, errorTemplate + ex.toString(), getContentPathSafe(file)); - throw ex; - } finally { - reader.dispose(); - } - } else { - IIOException iioException = new IIOException("No ImageReader found."); - LOGGER.log(Level.WARNING, errorTemplate + iioException.toString(), getContentPathSafe(file)); - - throw iioException; + try (InputStream inputStream = getBufferedReadContentStream(file); + ImageInputStream input = ImageIO.createImageInputStream(inputStream)) { + if (input == null) { + IIOException iioException = new IIOException("Could not create ImageInputStream."); + LOGGER.log(Level.WARNING, errorTemplate + iioException.toString(), getContentPathSafe(file)); + throw iioException; + } + Iterator readers = ImageIO.getImageReaders(input); + + if (readers.hasNext()) { + ImageReader reader = readers.next(); + reader.setInput(input); + try { + return propertyExtractor.extract(reader); + } catch (IOException ex) { + LOGGER.log(Level.WARNING, errorTemplate + ex.toString(), getContentPathSafe(file)); + throw ex; + } finally { + reader.dispose(); } + } else { + IIOException iioException = new IIOException("No ImageReader found."); + LOGGER.log(Level.WARNING, errorTemplate + iioException.toString(), getContentPathSafe(file)); + throw iioException; } } } @@ -785,7 +810,7 @@ public class ImageUtils { protected javafx.scene.image.Image readImage() throws IOException { if (ImageUtils.isGIF(file)) { //use JavaFX to directly read GIF to preserve potential animation - javafx.scene.image.Image image = new javafx.scene.image.Image(new BufferedInputStream(new ReadContentInputStream(file))); + javafx.scene.image.Image image = new javafx.scene.image.Image(getBufferedReadContentStream(file)); if (image.isError() == false) { return image; } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java index 2b9fb9f2b8..972951ea99 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java @@ -90,6 +90,8 @@ public interface DisplayableItemNodeVisitor { T visit(KeywordHits.ListNode khsn); T visit(KeywordHits.TermNode khmln); + + T visit(KeywordHits.RegExpInstanceNode khmln); T visit(HashsetHits.RootNode hhrn); @@ -280,6 +282,11 @@ public interface DisplayableItemNodeVisitor { public T visit(KeywordHits.ListNode khsn) { return defaultVisit(khsn); } + + @Override + public T visit(KeywordHits.RegExpInstanceNode khsn) { + return defaultVisit(khsn); + } @Override public T visit(KeywordHits.TermNode khmln) { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java index c7b8983be0..72746663e7 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java @@ -64,17 +64,24 @@ public class KeywordHits implements AutopsyVisitableItem { public static final String SIMPLE_REGEX_SEARCH = NbBundle .getMessage(KeywordHits.class, "KeywordHits.singleRegexSearch.text"); private final KeywordResults keywordResults; - + private final String DUMMY_INSTANCE = "DUMMY_EXACT_MATCH_INSTANCE"; + public KeywordHits(SleuthkitCase skCase) { this.skCase = skCase; keywordResults = new KeywordResults(); } + + /* All of these maps and code assume the following: + * Regexps will have an 'instance' layer that shows the specific words that matched the regexp + * Exact match and substring will not have the instance layer and instead will have the specific hits + * below their term. + */ private final class KeywordResults extends Observable { - // Map from listName/Type to Map of keyword to set of artifact Ids + // Map from listName/Type to Map of keywords/regexp to Map of instance terms to Set of artifact Ids // NOTE: the map can be accessed by multiple worker threads and needs to be synchronized - private final Map>> topLevelMap = new LinkedHashMap<>(); + private final Map>>> topLevelMap = new LinkedHashMap<>(); KeywordResults() { update(); @@ -98,26 +105,66 @@ public class KeywordHits implements AutopsyVisitableItem { Collections.sort(keywords); return keywords; } - - Set getArtifactIds(String listName, String keyword) { + + List getKeywordInstances(String listName, String keyword) { + List instances; synchronized (topLevelMap) { - return topLevelMap.get(listName).get(keyword); + instances = new ArrayList<>(topLevelMap.get(listName).get(keyword).keySet()); + } + Collections.sort(instances); + return instances; + } + + Set getArtifactIds(String listName, String keyword, String keywordInstance) { + synchronized (topLevelMap) { + return topLevelMap.get(listName).get(keyword).get(keywordInstance); } } + void addRegExpToList(Map>> listMap, String regExp, String word, Long id) { + if (listMap.containsKey(regExp) == false) { + listMap.put(regExp, new LinkedHashMap<>()); + } + Map> instanceMap = listMap.get(regExp); + + // get or create keyword instances entry. + if (instanceMap.containsKey(word) == false) { + instanceMap.put(word, new HashSet<>()); + } + + // add this ID to the instance + instanceMap.get(word).add(id); + } + + void addExactMatchToList(Map>> listMap, String word, Long id) { + if (listMap.containsKey(word) == false) { + listMap.put(word, new LinkedHashMap<>()); + } + Map> instanceMap = listMap.get(word); + + // get or create keyword instances entry. + // for exact match, use a dummy instance + if (instanceMap.containsKey(DUMMY_INSTANCE) == false) { + instanceMap.put(DUMMY_INSTANCE, new HashSet<>()); + } + + // add this ID to the instance + instanceMap.get(DUMMY_INSTANCE).add(id); + } + // populate maps based on artifactIds void populateMaps(Map> artifactIds) { synchronized (topLevelMap) { topLevelMap.clear(); // map of list name to keword to artifact IDs - Map>> listsMap = new LinkedHashMap<>(); + Map>>> listsMap = new LinkedHashMap<>(); - // Map from from literal keyword to artifact IDs - Map> literalMap = new LinkedHashMap<>(); + // Map from from literal keyword to instances (which will be empty) to artifact IDs + Map>> literalMap = new LinkedHashMap<>(); - // Map from regex keyword artifact IDs - Map> regexMap = new LinkedHashMap<>(); + // Map from regex keyword artifact to instances to artifact IDs + Map>> regexMap = new LinkedHashMap<>(); // top-level nodes topLevelMap.put(SIMPLE_LITERAL_SEARCH, literalMap); @@ -131,34 +178,47 @@ public class KeywordHits implements AutopsyVisitableItem { String listName = attributes.get(Long.valueOf(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID())); String word = attributes.get(Long.valueOf(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD.getTypeID())); String reg = attributes.get(Long.valueOf(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP.getTypeID())); - + // new in 4.4 + String kwType = attributes.get(Long.valueOf(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE.getTypeID())); + // part of a list if (listName != null) { + // get or create list entry if (listsMap.containsKey(listName) == false) { - listsMap.put(listName, new LinkedHashMap>()); + listsMap.put(listName, new LinkedHashMap<>()); } - - Map> listMap = listsMap.get(listName); - if (listMap.containsKey(word) == false) { - listMap.put(word, new HashSet()); + Map>> listMap = listsMap.get(listName); + + // substring, treated same as exact match + // Enum for "1" is defined in KeywordSearch.java + if ((kwType != null) && (kwType.equals("1"))) { + // original term should be stored in reg + if (reg != null) { + addExactMatchToList(listMap, reg, id); + } else { + addExactMatchToList(listMap, word, id); + } + } + else if (reg != null) { + addRegExpToList(listMap, reg, word, id); + } else { + addExactMatchToList(listMap, word, id); } - - listMap.get(word).add(id); } // regular expression, single term else if (reg != null) { - if (regexMap.containsKey(reg) == false) { - regexMap.put(reg, new HashSet()); + // substring is treated same as exact + if ((kwType != null) && (kwType.equals("1"))) { + // original term should be stored in reg + addExactMatchToList(literalMap, reg, id); + } else { + addRegExpToList(regexMap, reg, word, id); } - regexMap.get(reg).add(id); } // literal, single term else { - if (literalMap.containsKey(word) == false) { - literalMap.put(word, new HashSet()); - } - literalMap.get(word).add(id); - } - topLevelMap.putAll(listsMap); + addExactMatchToList(literalMap, word, id); + } } + topLevelMap.putAll(listsMap); } setChanged(); @@ -167,35 +227,43 @@ public class KeywordHits implements AutopsyVisitableItem { @SuppressWarnings("deprecation") public void update() { + // maps Artifact ID to map of attribute types to attribute values Map> artifactIds = new LinkedHashMap<>(); if (skCase == null) { return; } + // query attributes table for the ones that we need for the tree int setId = BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID(); int wordId = BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD.getTypeID(); int regexId = BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP.getTypeID(); int artId = BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID(); - String query = "SELECT blackboard_attributes.value_text,blackboard_attributes.artifact_id," //NON-NLS + String query = "SELECT blackboard_attributes.value_text,blackboard_attributes.value_int32," + + "blackboard_attributes.artifact_id," //NON-NLS + "blackboard_attributes.attribute_type_id FROM blackboard_attributes,blackboard_artifacts WHERE " //NON-NLS + "(blackboard_attributes.artifact_id=blackboard_artifacts.artifact_id AND " //NON-NLS + "blackboard_artifacts.artifact_type_id=" + artId //NON-NLS + ") AND (attribute_type_id=" + setId + " OR " //NON-NLS + "attribute_type_id=" + wordId + " OR " //NON-NLS + + "attribute_type_id=" + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE.getTypeID() + " OR " //NON-NLS + "attribute_type_id=" + regexId + ")"; //NON-NLS try (CaseDbQuery dbQuery = skCase.executeQuery(query)) { ResultSet resultSet = dbQuery.getResultSet(); while (resultSet.next()) { - String value = resultSet.getString("value_text"); //NON-NLS + String valueStr = resultSet.getString("value_text"); //NON-NLS long artifactId = resultSet.getLong("artifact_id"); //NON-NLS long typeId = resultSet.getLong("attribute_type_id"); //NON-NLS if (!artifactIds.containsKey(artifactId)) { artifactIds.put(artifactId, new LinkedHashMap()); } - if (!value.equals("")) { - artifactIds.get(artifactId).put(typeId, value); + if (valueStr != null && !valueStr.equals("")) { + artifactIds.get(artifactId).put(typeId, valueStr); + } else { + // Keyword Search Type is an int + Long valueLong = resultSet.getLong("value_int32"); + artifactIds.get(artifactId).put(typeId, valueLong.toString()); } } } catch (TskCoreException | SQLException ex) { @@ -254,6 +322,9 @@ public class KeywordHits implements AutopsyVisitableItem { } } + /** + * Creates the list nodes + */ private class ListFactory extends ChildFactory.Detachable implements Observer { private final PropertyChangeListener pcl = new PropertyChangeListener() { @@ -344,6 +415,9 @@ public class KeywordHits implements AutopsyVisitableItem { } } + /** + * Represents the keyword search lists (or default groupings if list was not given) + */ public class ListNode extends DisplayableItemNode implements Observer { private final String listName; @@ -360,8 +434,10 @@ public class KeywordHits implements AutopsyVisitableItem { private void updateDisplayName() { int totalDescendants = 0; for (String word : keywordResults.getKeywords(listName)) { - Set ids = keywordResults.getArtifactIds(listName, word); - totalDescendants += ids.size(); + for (String instance : keywordResults.getKeywordInstances(listName, word)) { + Set ids = keywordResults.getArtifactIds(listName, word, instance); + totalDescendants += ids.size(); + } } super.setDisplayName(listName + " (" + totalDescendants + ")"); } @@ -409,6 +485,9 @@ public class KeywordHits implements AutopsyVisitableItem { } } + /** + * Creates the nodes that represent search terms + */ private class TermFactory extends ChildFactory.Detachable implements Observer { private final String setName; @@ -445,13 +524,16 @@ public class KeywordHits implements AutopsyVisitableItem { } } + /** + * Represents the search term or regexp that user searched for + */ public class TermNode extends DisplayableItemNode implements Observer { private final String setName; private final String keyword; public TermNode(String setName, String keyword) { - super(Children.create(new HitsFactory(setName, keyword), true), Lookups.singleton(keyword)); + super(Children.create(new RegExpInstancesFactory(setName, keyword), true), Lookups.singleton(keyword)); super.setName(keyword); this.setName = setName; this.keyword = keyword; @@ -461,7 +543,175 @@ public class KeywordHits implements AutopsyVisitableItem { } private void updateDisplayName() { - super.setDisplayName(keyword + " (" + keywordResults.getArtifactIds(setName, keyword).size() + ")"); + int totalDescendants = 0; + + for (String instance : keywordResults.getKeywordInstances(setName, keyword)) { + Set ids = keywordResults.getArtifactIds(setName, keyword, instance); + totalDescendants += ids.size(); + } + + super.setDisplayName(keyword + " (" + totalDescendants + ")"); + } + + @Override + public void update(Observable o, Object arg) { + updateDisplayName(); + } + + @Override + public boolean isLeafTypeNode() { + List instances = keywordResults.getKeywordInstances(setName, keyword); + // is this an exact match + if (instances.size() == 1 && instances.get(0).equals(DUMMY_INSTANCE)) { + return true; + } + else { + return false; + } + } + + @Override + public T accept(DisplayableItemNodeVisitor v) { + return v.visit(this); + } + + @Override + protected Sheet createSheet() { + Sheet s = super.createSheet(); + Sheet.Set ss = s.get(Sheet.PROPERTIES); + if (ss == null) { + ss = Sheet.createPropertiesSet(); + s.put(ss); + } + + ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.listName.name"), + NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.listName.displayName"), + NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.listName.desc"), + getDisplayName())); + + ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.filesWithHits.name"), + NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.filesWithHits.displayName"), + NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.filesWithHits.desc"), + keywordResults.getKeywordInstances(setName, keyword).size())); + + return s; + } + + @Override + public String getItemType() { + return getClass().getName(); + } + } + + // Allows us to pass in either longs or strings + // as they keys for different types of nodes at the + // same level. Probably a better way to do this, but + // it works. + class RegExpInstanceKey { + private final boolean isRegExp; + private String strKey; + private Long longKey; + public RegExpInstanceKey(String key) { + isRegExp = true; + strKey = key; + } + public RegExpInstanceKey(Long key) { + isRegExp = false; + longKey = key; + } + boolean isRegExp() { + return isRegExp; + } + Long getIdKey() { + return longKey; + } + String getRegExpKey() { + return strKey; + } + } + + /** + * Creates the nodes for a given regexp that represent the specific terms that were found + */ + public class RegExpInstancesFactory extends ChildFactory.Detachable implements Observer { + private final String keyword; + private final String setName; + + public RegExpInstancesFactory(String setName, String keyword) { + super(); + this.setName = setName; + this.keyword = keyword; + } + + @Override + protected void addNotify() { + keywordResults.addObserver(this); + } + + @Override + protected void removeNotify() { + keywordResults.deleteObserver(this); + } + + @Override + protected boolean createKeys(List list) { + List instances = keywordResults.getKeywordInstances(setName, keyword); + // The keys are different depending on what we are displaying. + // regexp get another layer to show instances. + // Exact matches don't. + if ((instances.size() == 1) && (instances.get(0).equals(DUMMY_INSTANCE))) { + for (Long id : keywordResults.getArtifactIds(setName, keyword, DUMMY_INSTANCE) ) { + list.add(new RegExpInstanceKey(id)); + } + } else { + for (String instance : instances) { + list.add(new RegExpInstanceKey(instance)); + } + + } + return true; + } + + @Override + protected Node createNodeForKey(RegExpInstanceKey key) { + // if it isn't not a regexp, then skip the 'instance' layer of the tree + if (key.isRegExp() == false) { + return createBlackboardArtifactNode(key.getIdKey()); + } else { + return new RegExpInstanceNode(setName, keyword, key.getRegExpKey()); + } + } + + @Override + public void update(Observable o, Object arg) { + refresh(true); + } + + } + + /** + * Represents a specific term that was found from a regexp + */ + public class RegExpInstanceNode extends DisplayableItemNode implements Observer { + + private final String setName; + private final String keyword; + private final String instance; + + public RegExpInstanceNode(String setName, String keyword, String instance) { + super(Children.create(new HitsFactory(setName, keyword, instance), true), Lookups.singleton(keyword)); + super.setName(keyword); + this.setName = setName; + this.keyword = keyword; + this.instance = instance; + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/keyword_hits.png"); //NON-NLS + updateDisplayName(); + keywordResults.addObserver(this); + } + + private void updateDisplayName() { + int totalDescendants = keywordResults.getArtifactIds(setName, keyword, instance).size(); + super.setDisplayName(instance + " (" + totalDescendants + ")"); } @Override @@ -496,7 +746,7 @@ public class KeywordHits implements AutopsyVisitableItem { ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.filesWithHits.name"), NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.filesWithHits.displayName"), NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.filesWithHits.desc"), - keywordResults.getArtifactIds(setName, keyword).size())); + keywordResults.getKeywordInstances(setName, keyword).size())); return s; } @@ -507,15 +757,76 @@ public class KeywordHits implements AutopsyVisitableItem { } } + /** + * Create a blackboard node for the given Keyword Hit artifact + * @param artifactId + * @return Node or null on error + */ + private BlackboardArtifactNode createBlackboardArtifactNode (Long artifactId) { + if (skCase == null) { + return null; + } + + try { + BlackboardArtifact art = skCase.getBlackboardArtifact(artifactId); + BlackboardArtifactNode n = new BlackboardArtifactNode(art); + AbstractFile file; + try { + file = skCase.getAbstractFileById(art.getObjectID()); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "TskCoreException while constructing BlackboardArtifact Node from KeywordHitsKeywordChildren"); //NON-NLS + return n; + } + + // It is possible to get a keyword hit on artifacts generated + // for the underlying image in which case MAC times are not + // available/applicable/useful. + if (file == null) { + return n; + } + + n.addNodeProperty(new NodeProperty<>( + NbBundle.getMessage(this.getClass(), "KeywordHits.createNodeForKey.modTime.name"), + NbBundle.getMessage(this.getClass(), + "KeywordHits.createNodeForKey.modTime.displayName"), + NbBundle.getMessage(this.getClass(), + "KeywordHits.createNodeForKey.modTime.desc"), + ContentUtils.getStringTime(file.getMtime(), file))); + n.addNodeProperty(new NodeProperty<>( + NbBundle.getMessage(this.getClass(), "KeywordHits.createNodeForKey.accessTime.name"), + NbBundle.getMessage(this.getClass(), + "KeywordHits.createNodeForKey.accessTime.displayName"), + NbBundle.getMessage(this.getClass(), + "KeywordHits.createNodeForKey.accessTime.desc"), + ContentUtils.getStringTime(file.getAtime(), file))); + n.addNodeProperty(new NodeProperty<>( + NbBundle.getMessage(this.getClass(), "KeywordHits.createNodeForKey.chgTime.name"), + NbBundle.getMessage(this.getClass(), + "KeywordHits.createNodeForKey.chgTime.displayName"), + NbBundle.getMessage(this.getClass(), + "KeywordHits.createNodeForKey.chgTime.desc"), + ContentUtils.getStringTime(file.getCtime(), file))); + return n; + } catch (TskException ex) { + logger.log(Level.WARNING, "TSK Exception occurred", ex); //NON-NLS + } + return null; + } + + /** + * Creates nodes for individual files that had hits + */ public class HitsFactory extends ChildFactory.Detachable implements Observer { private final String keyword; private final String setName; + private final String instance; - public HitsFactory(String setName, String keyword) { + public HitsFactory(String setName, String keyword, String instance) { super(); this.setName = setName; this.keyword = keyword; + this.instance = instance; } @Override @@ -530,60 +841,13 @@ public class KeywordHits implements AutopsyVisitableItem { @Override protected boolean createKeys(List list) { - list.addAll(keywordResults.getArtifactIds(setName, keyword)); + list.addAll(keywordResults.getArtifactIds(setName, keyword, instance)); return true; } @Override protected Node createNodeForKey(Long artifactId) { - if (skCase == null) { - return null; - } - - try { - BlackboardArtifact art = skCase.getBlackboardArtifact(artifactId); - BlackboardArtifactNode n = new BlackboardArtifactNode(art); - AbstractFile file; - try { - file = skCase.getAbstractFileById(art.getObjectID()); - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "TskCoreException while constructing BlackboardArtifact Node from KeywordHitsKeywordChildren"); //NON-NLS - return n; - } - - // It is possible to get a keyword hit on artifacts generated - // for the underlying image in which case MAC times are not - // available/applicable/useful. - if (file == null) { - return n; - } - - n.addNodeProperty(new NodeProperty<>( - NbBundle.getMessage(this.getClass(), "KeywordHits.createNodeForKey.modTime.name"), - NbBundle.getMessage(this.getClass(), - "KeywordHits.createNodeForKey.modTime.displayName"), - NbBundle.getMessage(this.getClass(), - "KeywordHits.createNodeForKey.modTime.desc"), - ContentUtils.getStringTime(file.getMtime(), file))); - n.addNodeProperty(new NodeProperty<>( - NbBundle.getMessage(this.getClass(), "KeywordHits.createNodeForKey.accessTime.name"), - NbBundle.getMessage(this.getClass(), - "KeywordHits.createNodeForKey.accessTime.displayName"), - NbBundle.getMessage(this.getClass(), - "KeywordHits.createNodeForKey.accessTime.desc"), - ContentUtils.getStringTime(file.getAtime(), file))); - n.addNodeProperty(new NodeProperty<>( - NbBundle.getMessage(this.getClass(), "KeywordHits.createNodeForKey.chgTime.name"), - NbBundle.getMessage(this.getClass(), - "KeywordHits.createNodeForKey.chgTime.displayName"), - NbBundle.getMessage(this.getClass(), - "KeywordHits.createNodeForKey.chgTime.desc"), - ContentUtils.getStringTime(file.getCtime(), file))); - return n; - } catch (TskException ex) { - logger.log(Level.WARNING, "TSK Exception occurred", ex); //NON-NLS - } - return null; + return createBlackboardArtifactNode(artifactId); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/framework/ProgressPanel.java b/Core/src/org/sleuthkit/autopsy/framework/ProgressPanel.java index 6d4e46cc42..4957a317b6 100644 --- a/Core/src/org/sleuthkit/autopsy/framework/ProgressPanel.java +++ b/Core/src/org/sleuthkit/autopsy/framework/ProgressPanel.java @@ -28,24 +28,25 @@ class ProgressPanel extends javax.swing.JPanel { ProgressPanel() { initComponents(); this.progressBar.setMinimum(0); + this.progressBar.setIndeterminate(true); } void setMessage(String message) { this.progressMessage.setText(message); - } - + } + void setInderminate(boolean indeterminate) { this.progressBar.setIndeterminate(indeterminate); } - + void setMaximum(int max) { this.progressBar.setMaximum(max); } - + void setCurrent(int current) { this.progressBar.setValue(current); } - + /** * 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 diff --git a/Core/src/org/sleuthkit/autopsy/modules/android/CallLogAnalyzer.java b/Core/src/org/sleuthkit/autopsy/modules/android/CallLogAnalyzer.java index 8075d8c3fe..27129456e2 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/android/CallLogAnalyzer.java +++ b/Core/src/org/sleuthkit/autopsy/modules/android/CallLogAnalyzer.java @@ -25,7 +25,9 @@ import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.logging.Level; import org.openide.util.NbBundle.Messages; @@ -36,6 +38,8 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.autopsy.ingest.IngestJobContext; +import org.sleuthkit.autopsy.ingest.IngestServices; +import org.sleuthkit.autopsy.ingest.ModuleDataEvent; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -52,6 +56,9 @@ class CallLogAnalyzer { private static final String moduleName = AndroidModuleFactory.getModuleName(); private static final Logger logger = Logger.getLogger(CallLogAnalyzer.class.getName()); private static Blackboard blackboard; + + private static final IngestServices services = IngestServices.getInstance(); + /** * the names of tables that potentially hold call logs in the dbs @@ -85,6 +92,8 @@ class CallLogAnalyzer { if (DatabasePath == null || DatabasePath.isEmpty()) { return; } + + Collection bbartifacts = new ArrayList<>(); try (Connection connection = DriverManager.getConnection("jdbc:sqlite:" + DatabasePath); //NON-NLS Statement statement = connection.createStatement();) { @@ -112,6 +121,8 @@ class CallLogAnalyzer { bba.addAttribute(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DIRECTION, moduleName, directionString)); bba.addAttribute(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_NAME, moduleName, name)); + bbartifacts.add(bba); + try { // index the artifact for keyword search blackboard.indexArtifact(bba); @@ -131,6 +142,13 @@ class CallLogAnalyzer { } catch (SQLException e) { logger.log(Level.SEVERE, "Could not parse call log; error connecting to db " + DatabasePath, e); //NON-NLS } + finally { + if (!bbartifacts.isEmpty()) { + services.fireModuleDataEvent(new ModuleDataEvent( + moduleName, + BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG, bbartifacts)); + } + } } private static enum CallDirection { diff --git a/Core/src/org/sleuthkit/autopsy/modules/android/ContactAnalyzer.java b/Core/src/org/sleuthkit/autopsy/modules/android/ContactAnalyzer.java index 58384f3235..17cc8ad2cc 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/android/ContactAnalyzer.java +++ b/Core/src/org/sleuthkit/autopsy/modules/android/ContactAnalyzer.java @@ -25,6 +25,8 @@ import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.logging.Level; import org.openide.util.NbBundle.Messages; @@ -35,6 +37,8 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.autopsy.ingest.IngestJobContext; +import org.sleuthkit.autopsy.ingest.IngestServices; +import org.sleuthkit.autopsy.ingest.ModuleDataEvent; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -49,6 +53,7 @@ class ContactAnalyzer { private static final String moduleName = AndroidModuleFactory.getModuleName(); private static final Logger logger = Logger.getLogger(ContactAnalyzer.class.getName()); + private static final IngestServices services = IngestServices.getInstance(); public static void findContacts(Content dataSource, FileManager fileManager, IngestJobContext context) { @@ -98,7 +103,8 @@ class ContactAnalyzer { logger.log(Level.SEVERE, "Error opening database", e); //NON-NLS return; } - + + Collection bbartifacts = new ArrayList<>(); try { // get display_name, mimetype(email or phone number) and data1 (phonenumber or email address depending on mimetype) //sorted by name, so phonenumber/email would be consecutive for a person if they exist. @@ -152,6 +158,8 @@ class ContactAnalyzer { } oldName = name; + bbartifacts.add(bba); + try { // index the artifact for keyword search blackboard.indexArtifact(bba); @@ -167,6 +175,12 @@ class ContactAnalyzer { } catch (TskCoreException e) { logger.log(Level.SEVERE, "Error posting to blackboard", e); //NON-NLS } finally { + if (!bbartifacts.isEmpty()) { + services.fireModuleDataEvent(new ModuleDataEvent( + moduleName, + BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT, bbartifacts)); + } + try { if (resultSet != null) { resultSet.close(); diff --git a/Core/src/org/sleuthkit/autopsy/modules/android/TextMessageAnalyzer.java b/Core/src/org/sleuthkit/autopsy/modules/android/TextMessageAnalyzer.java index 2971f07fed..979291d5d3 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/android/TextMessageAnalyzer.java +++ b/Core/src/org/sleuthkit/autopsy/modules/android/TextMessageAnalyzer.java @@ -24,6 +24,8 @@ import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.logging.Level; import org.openide.util.NbBundle; @@ -35,6 +37,8 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.autopsy.ingest.IngestJobContext; +import org.sleuthkit.autopsy.ingest.IngestServices; +import org.sleuthkit.autopsy.ingest.ModuleDataEvent; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -48,6 +52,7 @@ class TextMessageAnalyzer { private static final String moduleName = AndroidModuleFactory.getModuleName(); private static final Logger logger = Logger.getLogger(TextMessageAnalyzer.class.getName()); + private static final IngestServices services = IngestServices.getInstance(); private static Blackboard blackboard; public static void findTexts(Content dataSource, FileManager fileManager, @@ -88,6 +93,7 @@ class TextMessageAnalyzer { return; } + Collection bbartifacts = new ArrayList<>(); try { resultSet = statement.executeQuery( "SELECT address,date,read,type,subject,body FROM sms;"); //NON-NLS @@ -127,6 +133,8 @@ class TextMessageAnalyzer { NbBundle.getMessage(TextMessageAnalyzer.class, "TextMessageAnalyzer.bbAttribute.smsMessage"))); + bbartifacts.add(bba); + try { // index the artifact for keyword search blackboard.indexArtifact(bba); @@ -139,6 +147,12 @@ class TextMessageAnalyzer { } catch (Exception e) { logger.log(Level.SEVERE, "Error parsing text messages to Blackboard", e); //NON-NLS } finally { + if (!bbartifacts.isEmpty()) { + services.fireModuleDataEvent(new ModuleDataEvent( + moduleName, + BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE, bbartifacts)); + } + try { if (resultSet != null) { resultSet.close(); diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetPanel.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetPanel.java index 02d3fb82e5..962c18c8e1 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetPanel.java @@ -29,7 +29,7 @@ import org.sleuthkit.autopsy.modules.interestingitems.FilesSetDefsPanel.PANEL_TY */ public class FilesSetPanel extends javax.swing.JPanel { - @NbBundle.Messages({"FilesSetPanel.ingest.title=File Ingest Filter", "FilesSetPanel.ingest.createNewFilter=Create/edit file ingest filter(s)...", "FilesSetPanel.ingest.messages.filtersMustBeNamed=File ingest filters must be named."}) + @NbBundle.Messages({"FilesSetPanel.ingest.title=File Ingest Filter", "FilesSetPanel.ingest.createNewFilter=Create/edit file ingest filters...", "FilesSetPanel.ingest.messages.filtersMustBeNamed=File ingest filters must be named."}) private static final String CREATE_NEW_FILE_INGEST_FILTER = Bundle.FilesSetPanel_ingest_createNewFilter(); private final String mustBeNamedErrorText; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index 07254c247c..dd370b0534 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -68,6 +68,7 @@ import org.apache.solr.client.solrj.impl.HttpSolrServer; import org.openide.util.Lookup; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case.CaseType; +import org.sleuthkit.autopsy.casemodule.Case.IllegalCaseNameException; import org.sleuthkit.autopsy.casemodule.CaseActionException; import org.sleuthkit.autopsy.casemodule.CaseMetadata; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; @@ -200,6 +201,12 @@ public final class AutoIngestManager extends Observable implements PropertyChang casesToManifests = new HashMap<>(); pendingJobs = new ArrayList<>(); completedJobs = new ArrayList<>(); + try { + RuntimeProperties.setRunningWithGUI(false); + SYS_LOGGER.log(Level.INFO, "Set running with desktop GUI runtime property to false"); + } catch (RuntimeProperties.RuntimePropertiesException ex) { + SYS_LOGGER.log(Level.SEVERE, "Failed to set running with desktop GUI runtime property to false", ex); + } } /** @@ -229,13 +236,6 @@ public final class AutoIngestManager extends Observable implements PropertyChang jobProcessingTaskFuture = jobProcessingExecutor.submit(jobProcessingTask); jobStatusPublishingExecutor.scheduleAtFixedRate(new PeriodicJobStatusEventTask(), JOB_STATUS_EVENT_INTERVAL_SECONDS, JOB_STATUS_EVENT_INTERVAL_SECONDS, TimeUnit.SECONDS); eventPublisher.addSubscriber(EVENT_LIST, instance); - try { - RuntimeProperties.setRunningWithGUI(false); - SYS_LOGGER.log(Level.INFO, "Set running with desktop GUI runtime property to false"); - } catch (RuntimeProperties.RuntimePropertiesException ex) { - SYS_LOGGER.log(Level.SEVERE, "Failed to set running with desktop GUI runtime property to false", ex); - throw new AutoIngestManagerStartupException("Failed to set running with desktop GUI runtime property to false", ex); - } state = State.RUNNING; errorState = ErrorState.NONE; } @@ -1923,17 +1923,23 @@ public final class AutoIngestManager extends Observable implements PropertyChang */ private Case openCase() throws CoordinationServiceException, CaseManagementException, InterruptedException { Manifest manifest = currentJob.getManifest(); - String caseName = manifest.getCaseName(); - SYS_LOGGER.log(Level.INFO, "Opening case {0} for {1}", new Object[]{caseName, manifest.getFilePath()}); + String caseDisplayName = manifest.getCaseName(); + String caseName; + try { + caseName = Case.displayNameToCaseName(caseDisplayName); + } catch (IllegalCaseNameException ex) { + throw new CaseManagementException(String.format("Error creating or opening case %s for %s", manifest.getCaseName(), manifest.getFilePath()), ex); + } + SYS_LOGGER.log(Level.INFO, "Opening case {0} ({1}) for {2}", new Object[]{caseDisplayName, caseName, manifest.getFilePath()}); currentJob.setStage(AutoIngestJob.Stage.OPENING_CASE); try { Path caseDirectoryPath = PathUtils.findCaseDirectory(rootOutputDirectory, caseName); if (null != caseDirectoryPath) { - Path metadataFilePath = caseDirectoryPath.resolve(manifest.getCaseName() + CaseMetadata.getFileExtension()); + Path metadataFilePath = caseDirectoryPath.resolve(caseName + CaseMetadata.getFileExtension()); Case.openAsCurrentCase(metadataFilePath.toString()); } else { caseDirectoryPath = PathUtils.createCaseFolderPath(rootOutputDirectory, caseName); - Case.createAsCurrentCase(caseDirectoryPath.toString(), currentJob.getManifest().getCaseName(), "", "", CaseType.MULTI_USER_CASE); + Case.createAsCurrentCase(caseDirectoryPath.toString(), caseName, "", "", CaseType.MULTI_USER_CASE); /* * Sleep a bit before releasing the lock to ensure that the * new case folder is visible on the network. @@ -1946,13 +1952,13 @@ public final class AutoIngestManager extends Observable implements PropertyChang return caseForJob; } catch (CaseActionException ex) { - throw new CaseManagementException(String.format("Error creating or opening case %s for %s", manifest.getCaseName(), manifest.getFilePath()), ex); + throw new CaseManagementException(String.format("Error creating or opening case %s (%s) for %s", manifest.getCaseName(), caseName, manifest.getFilePath()), ex); } catch (IllegalStateException ex) { /* * Deal with the unfortunate fact that Case.getCurrentCase * throws IllegalStateException. */ - throw new CaseManagementException(String.format("Error getting current case %s for %s", manifest.getCaseName(), manifest.getFilePath()), ex); + throw new CaseManagementException(String.format("Error getting current case %s (%s) for %s", caseName, manifest.getCaseName(), manifest.getFilePath()), ex); } } @@ -2345,7 +2351,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang throw new AnalysisStartupException("Ingest manager error starting job", ingestJobStartResult.getStartupException()); } } else { - for (String warning : ingestJobSettings.getWarnings()) { + for (String warning : settingsWarnings) { SYS_LOGGER.log(Level.SEVERE, "Ingest job settings error for {0}: {1}", new Object[]{manifestPath, warning}); } currentJob.setErrorsOccurred(true); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PathUtils.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PathUtils.java index 852ab4714f..9b99e8c13f 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PathUtils.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PathUtils.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2015 Basis Technology Corp. + * Copyright 2013-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,6 +26,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.CaseMetadata; import org.sleuthkit.autopsy.casemodule.GeneralFilter; @@ -45,12 +46,18 @@ final class PathUtils { * @return The path of the case folder, or null if it is not found. */ static Path findCaseDirectory(Path folderToSearch, String caseName) { + String sanitizedCaseName; + try { + sanitizedCaseName = Case.displayNameToCaseName(caseName); + } catch (Case.IllegalCaseNameException unused) { + return null; + } File searchFolder = new File(folderToSearch.toString()); if (!searchFolder.isDirectory()) { return null; } Path caseFolderPath = null; - String[] candidateFolders = searchFolder.list(new CaseFolderFilter(caseName)); + String[] candidateFolders = searchFolder.list(new CaseFolderFilter(sanitizedCaseName)); long mostRecentModified = 0; for (String candidateFolder : candidateFolders) { File file = new File(candidateFolder); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index f85b5a4050..6e6bcf3d8b 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-16 Basis Technology Corp. + * Copyright 2011-17 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -182,7 +182,7 @@ public final class ImageGalleryController implements Executor { return groupManager; } - public DrawableDB getDatabase() { + synchronized public DrawableDB getDatabase() { return db; } @@ -306,7 +306,7 @@ public final class ImageGalleryController implements Executor { "ImageGalleryController.noGroupsDlg.msg6=There are no fully analyzed groups to display:" + " the current Group By setting resulted in no groups, " + "or no groups are fully analyzed but ingest is not running."}) - public void checkForGroups() { + synchronized private void checkForGroups() { if (groupManager.getAnalyzedGroups().isEmpty()) { if (IngestManager.getInstance().isIngestRunning()) { if (listeningEnabled.get() == false) { @@ -951,13 +951,15 @@ public final class ImageGalleryController implements Executor { if (isListeningEnabled()) { if (file.isFile()) { try { - if (ImageGalleryModule.isDrawableAndNotKnown(file)) { - //this file should be included and we don't already know about it from hash sets (NSRL) - queueDBWorkerTask(new UpdateFileTask(file, db)); - } else if (FileTypeUtils.getAllSupportedExtensions().contains(file.getNameExtension())) { - //doing this check results in fewer tasks queued up, and faster completion of db update - //this file would have gotten scooped up in initial grab, but actually we don't need it - queueDBWorkerTask(new RemoveFileTask(file, db)); + synchronized (ImageGalleryController.this) { + if (ImageGalleryModule.isDrawableAndNotKnown(file)) { + //this file should be included and we don't already know about it from hash sets (NSRL) + queueDBWorkerTask(new UpdateFileTask(file, db)); + } else if (FileTypeUtils.getAllSupportedExtensions().contains(file.getNameExtension())) { + //doing this check results in fewer tasks queued up, and faster completion of db update + //this file would have gotten scooped up in initial grab, but actually we don't need it + queueDBWorkerTask(new RemoveFileTask(file, db)); + } } } catch (TskCoreException | FileTypeDetector.FileTypeDetectorInitException ex) { //TODO: What to do here? diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownToolbar.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownToolbar.java index 2d19342923..3272b9dad1 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownToolbar.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownToolbar.java @@ -132,7 +132,7 @@ class DropdownToolbar extends javax.swing.JPanel { } private void maybeShowListsPopup(MouseEvent evt) { - if (!active) { + if (!active || !listsButton.isEnabled()) { return; } if (evt != null && !SwingUtilities.isLeftMouseButton(evt)) { @@ -142,7 +142,7 @@ class DropdownToolbar extends javax.swing.JPanel { } private void maybeShowSearchPopup(MouseEvent evt) { - if (!active) { + if (!active || !searchDropButton.isEnabled()) { return; } if (evt != null && !SwingUtilities.isLeftMouseButton(evt)) { @@ -159,41 +159,50 @@ class DropdownToolbar extends javax.swing.JPanel { String changed = evt.getPropertyName(); if (changed.equals(Case.Events.CURRENT_CASE.toString())) { if (null != evt.getNewValue()) { + boolean disableSearch = false; /* * A case has been opened. */ try { Server server = KeywordSearch.getServer(); - Index indexInfo = server.getIndexInfo(); - if (server.coreIsOpen() && IndexFinder.getCurrentSolrVersion().equals(indexInfo.getSolrVersion())) { - /* - * Solr version is current, so check the Solr - * schema version and selectively enable the ad - * hoc search UI components. - */ - boolean schemaIsCurrent = IndexFinder.getCurrentSchemaVersion().equals(indexInfo.getSchemaVersion()); - listsButton.setEnabled(schemaIsCurrent); - searchDropButton.setEnabled(true); - dropPanel.setRegexSearchEnabled(schemaIsCurrent); - active = true; - } else { - /* - * Unsupported Solr version, disable the ad hoc - * search UI components. - */ - searchDropButton.setEnabled(false); - listsButton.setEnabled(false); - active = false; + if (server.coreIsOpen() == false) { + disableSearch = true; } - } catch (KeywordSearchModuleException ex) { + else { + Index indexInfo = server.getIndexInfo(); + if (IndexFinder.getCurrentSolrVersion().equals(indexInfo.getSolrVersion())) { + /* + * Solr version is current, so check the Solr + * schema version and selectively enable the ad + * hoc search UI components. + */ + boolean schemaIsCurrent = IndexFinder.getCurrentSchemaVersion().equals(indexInfo.getSchemaVersion()); + listsButton.setEnabled(schemaIsCurrent); + searchDropButton.setEnabled(true); + dropPanel.setRegexSearchEnabled(schemaIsCurrent); + active = true; + } else { + /* + * Unsupported Solr version, disable the ad hoc + * search UI components. + */ + disableSearch = true; + } + } + } catch (NoOpenCoreException ex) { /* * Error, disable the ad hoc search UI components. */ logger.log(Level.SEVERE, "Error getting text index info", ex); //NON-NLS + disableSearch = true; + } + + if (disableSearch) { searchDropButton.setEnabled(false); listsButton.setEnabled(false); active = false; } + } else { /* * A case has been closed. diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java index a111bc877a..47c71a8213 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java @@ -327,6 +327,9 @@ public class ExtractedContentViewer implements DataContentViewer { */ private boolean solrHasContent(Long objectId) { final Server solrServer = KeywordSearch.getServer(); + if (solrServer.coreIsOpen() == false) + return false; + try { return solrServer.queryIsIndexed(objectId); } catch (NoOpenCoreException | KeywordSearchModuleException ex) { diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java index 9379898051..ad9e151e9e 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java @@ -227,7 +227,7 @@ class Ingester { solrServer.addDocument(updateDoc); uncommitedIngests = true; - } catch (KeywordSearchModuleException ex) { + } catch (KeywordSearchModuleException | NoOpenCoreException ex) { //JMTODO: does this need to be internationalized? throw new IngesterException( NbBundle.getMessage(Ingester.class, "Ingester.ingest.exception.err.msg", sourceName), ex); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearch.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearch.java index 65fc900569..b09ad5430e 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearch.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearch.java @@ -42,8 +42,9 @@ public class KeywordSearch { private static final Logger TIKA_LOGGER = Logger.getLogger("Tika"); //NON-NLS private static final org.sleuthkit.autopsy.coreutils.Logger logger = org.sleuthkit.autopsy.coreutils.Logger.getLogger(Case.class.getName()); + // @@@ We should move this into TskData (or somewhere) because we are using + // this value in the results tree to display substring differently from regexp (KeywordHit.java) public enum QueryType { - LITERAL, SUBSTRING, REGEX }; public static final String NUM_FILES_CHANGE_EVT = "NUM_FILES_CHANGE_EVT"; //NON-NLS diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java index 92e186bafe..6f3c11f8cb 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java @@ -166,7 +166,7 @@ public final class KeywordSearchIngestModule implements FileIngestModule { if (!IndexFinder.getCurrentSchemaVersion().equals(indexInfo.getSchemaVersion())) { throw new IngestModuleException(Bundle.KeywordSearchIngestModule_startupException_indexSchemaNotSupported(indexInfo.getSchemaVersion())); } - } catch (KeywordSearchModuleException ex) { + } catch (NoOpenCoreException ex) { throw new IngestModuleException(Bundle.KeywordSearchIngestModule_startupMessage_failedToGetIndexSchema(), ex); } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchQueryDelegator.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchQueryDelegator.java index af5381ba4b..f2dfb53622 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchQueryDelegator.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchQueryDelegator.java @@ -55,22 +55,7 @@ class KeywordSearchQueryDelegator { for (KeywordList keywordList : keywordLists) { for (Keyword keyword : keywordList.getKeywords()) { - KeywordSearchQuery query; - if (keyword.searchTermIsLiteral()) { - // literal, exact match - if (keyword.searchTermIsWholeWord()) { - query = new LuceneQuery(keywordList, keyword); - query.escape(); - } // literal, substring match - else { - query = new TermsComponentQuery(keywordList, keyword); - query.escape(); - query.setSubstringQuery(); - } - } // regexp - else { - query = new RegexQuery(keywordList, keyword); - } + KeywordSearchQuery query = KeywordSearchUtil.getQueryForKeyword(keyword, keywordList); queryDelegates.add(query); } } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchUtil.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchUtil.java index bb2a7a4111..cb272811b1 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchUtil.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchUtil.java @@ -126,6 +126,26 @@ class KeywordSearchUtil { return false; } } + + static KeywordSearchQuery getQueryForKeyword(Keyword keyword, KeywordList keywordList) { + KeywordSearchQuery query = null; + if (keyword.searchTermIsLiteral()) { + // literal, exact match + if (keyword.searchTermIsWholeWord()) { + query = new LuceneQuery(keywordList, keyword); + query.escape(); + } // literal, substring match + else { + query = new TermsComponentQuery(keywordList, keyword); + query.escape(); + query.setSubstringQuery(); + } + } // regexp + else { + query = new RegexQuery(keywordList, keyword); + } + return query; + } /** * Is the Keyword Search list at absPath an XML list? diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SearchRunner.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SearchRunner.java index ce0d07bc78..6db8b6beb3 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SearchRunner.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SearchRunner.java @@ -370,7 +370,7 @@ public final class SearchRunner { private List keywords; //keywords to search private List keywordListNames; // lists currently being searched private List keywordLists; - private Map keywordToList; //keyword to list name mapping + private Map keywordToList; //keyword to list name mapping private AggregateProgressHandle progressGroup; private final Logger logger = Logger.getLogger(SearchRunner.Searcher.class.getName()); private boolean finalRun = false; @@ -425,14 +425,13 @@ public final class SearchRunner { int keywordsSearched = 0; - for (Keyword keywordQuery : keywords) { + for (Keyword keyword : keywords) { if (this.isCancelled()) { - logger.log(Level.INFO, "Cancel detected, bailing before new keyword processed: {0}", keywordQuery.getSearchTerm()); //NON-NLS + logger.log(Level.INFO, "Cancel detected, bailing before new keyword processed: {0}", keyword.getSearchTerm()); //NON-NLS return null; } - final String queryStr = keywordQuery.getSearchTerm(); - final KeywordList list = keywordToList.get(queryStr); + final KeywordList keywordList = keywordToList.get(keyword); //new subProgress will be active after the initial query //when we know number of hits to start() with @@ -440,15 +439,7 @@ public final class SearchRunner { subProgresses[keywordsSearched - 1].finish(); } - KeywordSearchQuery keywordSearchQuery = null; - - boolean isRegex = !keywordQuery.searchTermIsLiteral(); - if (isRegex) { - keywordSearchQuery = new RegexQuery(list, keywordQuery); - } else { - keywordSearchQuery = new LuceneQuery(list, keywordQuery); - keywordSearchQuery.escape(); - } + KeywordSearchQuery keywordSearchQuery = KeywordSearchUtil.getQueryForKeyword(keyword, keywordList); // Filtering //limit search to currently ingested data sources @@ -462,14 +453,14 @@ public final class SearchRunner { try { queryResults = keywordSearchQuery.performQuery(); } catch (KeywordSearchModuleException | NoOpenCoreException ex) { - logger.log(Level.SEVERE, "Error performing query: " + keywordQuery.getSearchTerm(), ex); //NON-NLS - MessageNotifyUtil.Notify.error(Bundle.SearchRunner_query_exception_msg() + keywordQuery.getSearchTerm(), ex.getCause().getMessage()); + logger.log(Level.SEVERE, "Error performing query: " + keyword.getSearchTerm(), ex); //NON-NLS + MessageNotifyUtil.Notify.error(Bundle.SearchRunner_query_exception_msg() + keyword.getSearchTerm(), ex.getCause().getMessage()); //no reason to continue with next query if recovery failed //or wait for recovery to kick in and run again later //likely case has closed and threads are being interrupted return null; } catch (CancellationException e) { - logger.log(Level.INFO, "Cancel detected, bailing during keyword query: {0}", keywordQuery.getSearchTerm()); //NON-NLS + logger.log(Level.INFO, "Cancel detected, bailing during keyword query: {0}", keyword.getSearchTerm()); //NON-NLS return null; } @@ -487,14 +478,14 @@ public final class SearchRunner { int totalUnits = newResults.getKeywords().size(); subProgresses[keywordsSearched].start(totalUnits); int unitProgress = 0; - String queryDisplayStr = keywordQuery.getSearchTerm(); + String queryDisplayStr = keyword.getSearchTerm(); if (queryDisplayStr.length() > 50) { queryDisplayStr = queryDisplayStr.substring(0, 49) + "..."; } - subProgresses[keywordsSearched].progress(list.getName() + ": " + queryDisplayStr, unitProgress); + subProgresses[keywordsSearched].progress(keywordList.getName() + ": " + queryDisplayStr, unitProgress); // Create blackboard artifacts - newArtifacts = newResults.writeAllHitsToBlackBoard(null, subProgresses[keywordsSearched], this, list.getIngestMessages()); + newArtifacts = newResults.writeAllHitsToBlackBoard(null, subProgresses[keywordsSearched], this, keywordList.getIngestMessages()); } //if has results @@ -553,7 +544,7 @@ public final class SearchRunner { keywordLists.add(list); for (Keyword k : list.getKeywords()) { keywords.add(k); - keywordToList.put(k.getSearchTerm(), list); + keywordToList.put(k, list); } } } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java index 81b6d5f483..dc3e3b7a28 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java @@ -36,11 +36,9 @@ import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Date; import java.util.List; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.logging.Level; @@ -62,11 +60,9 @@ import org.apache.solr.common.SolrInputDocument; import org.apache.solr.common.util.NamedList; import org.openide.modules.InstalledFileLocator; import org.openide.modules.Places; -import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case.CaseType; -import org.sleuthkit.autopsy.casemodule.CaseMetadata; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ModuleSettings; @@ -241,7 +237,7 @@ public class Server { javaHome = System.getenv("JAVA_HOME"); // NON-NLS } - if (javaHome.isEmpty()) { + if (javaHome == null || javaHome.isEmpty()) { logger.log(Level.WARNING, "Java not found. Keyword search functionality may not work."); //NON-NLS } @@ -696,17 +692,16 @@ public class Server { } } - Index getIndexInfo() throws KeywordSearchModuleException { + Index getIndexInfo() throws NoOpenCoreException { currentCoreLock.readLock().lock(); try { - if (null != currentCore) { - return currentCore.getIndexInfo(); - } else { - throw new KeywordSearchModuleException("Cannot get text index info, no core is open"); + if (null == currentCore) { + throw new NoOpenCoreException(); } + return currentCore.getIndexInfo(); } finally { currentCoreLock.readLock().unlock(); - } + } } void closeCore() throws KeywordSearchModuleException { @@ -722,9 +717,12 @@ public class Server { } } - void addDocument(SolrInputDocument doc) throws KeywordSearchModuleException { + void addDocument(SolrInputDocument doc) throws KeywordSearchModuleException, NoOpenCoreException { currentCoreLock.readLock().lock(); try { + if (null == currentCore) { + throw new NoOpenCoreException(); + } currentCore.addDocument(doc); } finally { currentCoreLock.readLock().unlock(); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java index 8e7f89c6c5..63ee08c10c 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java @@ -179,7 +179,7 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService { "SolrSearch.checkingForLatestIndex.msg=Looking for text index with latest Solr and schema version", "SolrSearch.indentifyingIndex.msg=Identifying text index for upgrade", "SolrSearch.copyIndex.msg=Copying existing text index", - "SolrSearch.openCore.msg=Creating/Opening text index", + "SolrSearch.openCore.msg=Opening text index", "SolrSearch.complete.msg=Text index successfully opened"}) public void openCaseResources(CaseContext context) throws AutopsyServiceException { ProgressIndicator progress = context.getProgressIndicator(); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java index 7ecfdbe35a..c1149eee87 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java @@ -347,6 +347,7 @@ final class TermsComponentQuery implements KeywordSearchQuery { if (originalKeyword.getArtifactAttributeType() != ATTRIBUTE_TYPE.TSK_CARD_NUMBER) { attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD, MODULE_NAME, foundKeyword.getSearchTerm())); attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP, MODULE_NAME, originalKeyword.getSearchTerm())); + try { newArtifact = hit.getContent().newArtifact(ARTIFACT_TYPE.TSK_KEYWORD_HIT); diff --git a/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java b/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java index 112984b33b..d62dffd155 100755 --- a/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java +++ b/Testing/test/qa-functional/src/org/sleuthkit/autopsy/testing/RegressionTest.java @@ -25,6 +25,7 @@ import junit.framework.Test; import junit.framework.TestCase; import org.netbeans.jemmy.Timeouts; import org.netbeans.junit.NbModuleSuite; +import org.sleuthkit.autopsy.core.UserPreferences; /** * This test expects the following system properties to be set: img_path: The @@ -98,6 +99,7 @@ public class RegressionTest extends TestCase { public void setUp() { logger.info("######## " + AutopsyTestCases.getEscapedPath(System.getProperty("img_path")) + " #######"); Timeouts.setDefault("ComponentOperator.WaitComponentTimeout", 1000000); + UserPreferences.setNumberOfFileIngestThreads(1); //Let nightly test using 1 ingest thread to avoid ordering results in report of insertion to tsk tables } /** diff --git a/docs/doxygen-user/images/change_logical_file_set_display_name.PNG b/docs/doxygen-user/images/change_logical_file_set_display_name.PNG index 6219181804..08528e9fed 100755 Binary files a/docs/doxygen-user/images/change_logical_file_set_display_name.PNG and b/docs/doxygen-user/images/change_logical_file_set_display_name.PNG differ diff --git a/docs/doxygen-user/images/data-source-progress-bar.PNG b/docs/doxygen-user/images/data-source-progress-bar.PNG index 747031914e..9957cf7830 100755 Binary files a/docs/doxygen-user/images/data-source-progress-bar.PNG and b/docs/doxygen-user/images/data-source-progress-bar.PNG differ diff --git a/docs/doxygen-user/images/e01-verifier.PNG b/docs/doxygen-user/images/e01-verifier.PNG index 3f5d3c3faa..f7b2cdf9b1 100755 Binary files a/docs/doxygen-user/images/e01-verifier.PNG and b/docs/doxygen-user/images/e01-verifier.PNG differ diff --git a/docs/doxygen-user/images/extension-mismatch-detected-ingest-settings.PNG b/docs/doxygen-user/images/extension-mismatch-detected-ingest-settings.PNG index b0ab22c6d0..9fb7daa33c 100755 Binary files a/docs/doxygen-user/images/extension-mismatch-detected-ingest-settings.PNG and b/docs/doxygen-user/images/extension-mismatch-detected-ingest-settings.PNG differ diff --git a/docs/doxygen-user/images/hash-lookup.PNG b/docs/doxygen-user/images/hash-lookup.PNG index 960892b20b..33f9ae804a 100755 Binary files a/docs/doxygen-user/images/hash-lookup.PNG and b/docs/doxygen-user/images/hash-lookup.PNG differ diff --git a/docs/doxygen-user/images/interesting_files_ingest_settings.PNG b/docs/doxygen-user/images/interesting_files_ingest_settings.PNG index c5dc42e20e..854a495862 100755 Binary files a/docs/doxygen-user/images/interesting_files_ingest_settings.PNG and b/docs/doxygen-user/images/interesting_files_ingest_settings.PNG differ diff --git a/docs/doxygen-user/images/keyword-search-ingest-settings.PNG b/docs/doxygen-user/images/keyword-search-ingest-settings.PNG index b55eca62c5..d7c0b8e2fc 100755 Binary files a/docs/doxygen-user/images/keyword-search-ingest-settings.PNG and b/docs/doxygen-user/images/keyword-search-ingest-settings.PNG differ diff --git a/docs/doxygen-user/images/local-disk-data-source.PNG b/docs/doxygen-user/images/local-disk-data-source.PNG index 22fdc40ec5..d9893b7de7 100644 Binary files a/docs/doxygen-user/images/local-disk-data-source.PNG and b/docs/doxygen-user/images/local-disk-data-source.PNG differ diff --git a/docs/doxygen-user/images/profile-data-source-panel.PNG b/docs/doxygen-user/images/profile-data-source-panel.PNG index e22a412d1d..debdf66ad5 100644 Binary files a/docs/doxygen-user/images/profile-data-source-panel.PNG and b/docs/doxygen-user/images/profile-data-source-panel.PNG differ diff --git a/docs/doxygen-user/images/select-data-source-type.PNG b/docs/doxygen-user/images/select-data-source-type.PNG index 2205f07d7b..41be35ff74 100755 Binary files a/docs/doxygen-user/images/select-data-source-type.PNG and b/docs/doxygen-user/images/select-data-source-type.PNG differ diff --git a/docs/doxygen-user/images/select-ingest-modules.PNG b/docs/doxygen-user/images/select-ingest-modules.PNG index fe359cbc15..9941a0dfb3 100755 Binary files a/docs/doxygen-user/images/select-ingest-modules.PNG and b/docs/doxygen-user/images/select-ingest-modules.PNG differ diff --git a/docs/doxygen-user/images/unallocated_space_options.PNG b/docs/doxygen-user/images/unallocated_space_options.PNG index 2f85e41b3d..a72fcc6dd8 100644 Binary files a/docs/doxygen-user/images/unallocated_space_options.PNG and b/docs/doxygen-user/images/unallocated_space_options.PNG differ diff --git a/thunderbirdparser/release/modules/ext/java-libpst-1.0-SNAPSHOT.jar b/thirdparty/java-libpst/java-libpst-1.0-SNAPSHOT.jar old mode 100755 new mode 100644 similarity index 100% rename from thunderbirdparser/release/modules/ext/java-libpst-1.0-SNAPSHOT.jar rename to thirdparty/java-libpst/java-libpst-1.0-SNAPSHOT.jar diff --git a/thunderbirdparser/build.xml b/thunderbirdparser/build.xml index c6442bd82f..098ed50445 100644 --- a/thunderbirdparser/build.xml +++ b/thunderbirdparser/build.xml @@ -2,7 +2,61 @@ - + Builds, tests, and runs the project org.sleuthkit.autopsy.thunderbirdparser. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/thunderbirdparser/ivy.xml b/thunderbirdparser/ivy.xml new file mode 100644 index 0000000000..4bf1f47c46 --- /dev/null +++ b/thunderbirdparser/ivy.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/thunderbirdparser/ivysettings.xml b/thunderbirdparser/ivysettings.xml new file mode 100644 index 0000000000..c27d905255 --- /dev/null +++ b/thunderbirdparser/ivysettings.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/thunderbirdparser/nbproject/project.properties b/thunderbirdparser/nbproject/project.properties index fbdabd0fc1..e21eb352f8 100644 --- a/thunderbirdparser/nbproject/project.properties +++ b/thunderbirdparser/nbproject/project.properties @@ -1,7 +1,6 @@ -file.reference.apache-mime4j-core-0.8.0-SNAPSHOT-sources.jar=release/modules/ext/apache-mime4j-core-0.8.0-SNAPSHOT-sources.jar -file.reference.apache-mime4j-core-0.8.0-SNAPSHOT.jar=release/modules/ext/apache-mime4j-core-0.8.0-SNAPSHOT.jar -file.reference.apache-mime4j-mbox-iterator-0.8.0-SNAPSHOT-sources.jar=release/modules/ext/apache-mime4j-mbox-iterator-0.8.0-SNAPSHOT-sources.jar -file.reference.apache-mime4j-mbox-iterator-0.8.0-SNAPSHOT.jar=release/modules/ext/apache-mime4j-mbox-iterator-0.8.0-SNAPSHOT.jar +file.reference.apache-mime4j-core-0.8.0.jar=release/modules/ext/apache-mime4j-core-0.8.0.jar +file.reference.apache-mime4j-dom-0.8.0.jar=release/modules/ext/apache-mime4j-dom-0.8.0.jar +file.reference.apache-mime4j-mbox-iterator-0.8.0.jar=release/modules/ext/apache-mime4j-mbox-iterator-0.8.0.jar file.reference.java-libpst-1.0-SNAPSHOT.jar=release/modules/ext/java-libpst-1.0-SNAPSHOT.jar javac.source=1.8 javac.compilerargs=-Xlint -Xlint:-serial diff --git a/thunderbirdparser/nbproject/project.xml b/thunderbirdparser/nbproject/project.xml index 84f4663022..060c91a323 100644 --- a/thunderbirdparser/nbproject/project.xml +++ b/thunderbirdparser/nbproject/project.xml @@ -51,32 +51,20 @@ - ext/apache-mime4j-dom-0.8.0-SNAPSHOT-sources.jar - release/modules/ext/apache-mime4j-dom-0.8.0-SNAPSHOT-sources.jar + ext/apache-mime4j-core-0.8.0.jar + release/modules/ext/apache-mime4j-core-0.8.0.jar - ext/apache-mime4j-core-0.8.0-SNAPSHOT-sources.jar - release/modules/ext/apache-mime4j-core-0.8.0-SNAPSHOT-sources.jar - - - ext/apache-mime4j-mbox-iterator-0.8.0-SNAPSHOT.jar - release/modules/ext/apache-mime4j-mbox-iterator-0.8.0-SNAPSHOT.jar - - - ext/apache-mime4j-core-0.8.0-SNAPSHOT.jar - release/modules/ext/apache-mime4j-core-0.8.0-SNAPSHOT.jar - - - ext/apache-mime4j-dom-0.8.0-SNAPSHOT.jar - release/modules/ext/apache-mime4j-dom-0.8.0-SNAPSHOT.jar + ext/apache-mime4j-dom-0.8.0.jar + release/modules/ext/apache-mime4j-dom-0.8.0.jar ext/java-libpst-1.0-SNAPSHOT.jar release/modules/ext/java-libpst-1.0-SNAPSHOT.jar - ext/apache-mime4j-mbox-iterator-0.8.0-SNAPSHOT-sources.jar - release/modules/ext/apache-mime4j-mbox-iterator-0.8.0-SNAPSHOT-sources.jar + ext/apache-mime4j-mbox-iterator-0.8.0.jar + release/modules/ext/apache-mime4j-mbox-iterator-0.8.0.jar diff --git a/thunderbirdparser/release/modules/ext/apache-mime4j-core-0.8.0-SNAPSHOT-sources.jar b/thunderbirdparser/release/modules/ext/apache-mime4j-core-0.8.0-SNAPSHOT-sources.jar deleted file mode 100755 index 9e6f5b463e..0000000000 Binary files a/thunderbirdparser/release/modules/ext/apache-mime4j-core-0.8.0-SNAPSHOT-sources.jar and /dev/null differ diff --git a/thunderbirdparser/release/modules/ext/apache-mime4j-core-0.8.0-SNAPSHOT.jar b/thunderbirdparser/release/modules/ext/apache-mime4j-core-0.8.0-SNAPSHOT.jar deleted file mode 100755 index 0aff45bb7d..0000000000 Binary files a/thunderbirdparser/release/modules/ext/apache-mime4j-core-0.8.0-SNAPSHOT.jar and /dev/null differ diff --git a/thunderbirdparser/release/modules/ext/apache-mime4j-dom-0.8.0-SNAPSHOT-sources.jar b/thunderbirdparser/release/modules/ext/apache-mime4j-dom-0.8.0-SNAPSHOT-sources.jar deleted file mode 100755 index 206408ba73..0000000000 Binary files a/thunderbirdparser/release/modules/ext/apache-mime4j-dom-0.8.0-SNAPSHOT-sources.jar and /dev/null differ diff --git a/thunderbirdparser/release/modules/ext/apache-mime4j-dom-0.8.0-SNAPSHOT.jar b/thunderbirdparser/release/modules/ext/apache-mime4j-dom-0.8.0-SNAPSHOT.jar deleted file mode 100755 index 773ab544ca..0000000000 Binary files a/thunderbirdparser/release/modules/ext/apache-mime4j-dom-0.8.0-SNAPSHOT.jar and /dev/null differ diff --git a/thunderbirdparser/release/modules/ext/apache-mime4j-mbox-iterator-0.8.0-SNAPSHOT-sources.jar b/thunderbirdparser/release/modules/ext/apache-mime4j-mbox-iterator-0.8.0-SNAPSHOT-sources.jar deleted file mode 100755 index f7248bd03e..0000000000 Binary files a/thunderbirdparser/release/modules/ext/apache-mime4j-mbox-iterator-0.8.0-SNAPSHOT-sources.jar and /dev/null differ diff --git a/thunderbirdparser/release/modules/ext/apache-mime4j-mbox-iterator-0.8.0-SNAPSHOT.jar b/thunderbirdparser/release/modules/ext/apache-mime4j-mbox-iterator-0.8.0-SNAPSHOT.jar deleted file mode 100755 index bb7d15da61..0000000000 Binary files a/thunderbirdparser/release/modules/ext/apache-mime4j-mbox-iterator-0.8.0-SNAPSHOT.jar and /dev/null differ diff --git a/thunderbirdparser/release/modules/ext/apache-mime4j-project-0.8.0-SNAPSHOT.jar b/thunderbirdparser/release/modules/ext/apache-mime4j-project-0.8.0-SNAPSHOT.jar deleted file mode 100755 index 70beac46b5..0000000000 Binary files a/thunderbirdparser/release/modules/ext/apache-mime4j-project-0.8.0-SNAPSHOT.jar and /dev/null differ