mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-06 03:50:02 +02:00
Merge branch 'GP-4433_ghidraffe_GhidraGo_accept_DomainFolder_GhidraURL'
This commit is contained in:
commit
da8ff58ba8
12 changed files with 399 additions and 63 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<PluginTool> 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());
|
||||
}
|
||||
|
|
|
@ -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<NullFolderDomainObject
|
|||
if (!(obj instanceof URLLinkObject)) {
|
||||
throw new IOException("Unsupported domain object: " + obj.getClass().getName());
|
||||
}
|
||||
return createFile((URLLinkObject) obj, FOLDER_LINK_CONTENT_TYPE, fs, path, name,
|
||||
monitor);
|
||||
return createFile((URLLinkObject) obj, FOLDER_LINK_CONTENT_TYPE, fs, path, name, monitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -91,6 +91,11 @@ public class FolderLinkContentHandler extends LinkHandler<NullFolderDomainObject
|
|||
URL url = getURL(folderLinkFile);
|
||||
|
||||
Project activeProject = AppInfo.getActiveProject();
|
||||
if (activeProject == null) {
|
||||
Msg.error(FolderLinkContentHandler.class,
|
||||
"Use of Linked Folders requires active project.");
|
||||
return null;
|
||||
}
|
||||
GhidraFolder parent = ((GhidraFile) folderLinkFile).getParent();
|
||||
return new LinkedGhidraFolder(activeProject, parent, folderLinkFile.getName(), url);
|
||||
}
|
||||
|
|
|
@ -1067,10 +1067,14 @@ public class GhidraFileData {
|
|||
if (folderItem.isCheckedOut() || versionedFolderItem != null) {
|
||||
throw new IOException("File already versioned");
|
||||
}
|
||||
if (isLinkFile() && !GhidraURL.isServerRepositoryURL(LinkHandler.getURL(folderItem))) {
|
||||
throw new IOException("Local project link-file may not be versioned");
|
||||
ContentHandler<?> 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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -165,8 +165,7 @@ public abstract class LinkHandler<T extends DomainObjectAdapterDB> 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");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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<DomainFile> 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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -406,6 +406,10 @@ class ProjectDataPanel extends JSplitPane implements ProjectViewListener {
|
|||
}
|
||||
}
|
||||
|
||||
void showTree() {
|
||||
projectTab.setSelectedIndex(0);
|
||||
}
|
||||
|
||||
private void showTable() {
|
||||
projectTab.setSelectedIndex(1);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue