diff --git a/Ghidra/Features/GhidraGo/src/main/java/ghidra/app/plugin/core/go/GhidraGoPlugin.java b/Ghidra/Features/GhidraGo/src/main/java/ghidra/app/plugin/core/go/GhidraGoPlugin.java index 8b0f488cda..dff030734b 100644 --- a/Ghidra/Features/GhidraGo/src/main/java/ghidra/app/plugin/core/go/GhidraGoPlugin.java +++ b/Ghidra/Features/GhidraGo/src/main/java/ghidra/app/plugin/core/go/GhidraGoPlugin.java @@ -22,23 +22,26 @@ import ghidra.app.CorePluginPackage; import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.core.go.ipc.GhidraGoListener; import ghidra.framework.main.*; -import ghidra.framework.model.ToolServices; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.util.PluginStatus; import ghidra.framework.protocol.ghidra.GhidraURL; import ghidra.util.Msg; -import ghidra.util.Swing; //@formatter:off @PluginInfo( category = PluginCategoryNames.COMMON, status = PluginStatus.UNSTABLE, packageName = CorePluginPackage.NAME, - shortDescription = "Listens for new GhidraURL's to launch using ToolServices", - description = "Polls the ghidraGo directory for any url files written by the GhidraGoClient and " + - "processes them in Ghidra", + shortDescription = "Listens for new GhidraURL's to launch using FrontEndTool's" + + " accept method", + description = "Polls the ghidraGo directory for any url files written by the " + + "GhidraGoSender and processes them in Ghidra", eventsConsumed = {ProjectPluginEvent.class}) //@formatter:on +/** + * Polls the ghidraGo directory located in the user's temporary directory for any url files written + * by the {@link GhidraGoSender} and processes them in Ghidra. + */ public class GhidraGoPlugin extends Plugin implements ApplicationLevelOnlyPlugin { private GhidraGoListener listener; @@ -69,7 +72,7 @@ public class GhidraGoPlugin extends Plugin implements ApplicationLevelOnlyPlugin else { try { listener = new GhidraGoListener((url) -> { - processGhidraURL(url); + accept(url); }); } catch (IOException e) { @@ -81,26 +84,14 @@ public class GhidraGoPlugin extends Plugin implements ApplicationLevelOnlyPlugin } /** - * If the active project is null, do nothing. - * Otherwise, try and open the url using {@link ToolServices} launchDefaultToolWithURL function. - * @param ghidraURL the GhidraURL to open. + * Accept the given url, which is then passed to the FrontEndTool to process. + * @param url a {@link GhidraURL} + * @return true if handled successfully, false otherwise. */ - private void processGhidraURL(URL ghidraURL) { - - Msg.info(this, "GhidraGo processing " + ghidraURL); - - try { - Msg.info(this, - "Accepting the resource at " + GhidraURL.getProjectURL(ghidraURL)); - Swing.runNow(() -> { - FrontEndTool frontEnd = AppInfo.getFrontEndTool(); - frontEnd.toFront(); - frontEnd.getToolServices().launchDefaultToolWithURL(ghidraURL); - }); - } - catch (IllegalArgumentException e) { - Msg.showError(this, null, "GhidraGo Unable to process GhidraURL", - "GhidraGo could not process " + ghidraURL, e); - } + public boolean accept(URL url) { + Msg.info(this, "GhidraGo accepting the resource at " + GhidraURL.getProjectURL(url)); + FrontEndTool frontEndTool = AppInfo.getFrontEndTool(); + frontEndTool.toFront(); + return frontEndTool.accept(url); } } diff --git a/Ghidra/Features/GhidraGo/src/test.slow/java/ghidra/app/plugin/core/go/GhidraGoPluginTest.java b/Ghidra/Features/GhidraGo/src/test.slow/java/ghidra/app/plugin/core/go/GhidraGoPluginTest.java index b05357e063..9ec72834a8 100644 --- a/Ghidra/Features/GhidraGo/src/test.slow/java/ghidra/app/plugin/core/go/GhidraGoPluginTest.java +++ b/Ghidra/Features/GhidraGo/src/test.slow/java/ghidra/app/plugin/core/go/GhidraGoPluginTest.java @@ -29,8 +29,7 @@ import ghidra.GhidraApplicationLayout; import ghidra.GhidraGo; import ghidra.app.plugin.core.go.ipc.CheckForFileProcessedRunnable; import ghidra.app.plugin.core.go.ipc.CheckForListenerRunnable; -import ghidra.framework.model.DomainFile; -import ghidra.framework.model.DomainFolder; +import ghidra.framework.model.*; import ghidra.framework.plugintool.PluginTool; import ghidra.framework.protocol.ghidra.GhidraURL; import ghidra.program.model.listing.Program; @@ -40,33 +39,40 @@ import ghidra.util.task.TaskMonitor; public class GhidraGoPluginTest extends AbstractGhidraHeadedIntegrationTest { + private final static String DIRECTORY_NAME = getTestDirectoryPath(); + private final static String ACTIVE_PROJECT = "active"; + private final static String INACTIVE_PROJECT = "inactive"; private TestEnv env; private PluginTool tool; private GhidraGo ghidraGo; - private URL url; + private Project inactiveProject; private GhidraApplicationLayout layout; @Before public void setUp() throws Exception { - env = new TestEnv(); + // clean up projects + ProjectTestUtils.deleteProject(DIRECTORY_NAME, ACTIVE_PROJECT); + ProjectTestUtils.deleteProject(DIRECTORY_NAME, INACTIVE_PROJECT); + + // create inactive project if it doesn't exist + ProjectTestUtils.getProject(DIRECTORY_NAME, INACTIVE_PROJECT).close(); + + // add program and folder to inactive project + inactiveProject = ProjectTestUtils.getProject(DIRECTORY_NAME, INACTIVE_PROJECT); + addProgramAndFolderToProject(inactiveProject); + inactiveProject.close(); + + // set up test env and add GhidraGoPlugin to the front end tool. + env = new TestEnv(ACTIVE_PROJECT); tool = env.getFrontEndTool(); tool.addPlugin(GhidraGoPlugin.class.getName()); showTool(tool); - - DomainFolder rootFolder = env.getProject().getProjectData().getRootFolder(); - - Program p = createNotepadProgram(); - - rootFolder.createFile("notepad", p, TaskMonitor.DUMMY); - - env.release(p); - - url = GhidraURL.makeURL(env.getProjectManager().getActiveProject().getProjectLocator(), - "/notepad", null); - layout = (GhidraApplicationLayout) createApplicationLayout(); + addProgramAndFolderToProject(env.getProject()); + + // initialize GhidraGo client ghidraGo = new GhidraGo(); CheckForFileProcessedRunnable.WAIT_FOR_PROCESSING_DELAY_MS = 1000; @@ -78,6 +84,14 @@ public class GhidraGoPluginTest extends AbstractGhidraHeadedIntegrationTest { CheckForListenerRunnable.WAIT_FOR_LISTENER_PERIOD_MS = 10; } + private void addProgramAndFolderToProject(Project p) throws Exception { + Program program = createNotepadProgram(); + DomainFolder rootFolder = p.getProjectData().getRootFolder(); + rootFolder.createFile("notepad", program, TaskMonitor.DUMMY); + rootFolder.createFolder("testFolder"); + + } + private Program createNotepadProgram() throws Exception { ClassicSampleX86ProgramBuilder builder = new ClassicSampleX86ProgramBuilder("notepad", false, this); @@ -87,11 +101,18 @@ public class GhidraGoPluginTest extends AbstractGhidraHeadedIntegrationTest { @After public void tearDown() { + ProjectTestUtils.deleteProject(DIRECTORY_NAME, ACTIVE_PROJECT); + ProjectTestUtils.deleteProject(DIRECTORY_NAME, INACTIVE_PROJECT); env.dispose(); } @Test - public void testProcessingUrl() throws Exception { + public void testLaunchingWithProgramUrl() throws Exception { + // given a valid local GhidraURL pointing to a program + URL url = GhidraURL.makeURL(env.getProjectManager().getActiveProject().getProjectLocator(), + "/notepad", null); + + // when ghidraGo is launched with the url Swing.runLater(() -> { try { ghidraGo.launch(layout, new String[] { url.toString() }); @@ -100,6 +121,8 @@ public class GhidraGoPluginTest extends AbstractGhidraHeadedIntegrationTest { // empty } }); + + // then the code browser should be launched waitForSwing(); waitFor(() -> Arrays.asList(tool.getToolServices().getRunningTools()) .stream() @@ -109,8 +132,9 @@ public class GhidraGoPluginTest extends AbstractGhidraHeadedIntegrationTest { .stream() .filter(p -> p.getName().equals("CodeBrowser")) .findFirst(); - assertTrue(cb.isPresent()); + + // and the domain file should be open in the code browser assertTrue(Arrays.asList(cb.get().getDomainFiles()) .stream() .map(DomainFile::getName) @@ -118,15 +142,114 @@ public class GhidraGoPluginTest extends AbstractGhidraHeadedIntegrationTest { } @Test - public void testLaunchingWithInvalidUrl() throws Exception { + public void testLaunchingWithProgramUrlForInactiveProject() throws Exception { + // given a valid local GhidraURL pointing to a program contained within the inactive project + URL url = GhidraURL.makeURL(inactiveProject.getProjectLocator(), "/notepad", null); + + try { + // when ghidraGo is launched with the url + Swing.runLater(() -> { + try { + ghidraGo.launch(layout, new String[] { url.toString() }); + } + catch (Exception e) { + // empty + } + }); + + // then the code browser should be launched + waitForSwing(); + waitFor(() -> Arrays.asList(tool.getToolServices().getRunningTools()) + .stream() + .map(PluginTool::getName) + .anyMatch(Predicate.isEqual("CodeBrowser"))); + Optional cb = Arrays.asList(tool.getToolServices().getRunningTools()) + .stream() + .filter(p -> p.getName().equals("CodeBrowser")) + .findFirst(); + assertTrue(cb.isPresent()); + + // and the domain file should be open in the code browser + assertTrue(Arrays.asList(cb.get().getDomainFiles()) + .stream() + .map(DomainFile::getName) + .anyMatch(Predicate.isEqual("notepad"))); + } + finally { + inactiveProject.close(); + } + + } + + @Test + public void testLaunchingWithFolderUrl() throws Exception { + // given a valid local GhidraURL pointing to a folder within the active project + URL url = GhidraURL.makeURL(env.getProjectManager().getActiveProject().getProjectLocator(), + "/testFolder", null); + + // when ghidraGo is launched with the url Swing.runLater(() -> { try { - ghidraGo.launch(layout, new String[] { "ghidra:/test" }); + ghidraGo.launch(layout, new String[] { url.toString() }); } catch (Exception e) { // empty } }); + + // then the project window should select the folder within the active project data panel + waitForSwing(); + + ProjectLocator[] projViews = env.getProject().getProjectViews(); + Assert.assertEquals(0, projViews.length); + } + + @Test + public void testLaunchingWithFolderUrlForInactiveProject() throws Exception { + // given a valid local GhidraURL pointing to a folder within an in-active project + URL url = + GhidraURL.makeURL(inactiveProject.getProjectLocator(), + "/testFolder", null); + + try { + // when ghidraGo is launched with the url + Swing.runLater(() -> { + try { + ghidraGo.launch(layout, new String[] { url.toString() }); + } + catch (Exception e) { + // empty + } + }); + + // then the project window should select the folder within the viewed project data panel + waitForSwing(); + ProjectLocator[] projViews = env.getProject().getProjectViews(); + Assert.assertEquals(1, projViews.length); + } + finally { + inactiveProject.close(); + } + + } + + @Test + public void testLaunchingWithResourceThatDoesNotExist() throws Exception { + // given a valid local GhidraURL pointing to a program that does not exist + URL url = GhidraURL.makeURL(env.getProjectManager().getActiveProject().getProjectLocator(), + "/test", null); + + // when ghidraGo is launched with the url + Swing.runLater(() -> { + try { + ghidraGo.launch(layout, new String[] { url.toString() }); + } + catch (Exception e) { + // empty + } + }); + + // then an error dialog should be displayed AbstractErrDialog err = waitForErrorDialog(); assertEquals("Content Not Found", err.getTitle()); } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/FolderLinkContentHandler.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/FolderLinkContentHandler.java index 0dccc72cf9..a58294c239 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/FolderLinkContentHandler.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/FolderLinkContentHandler.java @@ -24,6 +24,7 @@ import ghidra.framework.main.AppInfo; import ghidra.framework.model.*; import ghidra.framework.store.FileSystem; import ghidra.util.InvalidNameException; +import ghidra.util.Msg; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; @@ -45,8 +46,7 @@ public class FolderLinkContentHandler extends LinkHandler contentHandler = getContentHandler(); + if (contentHandler instanceof LinkHandler linkHandler) { + // must check local vs remote URL + if (!GhidraURL.isServerRepositoryURL(LinkHandler.getURL(folderItem))) { + throw new IOException("Local project link-file may not be versioned"); + } } - if (getContentHandler().isPrivateContentType()) { + else if (contentHandler.isPrivateContentType()) { throw new IOException("Content may not be versioned: " + getContentType()); } } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/LinkHandler.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/LinkHandler.java index ec3743ce55..0951a7319c 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/LinkHandler.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/LinkHandler.java @@ -165,8 +165,7 @@ public abstract class LinkHandler extends DBCon @Override public final boolean isPrivateContentType() { - // NOTE: URL must be checked - only repository-based links may be versioned - return true; + throw new UnsupportedOperationException("Link file requires checking server vs local URL"); } /** diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/LinkedGhidraFolder.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/LinkedGhidraFolder.java index 6193d4864b..865038d3b6 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/LinkedGhidraFolder.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/LinkedGhidraFolder.java @@ -34,8 +34,7 @@ public class LinkedGhidraFolder extends LinkedGhidraSubFolder { public static Icon FOLDER_LINK_CLOSED_ICON = new GIcon("icon.content.handler.linked.folder.closed"); - public static Icon FOLDER_LINK_OPEN_ICON = - new GIcon("icon.content.handler.linked.folder.open"); + public static Icon FOLDER_LINK_OPEN_ICON = new GIcon("icon.content.handler.linked.folder.open"); private final Project activeProject; private final DomainFolder localParent; @@ -73,8 +72,8 @@ public class LinkedGhidraFolder extends LinkedGhidraSubFolder { } /** - * Get the Ghidra URL associated with this linked folder's project or repository - * @return Ghidra URL associated with this linked folder's project or repository + * Get the Ghidra URL of the project/repository folder referenced by this object + * @return Ghidra URL of the project/repository folder referenced by this object */ public URL getProjectURL() { if (projectUrl == null) { @@ -83,6 +82,7 @@ public class LinkedGhidraFolder extends LinkedGhidraSubFolder { return projectUrl; } + @Override LinkedGhidraFolder getLinkedRootFolder() { return this; } @@ -108,12 +108,12 @@ public class LinkedGhidraFolder extends LinkedGhidraSubFolder { @Override public ProjectLocator getProjectLocator() { - return activeProject.getProjectLocator(); + return localParent.getProjectLocator(); } @Override public ProjectData getProjectData() { - return activeProject.getProjectData(); + return localParent.getProjectData(); } @Override diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/LinkedGhidraSubFolder.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/LinkedGhidraSubFolder.java index 1ac8db6833..05c07f1aad 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/LinkedGhidraSubFolder.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/LinkedGhidraSubFolder.java @@ -28,6 +28,10 @@ import ghidra.util.*; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; +/** + * {@code LinkedGhidraSubFolder} corresponds to a {@link DomainFolder} contained within a + * {@link LinkedGhidraFolder} or another {@code LinkedGhidraSubFolder}. + */ class LinkedGhidraSubFolder implements LinkedDomainFolder { private final LinkedGhidraFolder linkedRootFolder; diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/AcceptUrlContentTask.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/AcceptUrlContentTask.java new file mode 100644 index 0000000000..355332770b --- /dev/null +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/AcceptUrlContentTask.java @@ -0,0 +1,122 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.framework.main; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.Set; + +import ghidra.framework.data.FolderLinkContentHandler; +import ghidra.framework.model.*; +import ghidra.framework.protocol.ghidra.GhidraURLQueryTask; +import ghidra.util.Msg; +import ghidra.util.Swing; +import ghidra.util.task.TaskMonitor; + +public class AcceptUrlContentTask extends GhidraURLQueryTask { + + private FrontEndPlugin plugin; + + public AcceptUrlContentTask(URL url, FrontEndPlugin plugin) { + super("Accepting URL", url); + this.plugin = plugin; + } + + private boolean isSameLocalProject(ProjectLocator projectLoc1, ProjectLocator projectLoc2) { + if (projectLoc1.isTransient() || projectLoc2.isTransient()) { + return false; + } + if (!projectLoc1.getName().equals(projectLoc2.getName())) { + return false; + } + try { + File proj1Dir = projectLoc1.getProjectDir().getCanonicalFile(); + File proj2Dir = projectLoc2.getProjectDir().getCanonicalFile(); + return proj1Dir.equals(proj2Dir); + } + catch (IOException e) { + return false; + } + } + + @Override + public void processResult(DomainFile domainFile, URL url, TaskMonitor monitor) + throws IOException { + + Project activeProject = AppInfo.getActiveProject(); + if (activeProject == null) { + Msg.showError(this, null, "Ghidra Error", + "Unable to accept URL without active project open"); + return; + } + + Swing.runNow(() -> { + if (FolderLinkContentHandler.FOLDER_LINK_CONTENT_TYPE + .equals(domainFile.getContentType())) { + // Simply select folder link-file within project - do not follow - let user do that. + if (isSameLocalProject(activeProject.getProjectLocator(), + domainFile.getProjectLocator())) { + // Select file within active project + DomainFile df = + activeProject.getProjectData().getFile(domainFile.getPathname()); + if (df == null) { + return; // unexpected race condition + } + plugin.selectFiles(Set.of(df)); + } + else { + // Select file within read-only viewed project + plugin.showInViewedProject(url, false); + } + } + else { + AppInfo.getFrontEndTool().getToolServices().launchDefaultToolWithURL(url); + } + }); + } + + @Override + public void processResult(DomainFolder domainFolder, URL url, TaskMonitor monitor) + throws IOException { + + Project activeProject = AppInfo.getActiveProject(); + if (activeProject == null) { + Msg.showError(this, null, "Ghidra Error", + "Unable to accept URL without active project open"); + return; + } + + Swing.runNow(() -> { + if (isSameLocalProject(activeProject.getProjectLocator(), + domainFolder.getProjectLocator())) { + // Select folder within active project + DomainFolder df = + activeProject.getProjectData().getFolder(domainFolder.getPathname()); + if (df == null) { + return; // unexpected race condition + } + plugin.selectFolder(df); + } + else { + // Select folder within read-only viewed project + plugin.showInViewedProject(url, true); + } + }); + + } + +} diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/FrontEndPlugin.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/FrontEndPlugin.java index 085a147eeb..4618dccd6f 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/FrontEndPlugin.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/FrontEndPlugin.java @@ -51,6 +51,7 @@ import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.util.PluginStatus; import ghidra.framework.preferences.Preferences; +import ghidra.framework.protocol.ghidra.GhidraURL; import ghidra.framework.remote.User; import ghidra.util.*; import ghidra.util.filechooser.GhidraFileChooserModel; @@ -539,8 +540,22 @@ public class FrontEndPlugin extends Plugin SwingUtilities.invokeLater(() -> { // there was a delete bug; make the set unmodifiable to catch this earlier Set unmodifiableFiles = Collections.unmodifiableSet(files); + if (dataTablePanel.isCapacityExceeded()) { + projectDataPanel.showTree(); + } + else { + dataTablePanel.setSelectedDomainFiles(unmodifiableFiles); + } dataTreePanel.selectDomainFiles(unmodifiableFiles); - dataTablePanel.setSelectedDomainFiles(unmodifiableFiles); + }); + } + + void selectFolder(final DomainFolder folder) { + // Do this later in case any of the given files are newly created, which means that the + // GUIs may have not yet been notified. + SwingUtilities.invokeLater(() -> { + projectDataPanel.showTree(); + dataTreePanel.selectDomainFolder(folder); }); } @@ -1081,7 +1096,7 @@ public class FrontEndPlugin extends Plugin public void openDomainFile(DomainFile domainFile) { if (FolderLinkContentHandler.FOLDER_LINK_CONTENT_TYPE.equals(domainFile.getContentType())) { - showLinkedFolder(domainFile); + showLinkedFolderInViewedProject(domainFile); return; } @@ -1109,7 +1124,7 @@ public class FrontEndPlugin extends Plugin "opens this type of file"); } - private void showLinkedFolder(DomainFile domainFile) { + private void showLinkedFolderInViewedProject(DomainFile domainFile) { try { LinkedGhidraFolder linkedFolder = @@ -1123,7 +1138,12 @@ public class FrontEndPlugin extends Plugin return; } - DomainFolder domainFolder = linkedFolder.getLinkedFolder(); + // Do not hang onto domainFile, linkedFolder or their underlying project data + + ProjectData viewedProjectData = dtp.getProjectData(); + DomainFolder domainFolder = + viewedProjectData.getFolder(linkedFolder.getLinkedPathname()); + if (domainFolder != null) { // delayed to ensure tree is displayed Swing.runLater(() -> dtp.selectDomainFolder(domainFolder)); @@ -1136,6 +1156,52 @@ public class FrontEndPlugin extends Plugin } + void showInViewedProject(URL ghidraURL, boolean isFolder) { + + ProjectDataTreePanel dtp = projectDataPanel.openView(GhidraURL.getProjectURL(ghidraURL)); + if (dtp == null) { + return; + } + + Swing.runLater(() -> { + // delayed to ensure tree is displayed + + ProjectData viewedProjectData = dtp.getProjectData(); + + String path = GhidraURL.getProjectPathname(ghidraURL); + + if (isFolder) { + DomainFolder viewedProjectFolder = getViewProjectFolder(viewedProjectData, path); + if (viewedProjectFolder != null) { + dtp.selectDomainFolder(viewedProjectFolder); + } + } + else { + DomainFile viewedProjectFile = getViewProjectFile(viewedProjectData, path); + if (viewedProjectFile != null) { + dtp.selectDomainFile(viewedProjectFile); + } + } + }); + } + + private DomainFile getViewProjectFile(ProjectData viewedProjectData, String path) { + if (path == null || path.endsWith(DomainFolder.SEPARATOR)) { + return null; + } + return viewedProjectData.getFile(path); + } + + private DomainFolder getViewProjectFolder(ProjectData viewedProjectData, String path) { + if (path == null || path.equals(DomainFolder.SEPARATOR)) { + return viewedProjectData.getRootFolder(); + } + if (path.endsWith(DomainFolder.SEPARATOR)) { + path = path.substring(0, path.length() - 1); // remove trailing separator + } + return viewedProjectData.getFolder(path); + } + private class MyToolChestChangeListener implements ToolChestChangeListener { @Override diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/FrontEndTool.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/FrontEndTool.java index a6aaf0a3a3..fa25fc4789 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/FrontEndTool.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/FrontEndTool.java @@ -64,6 +64,7 @@ import ghidra.framework.plugintool.util.*; import ghidra.framework.preferences.Preferences; import ghidra.framework.project.tool.GhidraTool; import ghidra.framework.project.tool.GhidraToolTemplate; +import ghidra.framework.protocol.ghidra.GhidraURL; import ghidra.util.*; import ghidra.util.bean.GGlassPane; import ghidra.util.classfinder.ClassSearcher; @@ -176,6 +177,15 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener { System.exit(0); } + @Override + public boolean accept(URL url) { + if (!GhidraURL.isLocalProjectURL(url) && !GhidraURL.isServerRepositoryURL(url)) { + return false; + } + Swing.runLater(() -> execute(new AcceptUrlContentTask(url, plugin))); + return true; + } + private void ensureSize() { JFrame frame = getToolFrame(); Dimension size = frame.getSize(); diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ProjectDataPanel.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ProjectDataPanel.java index cc30e257e5..2ebc410ce4 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ProjectDataPanel.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/ProjectDataPanel.java @@ -406,6 +406,10 @@ class ProjectDataPanel extends JSplitPane implements ProjectViewListener { } } + void showTree() { + projectTab.setSelectedIndex(0); + } + private void showTable() { projectTab.setSelectedIndex(1); } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatable/ProjectDataTablePanel.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatable/ProjectDataTablePanel.java index ab7ddd41d9..3ee85c9ffa 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatable/ProjectDataTablePanel.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatable/ProjectDataTablePanel.java @@ -103,6 +103,14 @@ public class ProjectDataTablePanel extends JPanel { new ProjectDataTableDnDHandler(gTable, model); } + /** + * Determine if table capacity has been exceeded and files are not shown + * @return true if files are not shown in project data table, else false + */ + public boolean isCapacityExceeded() { + return capacityExceeded; + } + public void dispose() { table.dispose(); // this will dispose the gTable as well }