GP-2509 GP-2644 Improved Ghidra URL support. Added support for Ghidra

URL linked files and folders within project.
This commit is contained in:
ghidra1
2022-08-30 18:31:11 -04:00
parent 8d6cf5e310
commit 5a422c4502
153 changed files with 7083 additions and 1732 deletions
@@ -27,6 +27,7 @@ import ghidra.framework.data.ProjectFileManager;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.framework.options.SaveState; import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.store.LockException;
import ghidra.trace.database.DBTraceContentHandler; import ghidra.trace.database.DBTraceContentHandler;
import ghidra.trace.model.Lifespan; import ghidra.trace.model.Lifespan;
import ghidra.trace.model.Trace; import ghidra.trace.model.Trace;
@@ -643,6 +644,7 @@ public class DebuggerCoordinates {
ProjectData projData = tool.getProject().getProjectData(projLoc); ProjectData projData = tool.getProject().getProjectData(projLoc);
if (projData == null) { if (projData == null) {
try { try {
// FIXME! orphaned instance - transient in nature
projData = new ProjectFileManager(projLoc, false, false); projData = new ProjectFileManager(projLoc, false, false);
} }
catch (NotOwnerException e) { catch (NotOwnerException e) {
@@ -650,7 +652,7 @@ public class DebuggerCoordinates {
"Not project owner: " + projLoc + "(" + pathname + ")"); "Not project owner: " + projLoc + "(" + pathname + ")");
return null; return null;
} }
catch (IOException e) { catch (IOException | LockException e) {
Msg.showError(DebuggerCoordinates.class, tool.getToolFrame(), "Trace Open Failed", Msg.showError(DebuggerCoordinates.class, tool.getToolFrame(), "Trace Open Failed",
"Project error: " + e.getMessage()); "Project error: " + e.getMessage());
return null; return null;
@@ -368,7 +368,18 @@ public class DebuggerTraceManagerServicePlugin extends Plugin
if (traceChooserDialog != null) { if (traceChooserDialog != null) {
return traceChooserDialog; return traceChooserDialog;
} }
DomainFileFilter filter = df -> Trace.class.isAssignableFrom(df.getDomainObjectClass()); DomainFileFilter filter = new DomainFileFilter() {
@Override
public boolean accept(DomainFile df) {
return Trace.class.isAssignableFrom(df.getDomainObjectClass());
}
@Override
public boolean followLinkedFolders() {
return false;
}
};
// TODO regarding the hack note below, I believe this issue ahs been fixed, but not sure how to test // TODO regarding the hack note below, I believe this issue ahs been fixed, but not sure how to test
return traceChooserDialog = return traceChooserDialog =
@@ -176,7 +176,7 @@ public class ProjectExperimentsTest extends AbstractGhidraHeadedIntegrationTest
assertNotNull(proj2 = pm.openProject(loc2, false, false)); assertNotNull(proj2 = pm.openProject(loc2, false, false));
ProjectData data1 = proj2.addProjectView(loc1.getURL()); ProjectData data1 = proj2.addProjectView(loc1.getURL(), true);
assertNotNull(data1); assertNotNull(data1);
// It's a cryin' shame. I don't get *any* callbacks. _ANY!_ // It's a cryin' shame. I don't get *any* callbacks. _ANY!_
@@ -18,11 +18,13 @@ package ghidra.trace.database;
import java.io.IOException; import java.io.IOException;
import javax.swing.Icon; import javax.swing.Icon;
import javax.swing.ImageIcon;
import db.DBHandle; import db.DBHandle;
import db.buffers.BufferFile; import db.buffers.BufferFile;
import db.buffers.ManagedBufferFile; import db.buffers.ManagedBufferFile;
import ghidra.framework.data.*; import ghidra.framework.data.DBWithUserDataContentHandler;
import ghidra.framework.data.DomainObjectMergeManager;
import ghidra.framework.model.ChangeSet; import ghidra.framework.model.ChangeSet;
import ghidra.framework.model.DomainObject; import ghidra.framework.model.DomainObject;
import ghidra.framework.store.*; import ghidra.framework.store.*;
@@ -34,9 +36,16 @@ import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException; import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
public class DBTraceContentHandler extends DBContentHandler { public class DBTraceContentHandler extends DBWithUserDataContentHandler<DBTrace> {
public static final String TRACE_CONTENT_TYPE = "Trace"; public static final String TRACE_CONTENT_TYPE = "Trace";
public static ImageIcon TRACE_ICON = Trace.TRACE_ICON;
static final Class<DBTrace> TRACE_DOMAIN_OBJECT_CLASS = DBTrace.class;
static final String TRACE_CONTENT_DEFAULT_TOOL = "Debugger";
private static final DBTraceLinkContentHandler linkHandler = new DBTraceLinkContentHandler();
@Override @Override
public long createFile(FileSystem fs, FileSystem userfs, String path, String name, public long createFile(FileSystem fs, FileSystem userfs, String path, String name,
DomainObject obj, TaskMonitor monitor) DomainObject obj, TaskMonitor monitor)
@@ -48,7 +57,7 @@ public class DBTraceContentHandler extends DBContentHandler {
} }
@Override @Override
public DomainObjectAdapter getImmutableObject(FolderItem item, Object consumer, int version, public DBTrace getImmutableObject(FolderItem item, Object consumer, int version,
int minChangeVersion, TaskMonitor monitor) int minChangeVersion, TaskMonitor monitor)
throws IOException, CancelledException, VersionException { throws IOException, CancelledException, VersionException {
String contentType = item.getContentType(); String contentType = item.getContentType();
@@ -96,7 +105,7 @@ public class DBTraceContentHandler extends DBContentHandler {
} }
@Override @Override
public DomainObjectAdapter getReadOnlyObject(FolderItem item, int version, boolean okToUpgrade, public DBTrace getReadOnlyObject(FolderItem item, int version, boolean okToUpgrade,
Object consumer, TaskMonitor monitor) Object consumer, TaskMonitor monitor)
throws IOException, VersionException, CancelledException { throws IOException, VersionException, CancelledException {
String contentType = item.getContentType(); String contentType = item.getContentType();
@@ -146,7 +155,7 @@ public class DBTraceContentHandler extends DBContentHandler {
} }
@Override @Override
public DomainObjectAdapter getDomainObject(FolderItem item, FileSystem userfs, long checkoutId, public DBTrace getDomainObject(FolderItem item, FileSystem userfs, long checkoutId,
boolean okToUpgrade, boolean recover, Object consumer, TaskMonitor monitor) boolean okToUpgrade, boolean recover, Object consumer, TaskMonitor monitor)
throws IOException, CancelledException, VersionException { throws IOException, CancelledException, VersionException {
String contentType = item.getContentType(); String contentType = item.getContentType();
@@ -296,8 +305,8 @@ public class DBTraceContentHandler extends DBContentHandler {
} }
@Override @Override
public Class<? extends DomainObject> getDomainObjectClass() { public Class<DBTrace> getDomainObjectClass() {
return DBTrace.class; return TRACE_DOMAIN_OBJECT_CLASS;
} }
@Override @Override
@@ -312,12 +321,12 @@ public class DBTraceContentHandler extends DBContentHandler {
@Override @Override
public String getDefaultToolName() { public String getDefaultToolName() {
return "Debugger"; return TRACE_CONTENT_DEFAULT_TOOL;
} }
@Override @Override
public Icon getIcon() { public Icon getIcon() {
return Trace.TRACE_ICON; return TRACE_ICON;
} }
@Override @Override
@@ -331,4 +340,9 @@ public class DBTraceContentHandler extends DBContentHandler {
// TODO: // TODO:
return null; return null;
} }
@Override
public DBTraceLinkContentHandler getLinkHandler() {
return linkHandler;
}
} }
@@ -0,0 +1,71 @@
/* ###
* 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.trace.database;
import java.io.IOException;
import javax.swing.Icon;
import ghidra.framework.data.LinkHandler;
import ghidra.framework.data.URLLinkObject;
import ghidra.framework.model.DomainObject;
import ghidra.framework.store.FileSystem;
import ghidra.util.InvalidNameException;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
public class DBTraceLinkContentHandler extends LinkHandler<DBTrace> {
public static final String TRACE_LINK_CONTENT_TYPE = "TraceLink";
@Override
public long createFile(FileSystem fs, FileSystem userfs, String path, String name,
DomainObject obj, TaskMonitor monitor)
throws IOException, InvalidNameException, CancelledException {
if (!(obj instanceof URLLinkObject)) {
throw new IOException("Unsupported domain object: " + obj.getClass().getName());
}
return createFile((URLLinkObject) obj, TRACE_LINK_CONTENT_TYPE, fs, path, name,
monitor);
}
@Override
public String getContentType() {
return TRACE_LINK_CONTENT_TYPE;
}
@Override
public String getContentTypeDisplayString() {
return TRACE_LINK_CONTENT_TYPE;
}
@Override
public Class<DBTrace> getDomainObjectClass() {
// return linked content class
return DBTraceContentHandler.TRACE_DOMAIN_OBJECT_CLASS;
}
@Override
public Icon getIcon() {
return DBTraceContentHandler.TRACE_ICON;
}
@Override
public String getDefaultToolName() {
return DBTraceContentHandler.TRACE_CONTENT_DEFAULT_TOOL;
}
}
+1 -1
View File
@@ -351,6 +351,7 @@ src/main/help/help/topics/FrontEndPlugin/images/DeleteProject.png||GHIDRA||||END
src/main/help/help/topics/FrontEndPlugin/images/EditPluginPath.png||GHIDRA||||END| src/main/help/help/topics/FrontEndPlugin/images/EditPluginPath.png||GHIDRA||||END|
src/main/help/help/topics/FrontEndPlugin/images/EditProjectAccessList.png||GHIDRA||||END| src/main/help/help/topics/FrontEndPlugin/images/EditProjectAccessList.png||GHIDRA||||END|
src/main/help/help/topics/FrontEndPlugin/images/EditProjectAccessPanel.png||GHIDRA||||END| src/main/help/help/topics/FrontEndPlugin/images/EditProjectAccessPanel.png||GHIDRA||||END|
src/main/help/help/topics/FrontEndPlugin/images/LinkOtherProject.png||GHIDRA||||END|
src/main/help/help/topics/FrontEndPlugin/images/MemoryUsage.png||GHIDRA||||END| src/main/help/help/topics/FrontEndPlugin/images/MemoryUsage.png||GHIDRA||||END|
src/main/help/help/topics/FrontEndPlugin/images/NonSharedProjectInfo.png||GHIDRA||||END| src/main/help/help/topics/FrontEndPlugin/images/NonSharedProjectInfo.png||GHIDRA||||END|
src/main/help/help/topics/FrontEndPlugin/images/OpenProject.png||GHIDRA||||END| src/main/help/help/topics/FrontEndPlugin/images/OpenProject.png||GHIDRA||||END|
@@ -1089,7 +1090,6 @@ src/main/resources/images/layout_add.png||FAMFAMFAM Icons - CC 2.5|||famfamfam s
src/main/resources/images/ledgreen.png||Nuvola Icons - LGPL 2.1|||Nuvola icon set|END| src/main/resources/images/ledgreen.png||Nuvola Icons - LGPL 2.1|||Nuvola icon set|END|
src/main/resources/images/ledred.png||Nuvola Icons - LGPL 2.1|||Nuvola icon set|END| src/main/resources/images/ledred.png||Nuvola Icons - LGPL 2.1|||Nuvola icon set|END|
src/main/resources/images/ledyellow.png||Nuvola Icons - LGPL 2.1|||Nuvola icon set|END| src/main/resources/images/ledyellow.png||Nuvola Icons - LGPL 2.1|||Nuvola icon set|END|
src/main/resources/images/link.png||Nuvola Icons - LGPL 2.1|||Nuvola icon set|END|
src/main/resources/images/lock.gif||GHIDRA||||END| src/main/resources/images/lock.gif||GHIDRA||||END|
src/main/resources/images/magnifier.png||FAMFAMFAM Icons - CC 2.5||||END| src/main/resources/images/magnifier.png||FAMFAMFAM Icons - CC 2.5||||END|
src/main/resources/images/media-flash.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END| src/main/resources/images/media-flash.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
@@ -17,6 +17,8 @@
// NOTE: Script will only process unversioned and checked-out files. // NOTE: Script will only process unversioned and checked-out files.
//@category Examples //@category Examples
import java.io.IOException;
import ghidra.app.script.GhidraScript; import ghidra.app.script.GhidraScript;
import ghidra.app.script.GhidraState; import ghidra.app.script.GhidraState;
import ghidra.framework.model.*; import ghidra.framework.model.*;
@@ -25,8 +27,6 @@ import ghidra.program.model.listing.Program;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException; import ghidra.util.exception.VersionException;
import java.io.IOException;
public class CallAnotherScriptForAllPrograms extends GhidraScript { public class CallAnotherScriptForAllPrograms extends GhidraScript {
// The script referenced in the following line should be replaced with the script to be called // The script referenced in the following line should be replaced with the script to be called
@@ -59,6 +59,10 @@ public class CallAnotherScriptForAllPrograms extends GhidraScript {
} }
private void processDomainFile(DomainFile domainFile) throws CancelledException, IOException { private void processDomainFile(DomainFile domainFile) throws CancelledException, IOException {
// Do not follow folder-links or consider program links. Using content type
// to filter is best way to control this. If program links should be considered
// "Program.class.isAssignableFrom(domainFile.getDomainObjectClass())"
// should be used.
if (!ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(domainFile.getContentType())) { if (!ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(domainFile.getContentType())) {
return; // skip non-Program files return; // skip non-Program files
} }
@@ -143,6 +143,8 @@ public class RepositoryFileUpgradeScript extends GhidraScript {
} }
private boolean performProgramUpgrade(DomainFile df) throws IOException, CancelledException { private boolean performProgramUpgrade(DomainFile df) throws IOException, CancelledException {
// Do not follow folder-links or consider program links. Using content type
// to filter is best way to control this.
if (!ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(df.getContentType())) { if (!ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(df.getContentType())) {
return false; return false;
} }
@@ -48,7 +48,10 @@ public class VersionControl_AddAll extends GhidraScript {
if (monitor.isCancelled()) { if (monitor.isCancelled()) {
break; break;
} }
// Do not follow folder-links or consider program links. Using content type
// to filter is best way to control this. If program links should be considered
// "Program.class.isAssignableFrom(domainFile.getDomainObjectClass())"
// should be used. It may also be appropriate to handle other content types.
if (!ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(file.getContentType()) || if (!ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(file.getContentType()) ||
file.isVersioned()) { file.isVersioned()) {
continue;// skip continue;// skip
@@ -22,13 +22,15 @@
hyperlink.</P> hyperlink.</P>
<!-- Annotation Example --> <!-- Annotation Example -->
<P>The following text shows the syntax of the URL annotation:</P> <P>The following text shows the syntax of a sample URL annotation:</P>
<pre><font size="4"><br> <pre><font size="4"><br>
<b>{@<i>url</i></b> "http://www.google.com"<b>}</b><br> <b>{@<i>url</i></b> "<i>http://www.google.com</i>"</b> "Search Web"<b>}</b><br>
</font><br></pre> </font><br></pre>
<P>The bold text is required for all annotations. The italicized text is required but is <P>The bold text is required for all annotations. The italicized text is required but is
specific to the annotation being used (see the table below).</P> specific to the annotation being used (see the table below). The optional rendered display text
"Search Web" will be displayed in listing. If the optional display test is omitted, the URL
will be displayed. Quotes around display text are optional.</P>
<H2>Examples</H2> <H2>Examples</H2>
@@ -47,10 +49,21 @@
<I>Rendered URL Annotation Example</I></P> <I>Rendered URL Annotation Example</I></P>
<P>When the URL text (e.g., "http://www.google.com") in the above image is clicked from within <P>When the URL text (e.g., "http://www.google.com") in the above image is clicked from within
Ghidra, a web browser is launched and attempts to load the corresponding web page. If the URL text Ghidra, a web browser is launched and attempts to load the corresponding web page. </P>
is of the form <i>ghidra://&lt;host&gt;[:&lt;port&gt;]/&lt;repository-name&gt;/&lt;program-path&gt;[#&lt;address-or-symbol-ref&gt;]</i>
an attempt will be made to open the corresponding program from the referenced Ghidra Server (e.g., <P>If the URL text corresponds to a Ghidra URL and attempt will be made to open the referenced
<i>ghidra://myserver/Repo/notepad.exe#entry</i>).</P> Program file within the Code Browser. Such a URL may refer to a Program file from a
local project or Ghidra Server. The Ghidra URL forms supported include:</P>
<P><U>Remote Ghidra Server file</U><BR>
<i>ghidra://&lt;host&gt;[:&lt;port&gt;]/&lt;repository-name&gt;/&lt;program-path&gt;[#&lt;address-or-symbol-ref&gt;]</i><BR>
Example: <i>ghidra://hostname/Repo/notepad.exe#entry</i>
</P>
<P><U>Local Ghidra project file</U><BR>
<i>ghidra:/[&lt;project-path&gt/]&lt;project-name&gt;?/&lt;program-path&gt;[#&lt;address-or-symbol-ref&gt;]</i><BR>
Example: <i>ghidra:/share/MyProject?/notepad.exe#entry</i>
</P>
</BLOCKQUOTE> </BLOCKQUOTE>
<H2>Valid Annotations</H2> <H2>Valid Annotations</H2>
@@ -174,9 +187,10 @@
<TD valign="top" width="15%">Displays the given URL has a hyperlink. This annotation <TD valign="top" width="15%">Displays the given URL has a hyperlink. This annotation
optionally takes display text so that the hyperlink may be displayed with text other optionally takes display text so that the hyperlink may be displayed with text other
than that of the URL.<BR><BR> than that of the URL.<BR><BR>
References to <i>ghidra://</i>, which refer to a program within a Ghidra Server repository, References to a program file on a Ghidra Server (<i>ghidra://&lt;host...</i>) or
will be opened within the Listing display, while all other URL protocols (e.g., <i>http://, https://, local project (<i>ghidra:/&lt;project-...</i>) will be opened within the Listing display,
file://,</i> etc.) will be launched via an external web browser (see while all other URL forms (e.g., <i>http://, https://, file://,</i> etc.) will be
launched via an external web browser (see
<a href="help/topics/ShowInstructionInfoPlugin/ShowInstructionInfo.htm#Show_Processor_Manual">command configuration <a href="help/topics/ShowInstructionInfoPlugin/ShowInstructionInfo.htm#Show_Processor_Manual">command configuration
for Processor Manuals</a>). for Processor Manuals</a>).
</TD> </TD>
@@ -216,6 +230,12 @@
<LI>{@url "ghidra://myserver/Repo/notepad.exe#entry"}</LI> <LI>{@url "ghidra://myserver/Repo/notepad.exe#entry"}</LI>
<LI>{@url "ghidra://myserver/Repo/notepad.exe" "see notepad.exe"}</LI> <LI>{@url "ghidra://myserver/Repo/notepad.exe" "see notepad.exe"}</LI>
<LI>{@url "ghidra:/share/MyProject?/notepad.exe#entry"}</LI>
<LI>{@url "ghidra:/share/MyProject?/notepad.exe" "see notepad.exe"}</LI>
</UL> </UL>
</TD> </TD>
</TR> </TR>
@@ -224,8 +244,9 @@
<TD valign="top" width="5%">Program<BR> <TD valign="top" width="5%">Program<BR>
</TD> </TD>
<TD valign="top" width="15%">Displays a hyperlink to the given Ghidra program name that <TD valign="top" width="15%">Displays a hyperlink to the given Ghidra program pathname
will open that program in a new Listing tab when clicked.<BR> with the current project. Referenced program
will open in a new Listing tab when clicked.<BR>
You may optionally provide an address or symbol to be displayed when the program is You may optionally provide an address or symbol to be displayed when the program is
opened by appending to the program name an '@' character, followed by an address or opened by appending to the program name an '@' character, followed by an address or
symbol name.</TD> symbol name.</TD>
@@ -688,6 +688,54 @@
<P>The tabbed pane for read-only Project data is removed from the Project Window.</P> <P>The tabbed pane for read-only Project data is removed from the Project Window.</P>
</BLOCKQUOTE> </BLOCKQUOTE>
<H3><A name="Create_File_Links"></A>Create Linked Folder or File</H3>
<BLOCKQUOTE>
<P>This feature allows you to create a folder or file link in your active project to a
corresponding folder or file within a read-only viewed project.
This is done using a Ghidra URL which references the
file in its local or remote storage location. If the viewed project corresponds to a
viewed repository a remote URL will be used, while other cases will refer to the
locally viewed project. It is possible for links to become broken if the referenced
repository, local project or file location are changed.</P>
<ol>
<li>Select a single folder or file from a viewed READ-ONLY Project Data tree.</li>
<li>Right mouse click on the selected tree node and choose the <I>Copy</I> option.</li>
<li>Select a destination folder in the active project tree.</li>
<li>Right mouse click on the folder and choose the <I>Paste as Link</I> option.
<P><IMG src="../../shared/note.png" border="0">Currently, linked-file types are
currently limited to <I>Program</I> and <I>Data Type Archive</I> files
only. The <I>Past as Link</I> menu item will be disabled for
unsupported file content types or for other unsupported situations such as internal
linking within the same project.</P>
</li>
</ol>
<P>A linked-file may be opened in a tool via the project window in the same fashion that
a normal file is opened (e.g., double-left-mouse-click or drag-n-drop onto a tool box icon).
Such a project file may also be opened within a Tool using its <B>File->Open...</B> action
and selected from the resulting project file selection dilaog.
Clicking on a linked-folder in the active project window will open that location in a
<B>READ-ONLY Project Data</B> tree. The user may be prompted for a shared repository
connection password when accessing a linked folder or file.</P>
<P>Within a project file chooser dialog a linked-folder may be expanded in a similar fashion
to local folders provided any neccessary repository connection can be completed.</P>
<P><IMG src="../../shared/note.png" border="0"><B>Add to Version Control...</B> is supported
for repository folder and file links only and will be disabled for links to a
local project.</P>
<P><IMG src="../../shared/note.png" border="0">Currently, linked-files only provide access
to the latest file version and do not facilitate access to older file versions.</P>
<P>The project window below shows a Program file-link "Program1" which is linked to the
same file in the viewed project. Hovering the mouse over a linked-file will show the URL
of the linked file or folder. The chain-link icon decoration indicates such a linked
file or folder.</P>
<CENTER>
<IMG src= "images/LinkOtherProject.png" border="0">
</CENTER>
</BLOCKQUOTE>
</BLOCKQUOTE> </BLOCKQUOTE>
<H2><A name="Workspace"></A>Workspaces</H2> <H2><A name="Workspace"></A>Workspaces</H2>
Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 36 KiB

@@ -1266,19 +1266,24 @@ public class CallTreeProvider extends ComponentProviderAdapter implements Domain
} }
@Override @Override
protected void expandNode(GTreeNode node, TaskMonitor monitor) throws CancelledException { protected void expandNode(GTreeNode node, boolean force, TaskMonitor monitor)
throws CancelledException {
TreePath treePath = node.getTreePath(); TreePath treePath = node.getTreePath();
Object[] path = treePath.getPath(); Object[] path = treePath.getPath();
if (path.length > maxDepth) { if (path.length > maxDepth) {
return; return;
} }
if (!force && !node.isAutoExpandPermitted()) {
return;
}
CallNode callNode = (CallNode) node; CallNode callNode = (CallNode) node;
if (callNode.functionIsInPath()) { if (callNode.functionIsInPath()) {
return; // this path hit a function that is already in the path return; // this path hit a function that is already in the path
} }
super.expandNode(node, monitor); super.expandNode(node, false, monitor);
} }
} }
@@ -106,9 +106,33 @@ public class ClearFlowAndRepairCmd extends BackgroundCommand {
CodeUnit cu = cuIter.next(); CodeUnit cu = cuIter.next();
if (cu instanceof Instruction) { if (cu instanceof Instruction) {
Instruction instr = (Instruction) cu; Instruction instr = (Instruction) cu;
// check for function on delay slot
if (listing.getFunctionAt(instr.getMinAddress()) != null) {
continue; // skip since it will be picked-up by flow if appropriate
}
// check for fallthrough to instruction
Address ffAddr = instr.getFallFrom(); Address ffAddr = instr.getFallFrom();
if (ffAddr != null && startAddrs.contains(ffAddr)) { if (ffAddr != null && startAddrs.contains(ffAddr)) {
continue; // skip since it will be picked-up by flow continue; // skip since it will be picked-up by flow if appropriate
}
// check for flow into delay slot
if (instr.isInDelaySlot()) {
boolean skip = false;
ReferenceIterator refToIter = instr.getReferenceIteratorTo();
while (refToIter.hasNext()) {
Reference ref = refToIter.next();
RefType refType = ref.getReferenceType();
if (refType.isJump() || refType.isCall()) {
skip = true;
break;
}
}
if (skip) {
continue; // skip since it will be picked-up by flow if appropriate
}
} }
} }
else { else {
@@ -240,10 +240,11 @@ public class CommentsDialog extends DialogComponentProvider implements KeyListen
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
private AnnotationAdapterWrapper[] getAnnotationAdapterWrappers() { private AnnotationAdapterWrapper[] getAnnotationAdapterWrappers() {
AnnotatedStringHandler[] annotations = Annotation.getAnnotatedStringHandlers(); List<AnnotatedStringHandler> annotations = Annotation.getAnnotatedStringHandlers();
AnnotationAdapterWrapper[] retVal = new AnnotationAdapterWrapper[annotations.length]; int count = annotations.size();
for (int i = 0; i < annotations.length; i++) { AnnotationAdapterWrapper[] retVal = new AnnotationAdapterWrapper[count];
retVal[i] = new AnnotationAdapterWrapper(annotations[i]); for (int i = 0; i < count; i++) {
retVal[i] = new AnnotationAdapterWrapper(annotations.get(i));
} }
return retVal; return retVal;
} }
@@ -56,7 +56,6 @@ import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.PluginInfo; import ghidra.framework.plugintool.PluginInfo;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.util.PluginStatus; import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.program.database.DataTypeArchiveContentHandler;
import ghidra.program.database.data.ProgramDataTypeManager; import ghidra.program.database.data.ProgramDataTypeManager;
import ghidra.program.model.address.AddressSetView; import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.data.*; import ghidra.program.model.data.*;
@@ -245,8 +244,7 @@ public class DataTypeManagerPlugin extends ProgramPlugin
Project project = tool.getProjectManager().getActiveProject(); Project project = tool.getProjectManager().getActiveProject();
if (project != null && project.getName().equals(projectName)) { if (project != null && project.getName().equals(projectName)) {
DomainFile df = project.getProjectData().getFile(pathname); DomainFile df = project.getProjectData().getFile(pathname);
if (df != null && DataTypeArchiveContentHandler.DATA_TYPE_ARCHIVE_CONTENT_TYPE if (DataTypeArchive.class.isAssignableFrom(df.getDomainObjectClass())) {
.equals(df.getContentType())) {
return df; return df;
} }
} }
@@ -588,12 +586,9 @@ public class DataTypeManagerPlugin extends ProgramPlugin
openArchive(domainFile, version); openArchive(domainFile, version);
} }
}; };
DomainFileFilter filter = f -> {
Class<?> c = f.getDomainObjectClass();
return DataTypeArchive.class.isAssignableFrom(c);
};
openDialog = openDialog =
new OpenVersionedFileDialog(tool, "Open Project Data Type Archive", filter); new OpenVersionedFileDialog(tool, "Open Project Data Type Archive",
df -> DataTypeArchive.class.isAssignableFrom(df.getDomainObjectClass()));
openDialog.setHelpLocation(new HelpLocation(HelpTopics.PROGRAM, "Open_File_Dialog")); openDialog.setHelpLocation(new HelpLocation(HelpTopics.PROGRAM, "Open_File_Dialog"));
openDialog.addOkActionListener(listener); openDialog.addOkActionListener(listener);
} }
@@ -608,8 +608,15 @@ public class DataTypesProvider extends ComponentProviderAdapter {
ArchiveNode archiveNode = dataTypeNode.getArchiveNode(); ArchiveNode archiveNode = dataTypeNode.getArchiveNode();
if (archiveNode instanceof ProjectArchiveNode && !archiveNode.isModifiable()) { if (archiveNode instanceof ProjectArchiveNode && !archiveNode.isModifiable()) {
Msg.showInfo(getClass(), archiveGTree, "Archive Not Checked Out", ProjectArchiveNode projectArchive = (ProjectArchiveNode) archiveNode;
"You must checkout this archive before you may edit data types."); if (projectArchive.getDomainFile().isReadOnly()) {
Msg.showInfo(getClass(), archiveGTree, "Read-Only Archive",
"You may not edit data type within a read-only project archive.");
}
else {
Msg.showInfo(getClass(), archiveGTree, "Archive Not Checked Out",
"You must checkout this archive before you may edit data types.");
}
return; return;
} }
@@ -38,6 +38,7 @@ import ghidra.app.plugin.core.datamgr.tree.ArchiveNode;
import ghidra.app.plugin.core.datamgr.tree.DataTypeNode; import ghidra.app.plugin.core.datamgr.tree.DataTypeNode;
import ghidra.app.plugin.core.datamgr.util.DataTypeTreeCopyMoveTask; import ghidra.app.plugin.core.datamgr.util.DataTypeTreeCopyMoveTask;
import ghidra.app.plugin.core.datamgr.util.DataTypeTreeCopyMoveTask.ActionType; import ghidra.app.plugin.core.datamgr.util.DataTypeTreeCopyMoveTask.ActionType;
import ghidra.app.plugin.core.datamgr.util.DataTypeUtils;
import ghidra.program.model.data.*; import ghidra.program.model.data.*;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.layout.PairLayout; import ghidra.util.layout.PairLayout;
@@ -85,26 +86,21 @@ public class AssociateDataTypeAction extends DockingAction {
return !nodes.isEmpty(); return !nodes.isEmpty();
} }
private boolean hasSingleModifiableSourceArchive(List<GTreeNode> nodes) { private Archive getSingleDTArchive(List<GTreeNode> nodes) {
Archive sourceArchive = null; Archive dtArchive = null;
for (GTreeNode node : nodes) { for (GTreeNode node : nodes) {
Archive archive = findArchive(node); Archive archive = findArchive(node);
if (sourceArchive == null) { if (dtArchive == null) {
sourceArchive = archive; dtArchive = archive;
continue; continue;
} }
if (sourceArchive != archive) { if (dtArchive != archive) {
return false; return null;
} }
} }
return dtArchive;
if (sourceArchive != null && sourceArchive.isModifiable()) {
return true;
}
return false;
} }
private static Archive findArchive(GTreeNode node) { private static Archive findArchive(GTreeNode node) {
@@ -134,13 +130,20 @@ public class AssociateDataTypeAction extends DockingAction {
List<GTreeNode> nodes = ((DataTypesActionContext) context).getSelectedNodes(); List<GTreeNode> nodes = ((DataTypesActionContext) context).getSelectedNodes();
if (!hasSingleModifiableSourceArchive(nodes)) { Archive dtArchive = getSingleDTArchive(nodes);
Msg.showInfo(this, getProviderComponent(), "Multiple Source Archives", if (dtArchive == null) {
Msg.showInfo(this, getProviderComponent(), "Multiple Data Type Archives",
"The currently selected nodes are from multiple archives.\n" + "The currently selected nodes are from multiple archives.\n" +
"Please select only nodes from a single archvie."); "Please select only nodes from a single archvie.");
return; return;
} }
if (!dtArchive.isModifiable()) {
DataTypeUtils.showUnmodifiableArchiveErrorMessage(context.getSourceComponent(),
"Disassociate Failed", dtArchive.getDataTypeManager());
return;
}
if (isAlreadyAssociated((DataTypesActionContext) context)) { if (isAlreadyAssociated((DataTypesActionContext) context)) {
Msg.showInfo(this, getProviderComponent(), "Already Associated", Msg.showInfo(this, getProviderComponent(), "Already Associated",
"One or more of the currently selected nodes are already associated\n" + "One or more of the currently selected nodes are already associated\n" +
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -76,7 +76,7 @@ public class DataTypeManagerHandler {
private DataTreeDialog dataTreeSaveDialog; private DataTreeDialog dataTreeSaveDialog;
private CreateDataTypeArchiveDataTreeDialog dataTreeCreateDialog; private CreateDataTypeArchiveDataTreeDialog dataTreeCreateDialog;
private boolean treeDialogCancelled = false; private boolean treeDialogCancelled = false;
private DomainFileFilter domainFileFilter; private DomainFileFilter createArchiveFileFilter;
private DataTypeIndexer dataTypeIndexer; private DataTypeIndexer dataTypeIndexer;
private List<ArchiveManagerListener> archiveManagerlisteners = new ArrayList<>(); private List<ArchiveManagerListener> archiveManagerlisteners = new ArrayList<>();
@@ -102,9 +102,17 @@ public class DataTypeManagerHandler {
dataTypeIndexer.addDataTypeManager(builtInDataTypesManager); dataTypeIndexer.addDataTypeManager(builtInDataTypesManager);
openArchives.add(new BuiltInArchive(this, builtInDataTypesManager)); openArchives.add(new BuiltInArchive(this, builtInDataTypesManager));
domainFileFilter = f -> { createArchiveFileFilter = new DomainFileFilter() {
Class<?> c = f.getDomainObjectClass();
return DataTypeArchive.class.isAssignableFrom(c); @Override
public boolean accept(DomainFile df) {
return DataTypeArchive.class.isAssignableFrom(df.getDomainObjectClass());
}
@Override
public boolean followLinkedFolders() {
return false;
}
}; };
folderListener = new MyFolderListener(); folderListener = new MyFolderListener();
@@ -1425,7 +1433,7 @@ public class DataTypeManagerHandler {
} }
}; };
dataTreeSaveDialog = dataTreeSaveDialog =
new DataTreeDialog(null, "Save As", DataTreeDialog.SAVE, domainFileFilter); new DataTreeDialog(null, "Save As", DataTreeDialog.SAVE, createArchiveFileFilter);
dataTreeSaveDialog.addOkActionListener(listener); dataTreeSaveDialog.addOkActionListener(listener);
dataTreeSaveDialog dataTreeSaveDialog
@@ -1465,7 +1473,7 @@ public class DataTypeManagerHandler {
}; };
dataTreeCreateDialog = new CreateDataTypeArchiveDataTreeDialog(null, "Create", dataTreeCreateDialog = new CreateDataTypeArchiveDataTreeDialog(null, "Create",
DataTreeDialog.CREATE, domainFileFilter); DataTreeDialog.CREATE, createArchiveFileFilter);
dataTreeCreateDialog.addOkActionListener(listener); dataTreeCreateDialog.addOkActionListener(listener);
dataTreeCreateDialog.setHelpLocation( dataTreeCreateDialog.setHelpLocation(
@@ -24,6 +24,7 @@ import javax.swing.Icon;
import generic.theme.GColor; import generic.theme.GColor;
import generic.theme.GIcon; import generic.theme.GIcon;
import ghidra.app.services.DataTypeQueryService; import ghidra.app.services.DataTypeQueryService;
import ghidra.program.database.data.ProjectDataTypeManager;
import ghidra.program.model.data.*; import ghidra.program.model.data.*;
import ghidra.program.model.data.Enum; import ghidra.program.model.data.Enum;
import ghidra.util.Msg; import ghidra.util.Msg;
@@ -438,9 +439,18 @@ public class DataTypeUtils {
msg = "The archive file is not modifiable!\nYou must open the archive for editing\n" + msg = "The archive file is not modifiable!\nYou must open the archive for editing\n" +
"before performing this operation.\n" + dtm.getName(); "before performing this operation.\n" + dtm.getName();
} }
else if (dtm instanceof ProjectDataTypeManager) {
ProjectDataTypeManager projectDtm = (ProjectDataTypeManager) dtm;
if (!projectDtm.isUpdatable() && !projectDtm.getDomainFile().canCheckout()) {
msg = "The project archive is not modifiable!\n" + dtm.getName();
}
else {
msg = "The project archive is not modifiable!\nYou must check out the archive\n" +
"before performing this operation.\n" + dtm.getName();
}
}
else { else {
msg = "The project archive is not modifiable!\nYou must check out the archive\n" + msg = "The Archive is not modifiable!\n";
"before performing this operation.\n" + dtm.getName();
} }
Msg.showInfo(DataTypeUtils.class, parent, title, msg); Msg.showInfo(DataTypeUtils.class, parent, title, msg);
} }
@@ -295,11 +295,15 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
return comboBox; return comboBox;
} }
@SuppressWarnings("unchecked")
private List<Exporter> getApplicableExporters() { private List<Exporter> getApplicableExporters() {
List<Exporter> list = new ArrayList<>(ClassSearcher.getInstances(Exporter.class)); List<Exporter> list = new ArrayList<>(ClassSearcher.getInstances(Exporter.class));
Class<? extends DomainObject> domainObjectClass = domainFile.getDomainObjectClass(); Class<?> domainObjectClass = domainFile.getDomainObjectClass();
list.removeIf(exporter -> !exporter.canExportDomainObject(domainObjectClass)); if (DomainObject.class.isAssignableFrom(domainObjectClass)) {
Collections.sort(list, (o1, o2) -> o1.toString().compareTo(o2.toString())); list.removeIf(exporter -> !exporter
.canExportDomainObject((Class<? extends DomainObject>) domainObjectClass));
Collections.sort(list, (o1, o2) -> o1.toString().compareTo(o2.toString()));
}
return list; return list;
} }
@@ -422,8 +426,16 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
private void doOpenFile(TaskMonitor monitor) { private void doOpenFile(TaskMonitor monitor) {
try { try {
domainObject = domainFile.getImmutableDomainObject(this, DomainFile.DEFAULT_VERSION, if (domainFile.isLinkFile()) {
TaskMonitor.DUMMY); // Linked files are always subject to upgrade if needed and do not support
// getImmutableDomainObject
domainObject =
domainFile.getReadOnlyDomainObject(this, DomainFile.DEFAULT_VERSION, monitor);
}
else {
domainObject =
domainFile.getImmutableDomainObject(this, DomainFile.DEFAULT_VERSION, monitor);
}
} }
catch (VersionException | CancelledException | IOException e) { catch (VersionException | CancelledException | IOException e) {
Msg.showError(this, getComponent(), "Error Opening File", Msg.showError(this, getComponent(), "Error Opening File",
@@ -147,7 +147,8 @@ public class MyProgramChangesDisplayPlugin extends ProgramPlugin implements Doma
@Override @Override
public boolean isEnabledForContext(ActionContext context) { public boolean isEnabledForContext(ActionContext context) {
return currentProgram != null && currentProgram.getDomainFile().canCheckin(); return currentProgram != null &&
currentProgram.getDomainFile().modifiedSinceCheckout();
} }
}; };
@@ -26,8 +26,8 @@ import docking.action.MenuData;
import docking.widgets.OptionDialog; import docking.widgets.OptionDialog;
import ghidra.app.CorePluginPackage; import ghidra.app.CorePluginPackage;
import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.PluginCategoryNames;
import ghidra.framework.main.FrontEndTool;
import ghidra.framework.main.ApplicationLevelPlugin; import ghidra.framework.main.ApplicationLevelPlugin;
import ghidra.framework.main.FrontEndTool;
import ghidra.framework.main.datatable.ProjectDataContext; import ghidra.framework.main.datatable.ProjectDataContext;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.*;
@@ -316,18 +316,20 @@ public final class LanguageProviderPlugin extends Plugin implements ApplicationL
try { try {
SwingUtilities.invokeAndWait(() -> { SwingUtilities.invokeAndWait(() -> {
ToolServices toolServices = tool.getToolServices(); ToolServices toolServices = tool.getToolServices();
String defaultToolName = toolServices.getDefaultToolTemplate(file).getName(); ToolTemplate defaultToolTemplate = toolServices.getDefaultToolTemplate(file);
for (PluginTool t : toolServices.getRunningTools()) { if (defaultToolTemplate != null) {
if (t.getName().equals(defaultToolName)) { String defaultToolName = defaultToolTemplate.getName();
openTool = t; for (PluginTool t : toolServices.getRunningTools()) {
break; if (t.getName().equals(defaultToolName)) {
openTool = t;
break;
}
} }
} }
if (openTool != null) { if (openTool == null ||
openTool.acceptDomainFiles(new DomainFile[] { file }); !openTool.acceptDomainFiles(new DomainFile[] { file })) {
} Msg.showError(this, tool.getToolFrame(), "Failed to Open Program",
else { "A suitable default tool could not found!");
openTool = tool.getToolServices().launchDefaultTool(file);
} }
}); });
} }
@@ -336,7 +338,7 @@ public final class LanguageProviderPlugin extends Plugin implements ApplicationL
} }
catch (InvocationTargetException e) { catch (InvocationTargetException e) {
Throwable t = e.getCause(); Throwable t = e.getCause();
Msg.showError(this, tool.getToolFrame(), "Tool Launch Failed", Msg.showError(this, tool.getToolFrame(), "Failed to Open Program",
"An error occurred while attempting to launch your default tool!", t); "An error occurred while attempting to launch your default tool!", t);
} }
} }
@@ -30,6 +30,7 @@ import ghidra.app.events.*;
import ghidra.app.nav.Navigatable; import ghidra.app.nav.Navigatable;
import ghidra.app.services.*; import ghidra.app.services.*;
import ghidra.app.util.task.OpenProgramTask; import ghidra.app.util.task.OpenProgramTask;
import ghidra.app.util.task.OpenProgramTask.OpenProgramRequest;
import ghidra.framework.data.DomainObjectAdapterDB; import ghidra.framework.data.DomainObjectAdapterDB;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
@@ -42,9 +43,6 @@ import ghidra.util.task.TaskLauncher;
class MultiProgramManager implements DomainObjectListener, TransactionListener { class MultiProgramManager implements DomainObjectListener, TransactionListener {
// arbitrary counter for given ProgramInfo objects and ID to use for sorting
private static final AtomicInteger nextAvailableId = new AtomicInteger();
private ProgramManagerPlugin plugin; private ProgramManagerPlugin plugin;
private PluginTool tool; private PluginTool tool;
private ProgramInfo currentInfo; private ProgramInfo currentInfo;
@@ -82,25 +80,31 @@ class MultiProgramManager implements DomainObjectListener, TransactionListener {
}; };
} }
void addProgram(Program p, URL ghidraURL, int state) { void addProgram(Program p, DomainFile domainFile, int state) {
addProgram(new ProgramInfo(p, domainFile, state != ProgramManager.OPEN_HIDDEN), state);
}
void addProgram(Program p, URL ghidraUrl, int state) {
addProgram(new ProgramInfo(p, ghidraUrl, state != ProgramManager.OPEN_HIDDEN), state);
}
private void addProgram(ProgramInfo programInfo, int state) {
Program p = programInfo.program;
ProgramInfo oldInfo = getInfo(p); ProgramInfo oldInfo = getInfo(p);
if (oldInfo == null) { if (oldInfo == null) {
oldInfo = programInfo;
p.addConsumer(tool); p.addConsumer(tool);
ProgramInfo info = new ProgramInfo(p, state != ProgramManager.OPEN_HIDDEN); openPrograms.add(oldInfo);
info.ghidraURL = ghidraURL;
openPrograms.add(info);
openPrograms.sort(Comparator.naturalOrder()); openPrograms.sort(Comparator.naturalOrder());
programMap.put(p, info); programMap.put(p, oldInfo);
fireOpenEvents(p); fireOpenEvents(p);
p.addListener(this); p.addListener(this);
p.addTransactionListener(this); p.addTransactionListener(this);
} }
else { else if (!oldInfo.visible && state != ProgramManager.OPEN_HIDDEN) {
if (!oldInfo.visible && state != ProgramManager.OPEN_HIDDEN) { oldInfo.setVisible(true);
oldInfo.setVisible(true);
}
} }
if (state == ProgramManager.OPEN_CURRENT) { if (state == ProgramManager.OPEN_CURRENT) {
saveLocation(); saveLocation();
@@ -246,12 +250,7 @@ class MultiProgramManager implements DomainObjectListener, TransactionListener {
TransientToolState toolState = null; TransientToolState toolState = null;
if (currentInfo != null) { if (currentInfo != null) {
currentInfo.setVisible(true); currentInfo.setVisible(true);
DomainFile df = currentInfo.program.getDomainFile(); tool.setSubTitle(currentInfo.toString());
String title = df.toString();
if (df.isReadOnly()) {
title = title + " [Read-Only]";
}
tool.setSubTitle(title);
txMonitor.setProgram(currentInfo.program); txMonitor.setProgram(currentInfo.program);
if (currentInfo.lastState != null) { if (currentInfo.lastState != null) {
toolState = currentInfo.lastState; toolState = currentInfo.lastState;
@@ -370,7 +369,7 @@ class MultiProgramManager implements DomainObjectListener, TransactionListener {
return (info != null && info.owner != null); return (info != null && info.owner != null);
} }
private ProgramInfo getInfo(Program p) { ProgramInfo getInfo(Program p) {
if (p == null) { if (p == null) {
return null; return null;
} }
@@ -378,9 +377,6 @@ class MultiProgramManager implements DomainObjectListener, TransactionListener {
} }
Program getOpenProgram(URL ghidraURL) { Program getOpenProgram(URL ghidraURL) {
if (!GhidraURL.isServerRepositoryURL(ghidraURL)) {
return null;
}
URL normalizedURL = GhidraURL.getNormalizedURL(ghidraURL); URL normalizedURL = GhidraURL.getNormalizedURL(ghidraURL);
for (ProgramInfo info : programMap.values()) { for (ProgramInfo info : programMap.values()) {
URL url = info.ghidraURL; URL url = info.ghidraURL;
@@ -392,10 +388,10 @@ class MultiProgramManager implements DomainObjectListener, TransactionListener {
} }
Program getOpenProgram(DomainFile domainFile, int version) { Program getOpenProgram(DomainFile domainFile, int version) {
for (Program program : programMap.keySet()) { for (ProgramInfo info : programMap.values()) {
DomainFile programDomainFile = program.getDomainFile(); DomainFile df = info.domainFile;
if (filesMatch(domainFile, version, programDomainFile)) { if (df != null && filesMatch(domainFile, version, df)) {
return program; return info.program;
} }
} }
return null; return null;
@@ -413,7 +409,7 @@ class MultiProgramManager implements DomainObjectListener, TransactionListener {
if (!SystemUtilities.isEqual(file1.getProjectLocator(), file2.getProjectLocator())) { if (!SystemUtilities.isEqual(file1.getProjectLocator(), file2.getProjectLocator())) {
return false; return false;
} }
// TODO: version check is questionable - unclear how proxy file would work
int openVersion = file2.isReadOnly() ? file2.getVersion() : -1; int openVersion = file2.isReadOnly() ? file2.getVersion() : -1;
return version == openVersion; return version == openVersion;
} }
@@ -488,28 +484,54 @@ class MultiProgramManager implements DomainObjectListener, TransactionListener {
OpenProgramTask openTask = new OpenProgramTask(file, -1, this); OpenProgramTask openTask = new OpenProgramTask(file, -1, this);
openTask.setSilent(); openTask.setSilent();
new TaskLauncher(openTask, tool.getToolFrame()); new TaskLauncher(openTask, tool.getToolFrame());
Program openProgram = openTask.getOpenProgram(); OpenProgramRequest openProgramReq = openTask.getOpenProgram();
plugin.openProgram(openProgram, if (openProgramReq != null) {
dataState != null ? ProgramManager.OPEN_CURRENT : ProgramManager.OPEN_VISIBLE); plugin.openProgram(openProgramReq.getProgram(),
openProgram.release(this); dataState != null ? ProgramManager.OPEN_CURRENT : ProgramManager.OPEN_VISIBLE);
removeProgram((Program) oldObject); openProgramReq.release();
if (dataState != null) { removeProgram((Program) oldObject);
tool.restoreDataStateFromXml(dataState); if (dataState != null) {
tool.restoreDataStateFromXml(dataState);
}
} }
} }
} }
private class ProgramInfo implements Comparable<ProgramInfo> { class ProgramInfo implements Comparable<ProgramInfo> {
// arbitrary counter for given ProgramInfo objects and ID to use for sorting
private static final AtomicInteger nextAvailableId = new AtomicInteger();
public final Program program;
// NOTE: domainFile and ghidraURL use are mutually exclusive and reflect how program was
// opened. Supported cases include:
// 1. Opened via Program file
// 2. Opened via ProgramLink file
// 3. Opened via Program URL
public final DomainFile domainFile; // may be link file
public final URL ghidraURL;
private Program program;
private URL ghidraURL;
private TransientToolState lastState; private TransientToolState lastState;
private int instance; private int instance;
private boolean visible; private boolean visible = false;
private Object owner; private Object owner;
ProgramInfo(Program p, boolean visible) { private String str; // cached toString
ProgramInfo(Program p, DomainFile domainFile, boolean visible) {
this.program = p; this.program = p;
this.domainFile = domainFile;
this.ghidraURL = null;
this.visible = visible;
instance = nextAvailableId.incrementAndGet();
}
ProgramInfo(Program p, URL ghidraURL, boolean visible) {
this.program = p;
this.domainFile = null;
this.ghidraURL = ghidraURL;
this.visible = visible; this.visible = visible;
instance = nextAvailableId.incrementAndGet(); instance = nextAvailableId.incrementAndGet();
} }
@@ -523,5 +545,24 @@ class MultiProgramManager implements DomainObjectListener, TransactionListener {
public int compareTo(ProgramInfo info) { public int compareTo(ProgramInfo info) {
return instance - info.instance; return instance - info.instance;
} }
@Override
public String toString() {
if (str != null) {
return str;
}
StringBuilder buf = new StringBuilder();
DomainFile df = program.getDomainFile();
if (domainFile != null && domainFile.isLinkFile()) {
buf.append(domainFile.getName());
buf.append("->");
}
buf.append(df.toString());
if (df.isReadOnly()) {
buf.append(" [Read-Only]");
}
str = buf.toString();
return str;
}
} }
} }
@@ -18,7 +18,6 @@ package ghidra.app.plugin.core.progmgr;
import java.awt.Component; import java.awt.Component;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.beans.PropertyEditor; import java.beans.PropertyEditor;
import java.io.IOException;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
@@ -32,26 +31,26 @@ import ghidra.app.CorePluginPackage;
import ghidra.app.context.ProgramActionContext; import ghidra.app.context.ProgramActionContext;
import ghidra.app.events.*; import ghidra.app.events.*;
import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.core.progmgr.MultiProgramManager.ProgramInfo;
import ghidra.app.services.ProgramManager; import ghidra.app.services.ProgramManager;
import ghidra.app.util.HelpTopics; import ghidra.app.util.HelpTopics;
import ghidra.app.util.NamespaceUtils; import ghidra.app.util.NamespaceUtils;
import ghidra.app.util.task.OpenProgramTask; import ghidra.app.util.task.OpenProgramTask;
import ghidra.app.util.task.OpenProgramTask.OpenProgramRequest;
import ghidra.framework.client.ClientUtil; import ghidra.framework.client.ClientUtil;
import ghidra.framework.data.ProjectFileManager;
import ghidra.framework.main.OpenVersionedFileDialog; import ghidra.framework.main.OpenVersionedFileDialog;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.framework.options.*; import ghidra.framework.options.*;
import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus; import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.framework.protocol.ghidra.*; import ghidra.framework.protocol.ghidra.GhidraURL;
import ghidra.program.database.ProgramContentHandler;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.Symbol; import ghidra.program.model.symbol.Symbol;
import ghidra.program.model.symbol.SymbolType; import ghidra.program.model.symbol.SymbolType;
import ghidra.program.util.*; import ghidra.program.util.*;
import ghidra.util.*; import ghidra.util.*;
import ghidra.util.exception.NotFoundException; import ghidra.util.exception.AssertException;
import ghidra.util.task.TaskLauncher; import ghidra.util.task.TaskLauncher;
//@formatter:off //@formatter:off
@@ -121,16 +120,21 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager {
if (domainFile == null) { if (domainFile == null) {
continue; continue;
} }
if (!(Program.class.isAssignableFrom(domainFile.getDomainObjectClass()))) { Class<? extends DomainObject> domainObjectClass = domainFile.getDomainObjectClass();
continue; if (Program.class.isAssignableFrom(domainObjectClass)) {
filesToOpen.add(domainFile);
} }
filesToOpen.add(domainFile);
} }
openPrograms(filesToOpen); openPrograms(filesToOpen);
return !filesToOpen.isEmpty(); return !filesToOpen.isEmpty();
} }
@Override
public boolean accept(URL url) {
return openProgram(url, OPEN_CURRENT) != null;
}
@Override @Override
public Class<?>[] getSupportedDataTypes() { public Class<?>[] getSupportedDataTypes() {
return new Class[] { Program.class }; return new Class[] { Program.class };
@@ -144,89 +148,53 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager {
return null; return null;
} }
return Swing.runNow(() -> doOpenProgram(ghidraURL, state)); // Check for URL already open and re-use
} URL url = GhidraURL.getNormalizedURL(ghidraURL);
Program p = programMgr.getOpenProgram(url);
private void messageBadProgramURL(URL ghidraURL) { if (p != null) {
Msg.showError(this, null, "Invalid Ghidra URL", showProgram(p, url, state);
"Ghidra URL does not reference a Ghidra Program: " + ghidraURL); if (state == ProgramManager.OPEN_CURRENT) {
} gotoProgramRef(p, ghidraURL.getRef());
protected Program doOpenProgram(URL ghidraURL, int openState) {
if (!GhidraURL.isServerRepositoryURL(ghidraURL)) {
Msg.showError(this, null, "Invalid Ghidra URL",
"Ghidra URL does not reference a Ghidra Program: " + ghidraURL);
return null;
}
Program openProgram = programMgr.getOpenProgram(ghidraURL);
if (openProgram != null) {
programMgr.addProgram(openProgram, GhidraURL.getNormalizedURL(ghidraURL), openState);
if (openState == ProgramManager.OPEN_CURRENT) {
gotoProgramRef(openProgram, ghidraURL.getRef());
programMgr.saveLocation(); programMgr.saveLocation();
} }
contextChanged(); return p;
return openProgram;
} }
GhidraURLWrappedContent wrappedContent = null; Program program = Swing.runNow(() -> doOpenProgram(ghidraURL, state));
Object content = null;
if (program != null) {
Msg.info(this, "Opened program in " + tool.getName() + " tool: " + ghidraURL);
}
return program;
}
/**
* Open GhidraURL which corresponds to {@code ghidra://} remote URLs which correspond to a
* repository program file.
* @param ghidraURL Ghidra URL which specified Program to be opened which optional ref
* @param openState open state
* @return program instance of null if open failed
*/
private Program doOpenProgram(URL ghidraURL, int openState) {
Program p = null;
try { try {
GhidraURLConnection c = (GhidraURLConnection) ghidraURL.openConnection(); URL url = GhidraURL.getNormalizedURL(ghidraURL);
Object obj = c.getContent(); OpenProgramTask task = new OpenProgramTask(url, this);
if (c.getResponseCode() == GhidraURLConnection.GHIDRA_UNAUTHORIZED) { new TaskLauncher(task, tool.getToolFrame());
return null; // assume user already notified OpenProgramRequest openProgramReq = task.getOpenProgram();
if (openProgramReq != null) {
p = openProgramReq.getProgram();
showProgram(p, url, openState);
openProgramReq.release();
} }
if (!(obj instanceof GhidraURLWrappedContent)) {
messageBadProgramURL(ghidraURL);
return null;
}
wrappedContent = (GhidraURLWrappedContent) obj;
content = wrappedContent.getContent(this);
if (!(content instanceof DomainFile)) {
messageBadProgramURL(ghidraURL);
return null;
}
DomainFile df = (DomainFile) content;
if (!ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(df.getContentType())) {
messageBadProgramURL(ghidraURL);
return null;
}
OpenProgramTask task = new OpenProgramTask(df, true, this);
TaskLauncher.launch(task);
openProgram = task.getOpenProgram();
if (openProgram == null) {
return null;
}
programMgr.addProgram(openProgram, GhidraURL.getNormalizedURL(ghidraURL), openState);
contextChanged();
openProgram.release(this);
if (openState == ProgramManager.OPEN_CURRENT) {
gotoProgramRef(openProgram, ghidraURL.getRef());
programMgr.saveLocation();
}
return openProgram;
}
catch (NotFoundException e) {
messageBadProgramURL(ghidraURL);
}
catch (MalformedURLException e) {
Msg.showError(this, null, "Invalid Ghidra URL",
"Improperly formed Ghidra URL: " + ghidraURL);
}
catch (IOException e) {
Msg.showError(this, null, "Program Open Failed",
"Failed to open Ghidra URL: " + e.getMessage());
} }
finally { finally {
if (content != null) { if (p != null && openState == ProgramManager.OPEN_CURRENT) {
wrappedContent.release(content, this); gotoProgramRef(p, ghidraURL.getRef());
programMgr.saveLocation();
} }
} }
return null; return p;
} }
private boolean gotoProgramRef(Program program, String ref) { private boolean gotoProgramRef(Program program, String ref) {
@@ -296,9 +264,7 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager {
} }
Program program = Swing.runNow(() -> { Program program = Swing.runNow(() -> {
Program p = doOpenProgram(domainFile, version, state); return doOpenProgram(domainFile, version, state);
contextChanged();
return p;
}); });
if (program != null) { if (program != null) {
@@ -460,13 +426,39 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager {
@Override @Override
public void openProgram(final Program program, final int state) { public void openProgram(final Program program, final int state) {
showProgram(program, program.getDomainFile(), state);
}
private void showProgram(Program p, URL ghidraUrl, final int state) {
if (p == null || p.isClosed()) {
throw new AssertException("Opened program required");
}
if (locked) { if (locked) {
throw new IllegalStateException( throw new IllegalStateException(
"Progam manager is locked and cannot accept a new program"); "Progam manager is locked and cannot accept a new program");
} }
Runnable r = () -> { Runnable r = () -> {
programMgr.addProgram(program, null, state); programMgr.addProgram(p, ghidraUrl, state);
if (state == ProgramManager.OPEN_CURRENT) {
programMgr.saveLocation();
}
contextChanged();
};
Swing.runNow(r);
}
private void showProgram(Program p, DomainFile domainFile, final int state) {
if (p == null || p.isClosed()) {
throw new AssertException("Opened program required");
}
if (locked) {
throw new IllegalStateException(
"Progam manager is locked and cannot accept a new program");
}
Runnable r = () -> {
programMgr.addProgram(p, domainFile, state);
if (state == ProgramManager.OPEN_CURRENT) { if (state == ProgramManager.OPEN_CURRENT) {
programMgr.saveLocation(); programMgr.saveLocation();
} }
@@ -625,11 +617,9 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager {
doOpenProgram(domainFile, version, OPEN_CURRENT); doOpenProgram(domainFile, version, OPEN_CURRENT);
} }
}; };
DomainFileFilter filter = f -> { openDialog = new OpenVersionedFileDialog(tool, "Open Program", f -> {
Class<?> c = f.getDomainObjectClass(); return Program.class.isAssignableFrom(f.getDomainObjectClass());
return Program.class.isAssignableFrom(c); });
};
openDialog = new OpenVersionedFileDialog(tool, "Open Program", filter);
openDialog.setHelpLocation(new HelpLocation(HelpTopics.PROGRAM, "Open_File_Dialog")); openDialog.setHelpLocation(new HelpLocation(HelpTopics.PROGRAM, "Open_File_Dialog"));
openDialog.addOkActionListener(listener); openDialog.addOkActionListener(listener);
} }
@@ -638,9 +628,12 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager {
} }
public void openPrograms(List<DomainFile> filesToOpen) { public void openPrograms(List<DomainFile> filesToOpen) {
Program showIfNeeded = null;
OpenProgramTask openTask = null; OpenProgramTask openTask = null;
for (DomainFile domainFile : filesToOpen) { for (DomainFile domainFile : filesToOpen) {
if (programMgr.getOpenProgram(domainFile, -1) != null) { Program p = programMgr.getOpenProgram(domainFile, -1);
if (p != null) {
showIfNeeded = p;
continue; continue;
} }
if (openTask == null) { if (openTask == null) {
@@ -652,32 +645,37 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager {
} }
if (openTask != null) { if (openTask != null) {
new TaskLauncher(openTask, tool.getToolFrame()); new TaskLauncher(openTask, tool.getToolFrame());
List<Program> openPrograms = openTask.getOpenPrograms(); List<OpenProgramRequest> openProgramReqs = openTask.getOpenPrograms();
boolean isFirst = true;
for (Program program : openPrograms) { for (OpenProgramRequest programReq : openProgramReqs) {
openProgram(program, OPEN_VISIBLE); showProgram(programReq.getProgram(), programReq.getDomainFile(),
program.release(this); isFirst ? OPEN_CURRENT : OPEN_VISIBLE);
} programReq.release();
if (!openPrograms.isEmpty()) { isFirst = false;
openProgram(openPrograms.get(0), OPEN_CURRENT); showIfNeeded = null;
} }
} }
if (showIfNeeded != null) {
showProgram(showIfNeeded, showIfNeeded.getDomainFile(), OPEN_CURRENT);
}
} }
protected Program doOpenProgram(DomainFile domainFile, int version, int openState) { protected Program doOpenProgram(DomainFile domainFile, int version, int openState) {
Program openProgram = programMgr.getOpenProgram(domainFile, version); Program p = programMgr.getOpenProgram(domainFile, version);
if (openProgram != null) { if (p != null) {
openProgram(openProgram, openState); openProgram(p, openState);
return openProgram;
} }
OpenProgramTask task = new OpenProgramTask(domainFile, version, this); else {
new TaskLauncher(task, tool.getToolFrame()); OpenProgramTask task = new OpenProgramTask(domainFile, version, this);
openProgram = task.getOpenProgram(); new TaskLauncher(task, tool.getToolFrame());
if (openProgram != null) { OpenProgramRequest programReq = task.getOpenProgram();
openProgram(openProgram, openState); if (programReq != null) {
openProgram.release(this); p = programReq.getProgram();
showProgram(p, programReq.getDomainFile(), openState);
programReq.release();
}
} }
return openProgram; return p;
} }
@Override @Override
@@ -705,18 +703,20 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager {
*/ */
@Override @Override
public void writeDataState(SaveState saveState) { public void writeDataState(SaveState saveState) {
// Only remember programs from non-transient projects
ArrayList<Program> programs = new ArrayList<>(); ArrayList<ProgramInfo> programInfos = new ArrayList<>();
for (Program p : programMgr.getAllPrograms()) { for (Program p : programMgr.getAllPrograms()) {
ProjectLocator projectLocator = p.getDomainFile().getProjectLocator(); ProgramInfo info = programMgr.getInfo(p);
if (projectLocator != null && !projectLocator.isTransient()) { if (info != null) {
programs.add(p); programInfos.add(info);
} }
} }
saveState.putInt("NUM_PROGRAMS", programs.size());
saveState.putInt("NUM_PROGRAMS", programInfos.size());
int i = 0; int i = 0;
for (Program p : programs) { for (ProgramInfo programInfo : programInfos) {
writeProgramInfo(p, saveState, i++); writeProgramInfo(programInfo, saveState, i++);
} }
Program p = programMgr.getCurrentProgram(); Program p = programMgr.getCurrentProgram();
if (p != null) { if (p != null) {
@@ -768,13 +768,21 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager {
} }
} }
private void writeProgramInfo(Program program, SaveState saveState, int index) { private void writeProgramInfo(ProgramInfo programInfo, SaveState saveState, int index) {
if (locked) { if (locked) {
return; // do not save state when locked. return; // do not save state when locked.
} }
if (programInfo.ghidraURL != null) {
saveState.putString("URL_" + index, programInfo.ghidraURL.toString());
return;
}
String projectLocation = null; String projectLocation = null;
String projectName = null; String projectName = null;
String path = null; String path = null;
Program program = programInfo.program;
DomainFile df = program.getDomainFile(); DomainFile df = program.getDomainFile();
ProjectLocator projectLocator = df.getProjectLocator(); ProjectLocator projectLocator = df.getProjectLocator();
if (projectLocator != null && !projectLocator.isTransient()) { if (projectLocator != null && !projectLocator.isTransient()) {
@@ -797,28 +805,30 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager {
* Read in my data state. * Read in my data state.
*/ */
private void loadPrograms(SaveState saveState) { private void loadPrograms(SaveState saveState) {
int n = saveState.getInt("NUM_PROGRAMS", 0); int n = saveState.getInt("NUM_PROGRAMS", 0);
if (n == 0) { if (n == 0) {
return; return;
} }
OpenProgramTask openTask = null; OpenProgramTask openTask = new OpenProgramTask(this);
for (int index = 0; index < n; index++) { for (int index = 0; index < n; index++) {
URL url = getGhidraURL(saveState, index);
if (url != null) {
openTask.addProgramToOpen(url);
continue;
}
DomainFile domainFile = getDomainFile(saveState, index); DomainFile domainFile = getDomainFile(saveState, index);
if (domainFile == null) { if (domainFile == null) {
continue; continue;
} }
int version = getVersion(saveState, index); int version = getVersion(saveState, index);
openTask.addProgramToOpen(domainFile, version);
if (openTask == null) {
openTask = new OpenProgramTask(domainFile, version, this);
}
else {
openTask.addProgramToOpen(domainFile, version);
}
} }
if (openTask == null) { if (!openTask.hasOpenProgramRequests()) {
return; return;
} }
@@ -835,10 +845,29 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager {
"Can't open program", e); "Can't open program", e);
} }
List<Program> openPrograms = openTask.getOpenPrograms(); List<OpenProgramRequest> openProgramReqs = openTask.getOpenPrograms();
for (Program program : openPrograms) { for (OpenProgramRequest programReq : openProgramReqs) {
openProgram(program, OPEN_VISIBLE); DomainFile df = programReq.getDomainFile();
program.release(this); if (df != null) {
showProgram(programReq.getProgram(), df, OPEN_VISIBLE);
}
else {
showProgram(programReq.getProgram(), programReq.getGhidraURL(), OPEN_VISIBLE);
}
programReq.release();
}
}
private URL getGhidraURL(SaveState saveState, int index) {
String url = saveState.getString("URL_" + index, null);
if (url == null) {
return null;
}
try {
return new URL(url);
}
catch (MalformedURLException e) {
return null;
} }
} }
@@ -853,20 +882,7 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager {
ProjectData projectData = tool.getProject().getProjectData(projectLocator); ProjectData projectData = tool.getProject().getProjectData(projectLocator);
if (projectData == null) { if (projectData == null) {
// Viewed project not available return null;
try {
projectData = new ProjectFileManager(projectLocator, false, false);
}
catch (NotOwnerException e) {
Msg.showError(this, tool.getToolFrame(), "Program Open Failed",
"Not project owner: " + projectLocator + "(" + pathname + ")");
return null;
}
catch (IOException e) {
Msg.showError(this, tool.getToolFrame(), "Program Open Failed",
"Project error: " + e.getMessage());
return null;
}
} }
DomainFile df = projectData.getFile(pathname); DomainFile df = projectData.getFile(pathname);
@@ -45,9 +45,17 @@ class ProgramSaveManager {
ProgramSaveManager(PluginTool tool, ProgramManager programMgr) { ProgramSaveManager(PluginTool tool, ProgramManager programMgr) {
this.tool = tool; this.tool = tool;
this.programMgr = programMgr; this.programMgr = programMgr;
domainFileFilter = f -> { domainFileFilter = new DomainFileFilter() {
Class<?> c = f.getDomainObjectClass();
return Program.class.isAssignableFrom(c); @Override
public boolean accept(DomainFile df) {
return Program.class.isAssignableFrom(df.getDomainObjectClass());
}
@Override
public boolean followLinkedFolders() {
return false; // can't save to linked-folder (read-only)
}
}; };
} }
@@ -244,7 +252,8 @@ class ProgramSaveManager {
return; return;
} }
if (existingFile != null) { if (existingFile != null) {
String msg = "Program " + name + " already exists.\n" + "Do you want to overwrite it?"; String msg = existingFile.getContentType() + " file " + name + " already exists.\n" +
"Do you want to overwrite it?";
if (OptionDialog.showOptionDialog(tool.getToolFrame(), "Duplicate Name", msg, if (OptionDialog.showOptionDialog(tool.getToolFrame(), "Duplicate Name", msg,
"Overwrite", OptionDialog.QUESTION_MESSAGE) == OptionDialog.CANCEL_OPTION) { "Overwrite", OptionDialog.QUESTION_MESSAGE) == OptionDialog.CANCEL_OPTION) {
return; return;
@@ -312,7 +312,7 @@ public class HeadlessAnalyzer {
Object obj = c.getContent(); Object obj = c.getContent();
if (!(obj instanceof GhidraURLWrappedContent)) { if (!(obj instanceof GhidraURLWrappedContent)) {
throw new IOException( throw new IOException(
"Connect to repository folder failed. Response code: " + c.getResponseCode()); "Connect to repository folder failed. Response code: " + c.getStatusCode());
} }
GhidraURLWrappedContent wrappedContent = (GhidraURLWrappedContent) obj; GhidraURLWrappedContent wrappedContent = (GhidraURLWrappedContent) obj;
Object content = null; Object content = null;
@@ -336,7 +336,7 @@ public class HeadlessAnalyzer {
processWithImport(folder.getPathname(), filesToImport); processWithImport(folder.getPathname(), filesToImport);
} }
} }
catch (NotFoundException e) { catch (FileNotFoundException e) {
throw new IOException("Connect to repository folder failed"); throw new IOException("Connect to repository folder failed");
} }
finally { finally {
@@ -369,7 +369,8 @@ public class HeadlessAnalyzer {
* @param rootFolderPath root folder for imports * @param rootFolderPath root folder for imports
* @param filesToImport directories and files to be imported (null or empty is acceptable if * @param filesToImport directories and files to be imported (null or empty is acceptable if
* we are in -process mode) * we are in -process mode)
* @throws IOException if there was an IO-related problem * @throws IOException if there was an IO-related problem. If caused by a failure to obtain a
* write-lock on the project the exception cause will a {@code LockException}.
*/ */
public void processLocal(String projectLocation, String projectName, String rootFolderPath, public void processLocal(String projectLocation, String projectName, String rootFolderPath,
List<File> filesToImport) throws IOException { List<File> filesToImport) throws IOException {
@@ -1107,6 +1108,8 @@ public class HeadlessAnalyzer {
return; return;
} }
// Do not follow folder-links or consider program links. Using content type
// to filter is best way to control this.
if (!ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(domFile.getContentType())) { if (!ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(domFile.getContentType())) {
return; // skip non-Program files return; // skip non-Program files
} }
@@ -1275,6 +1278,8 @@ public class HeadlessAnalyzer {
for (DomainFile domFile : parentFolder.getFiles()) { for (DomainFile domFile : parentFolder.getFiles()) {
if (filenamePattern == null || filenamePattern.matcher(domFile.getName()).matches()) { if (filenamePattern == null || filenamePattern.matcher(domFile.getName()).matches()) {
// Do not follow folder-links or consider program links. Using content type
// to filter is best way to control this.
if (ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(domFile.getContentType())) { if (ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(domFile.getContentType())) {
filesProcessed = true; filesProcessed = true;
processFileNoImport(domFile); processFileNoImport(domFile);
@@ -1308,6 +1313,8 @@ public class HeadlessAnalyzer {
boolean filesProcessed = false; boolean filesProcessed = false;
DomainFile domFile = parentFolder.getFile(filename); DomainFile domFile = parentFolder.getFile(filename);
// Do not follow folder-links or consider program links. Using content type
// to filter is best way to control this.
if (domFile != null && if (domFile != null &&
ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(domFile.getContentType())) { ProgramContentHandler.PROGRAM_CONTENT_TYPE.equals(domFile.getContentType())) {
filesProcessed = true; filesProcessed = true;
@@ -16,8 +16,9 @@
package ghidra.app.util.task; package ghidra.app.util.task;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.net.MalformedURLException;
import java.util.List; import java.net.URL;
import java.util.*;
import docking.widgets.OptionDialog; import docking.widgets.OptionDialog;
import ghidra.app.util.dialog.CheckoutDialog; import ghidra.app.util.dialog.CheckoutDialog;
@@ -25,8 +26,11 @@ import ghidra.framework.client.ClientUtil;
import ghidra.framework.client.RepositoryAdapter; import ghidra.framework.client.RepositoryAdapter;
import ghidra.framework.main.AppInfo; import ghidra.framework.main.AppInfo;
import ghidra.framework.model.DomainFile; import ghidra.framework.model.DomainFile;
import ghidra.framework.protocol.ghidra.*;
import ghidra.framework.protocol.ghidra.GhidraURLConnection.StatusCode;
import ghidra.framework.remote.User; import ghidra.framework.remote.User;
import ghidra.framework.store.ExclusiveCheckoutException; import ghidra.framework.store.ExclusiveCheckoutException;
import ghidra.program.database.ProgramLinkContentHandler;
import ghidra.program.model.lang.LanguageNotFoundException; import ghidra.program.model.lang.LanguageNotFoundException;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.util.*; import ghidra.util.*;
@@ -37,8 +41,8 @@ import ghidra.util.task.TaskMonitor;
public class OpenProgramTask extends Task { public class OpenProgramTask extends Task {
private final List<DomainFileInfo> domainFileInfoList = new ArrayList<>(); private final List<OpenProgramRequest> openProgramRequests = new ArrayList<>();
private List<Program> programList = new ArrayList<>(); private List<OpenProgramRequest> openedProgramList = new ArrayList<>();
private final Object consumer; private final Object consumer;
private boolean silent; // if true operation does not permit interaction private boolean silent; // if true operation does not permit interaction
@@ -46,11 +50,16 @@ public class OpenProgramTask extends Task {
private String openPromptText = "Open"; private String openPromptText = "Open";
public OpenProgramTask(Object consumer) {
super("Open Program(s)", true, false, true);
this.consumer = consumer;
}
public OpenProgramTask(DomainFile domainFile, int version, boolean forceReadOnly, public OpenProgramTask(DomainFile domainFile, int version, boolean forceReadOnly,
Object consumer) { Object consumer) {
super("Open Program(s)", true, false, true); super("Open Program(s)", true, false, true);
this.consumer = consumer; this.consumer = consumer;
domainFileInfoList.add(new DomainFileInfo(domainFile, version, forceReadOnly)); openProgramRequests.add(new OpenProgramRequest(domainFile, version, forceReadOnly));
} }
public OpenProgramTask(DomainFile domainFile, int version, Object consumer) { public OpenProgramTask(DomainFile domainFile, int version, Object consumer) {
@@ -65,17 +74,10 @@ public class OpenProgramTask extends Task {
this(domainFile, DomainFile.DEFAULT_VERSION, false, consumer); this(domainFile, DomainFile.DEFAULT_VERSION, false, consumer);
} }
public OpenProgramTask(List<DomainFile> domainFileList, boolean forceReadOnly, public OpenProgramTask(URL ghidraURL, Object consumer) {
Object consumer) { super("Open Program(s)", true, false, true);
super("Open Program(s)", true, domainFileList.size() > 1, true);
this.consumer = consumer; this.consumer = consumer;
for (DomainFile domainFile : domainFileList) { openProgramRequests.add(new OpenProgramRequest(ghidraURL));
domainFileInfoList.add(new DomainFileInfo(domainFile, -1, forceReadOnly));
}
}
public OpenProgramTask(List<DomainFile> domainFileList, Object consumer) {
this(domainFileList, false, consumer);
} }
public void setOpenPromptText(String text) { public void setOpenPromptText(String text) {
@@ -88,7 +90,16 @@ public class OpenProgramTask extends Task {
public void addProgramToOpen(DomainFile domainFile, int version, boolean forceReadOnly) { public void addProgramToOpen(DomainFile domainFile, int version, boolean forceReadOnly) {
setHasProgress(true); setHasProgress(true);
domainFileInfoList.add(new DomainFileInfo(domainFile, version, forceReadOnly)); openProgramRequests.add(new OpenProgramRequest(domainFile, version, forceReadOnly));
}
public void addProgramToOpen(URL ghidraURL) {
setHasProgress(true);
openProgramRequests.add(new OpenProgramRequest(ghidraURL));
}
public boolean hasOpenProgramRequests() {
return !openProgramRequests.isEmpty();
} }
/** /**
@@ -110,100 +121,97 @@ public class OpenProgramTask extends Task {
this.noCheckout = true; this.noCheckout = true;
} }
public List<Program> getOpenPrograms() { /**
return programList; * Get all successful open program requests
* @return all successful open program requests
*/
public List<OpenProgramRequest> getOpenPrograms() {
return Collections.unmodifiableList(openedProgramList);
} }
public Program getOpenProgram() { /**
if (programList.isEmpty()) { * Get the first successful open program request
* @return first successful open program request or null if none
*/
public OpenProgramRequest getOpenProgram() {
if (openedProgramList.isEmpty()) {
return null; return null;
} }
return programList.get(0); return openedProgramList.get(0);
} }
@Override @Override
public void run(TaskMonitor monitor) { public void run(TaskMonitor monitor) {
taskMonitor.initialize(domainFileInfoList.size()); taskMonitor.initialize(openProgramRequests.size());
for (DomainFileInfo domainFileInfo : domainFileInfoList) { for (OpenProgramRequest domainFileInfo : openProgramRequests) {
if (taskMonitor.isCancelled()) { if (taskMonitor.isCancelled()) {
return; return;
} }
openDomainFile(domainFileInfo); domainFileInfo.open();
taskMonitor.incrementProgress(1); taskMonitor.incrementProgress(1);
} }
} }
private void openDomainFile(DomainFileInfo domainFileInfo) { private Object openReadOnlyFile(DomainFile domainFile, URL url, int version) {
int version = domainFileInfo.getVersion();
DomainFile domainFile = domainFileInfo.getDomainFile();
if (version != DomainFile.DEFAULT_VERSION) {
openVersionedFile(domainFile, version);
}
else if (domainFileInfo.isReadOnly()) {
openReadOnlyFile(domainFile, version);
}
else {
openUnversionedFile(domainFile);
}
}
private void openReadOnlyFile(DomainFile domainFile, int version) {
taskMonitor.setMessage("Opening " + domainFile.getName()); taskMonitor.setMessage("Opening " + domainFile.getName());
openReadOnly(domainFile, version); return openReadOnly(domainFile, url, version);
} }
private void openVersionedFile(DomainFile domainFile, int version) { private Object openVersionedFile(DomainFile domainFile, URL url, int version) {
taskMonitor.setMessage("Getting Version " + version + " for " + domainFile.getName()); taskMonitor.setMessage("Getting Version " + version + " for " + domainFile.getName());
openReadOnly(domainFile, version); return openReadOnly(domainFile, url, version);
} }
private void openReadOnly(DomainFile domainFile, int version) { private Object openReadOnly(DomainFile domainFile, URL url, int version) {
String contentType = null; String contentType = domainFile.getContentType();
String path = url != null ? url.toString() : domainFile.getPathname();
Object obj = null;
try { try {
contentType = domainFile.getContentType();
Program program =
(Program) domainFile.getReadOnlyDomainObject(consumer, version, taskMonitor);
if (program == null) { obj = domainFile.getReadOnlyDomainObject(consumer, version, taskMonitor);
String errorMessage = "Can't open program - \"" + domainFile.getPathname() + "\"";
if (obj == null) {
String errorMessage = "Can't open " + contentType + " - \"" + path + "\"";
if (version != DomainFile.DEFAULT_VERSION) { if (version != DomainFile.DEFAULT_VERSION) {
errorMessage += " version " + version; errorMessage += " version " + version;
} }
Msg.showError(this, null, "DomainFile Not Found", errorMessage); Msg.showError(this, null, "File Not Found", errorMessage);
}
else {
programList.add(program);
} }
} }
catch (CancelledException e) { catch (CancelledException e) {
// we don't care, the task has been cancelled // we don't care, the task has been cancelled
} }
catch (IOException e) { catch (IOException e) {
if (domainFile.isInWritableProject()) { if (url == null && domainFile.isInWritableProject()) {
ClientUtil.handleException(AppInfo.getActiveProject().getRepository(), e, ClientUtil.handleException(AppInfo.getActiveProject().getRepository(), e,
"Get Versioned Object", null); "Get " + contentType, null);
}
else if (version != DomainFile.DEFAULT_VERSION) {
Msg.showError(this, null, "Error Getting Versioned Program",
"Could not get version " + version + " for " + path, e);
} }
else { else {
Msg.showError(this, null, "Error Getting Versioned Object", Msg.showError(this, null, "Error Getting Program",
"Could not get version " + version + " for " + domainFile.getName(), e); "Open program failed for " + path, e);
} }
} }
catch (VersionException e) { catch (VersionException e) {
VersionExceptionHandler.showVersionError(null, domainFile.getName(), contentType, VersionExceptionHandler.showVersionError(null, domainFile.getName(), contentType,
"Open", e); "Open", e);
} }
return obj;
} }
private void openUnversionedFile(DomainFile domainFile) { private Program openUnversionedFile(DomainFile domainFile) {
String filename = domainFile.getName(); String filename = domainFile.getName();
taskMonitor.setMessage("Opening " + filename); taskMonitor.setMessage("Opening " + filename);
performOptionalCheckout(domainFile); performOptionalCheckout(domainFile);
try { try {
openFileMaybeUgrade(domainFile); return openFileMaybeUgrade(domainFile);
} }
catch (VersionException e) { catch (VersionException e) {
String contentType = domainFile.getContentType(); String contentType = domainFile.getContentType();
@@ -226,9 +234,10 @@ public class OpenProgramTask extends Task {
"Getting domain object failed.\n" + e.getMessage(), e); "Getting domain object failed.\n" + e.getMessage(), e);
} }
} }
return null;
} }
private void openFileMaybeUgrade(DomainFile domainFile) private Program openFileMaybeUgrade(DomainFile domainFile)
throws IOException, CancelledException, VersionException { throws IOException, CancelledException, VersionException {
boolean recoverFile = false; boolean recoverFile = false;
@@ -236,24 +245,18 @@ public class OpenProgramTask extends Task {
recoverFile = askRecoverFile(domainFile.getName()); recoverFile = askRecoverFile(domainFile.getName());
} }
Program program = null;
try { try {
Program program = program =
(Program) domainFile.getDomainObject(consumer, false, recoverFile, taskMonitor); (Program) domainFile.getDomainObject(consumer, false, recoverFile, taskMonitor);
if (program != null) {
programList.add(program);
}
} }
catch (VersionException e) { catch (VersionException e) {
if (VersionExceptionHandler.isUpgradeOK(null, domainFile, openPromptText, e)) { if (VersionExceptionHandler.isUpgradeOK(null, domainFile, openPromptText, e)) {
Program program = program =
(Program) domainFile.getDomainObject(consumer, true, recoverFile, taskMonitor); (Program) domainFile.getDomainObject(consumer, true, recoverFile, taskMonitor);
if (program != null) {
programList.add(program);
}
} }
} }
return program;
} }
private boolean askRecoverFile(final String filename) { private boolean askRecoverFile(final String filename) {
@@ -294,32 +297,158 @@ public class OpenProgramTask extends Task {
} }
} }
static class DomainFileInfo { public class OpenProgramRequest {
private final DomainFile domainFile;
private final int version;
private boolean forceReadOnly;
public DomainFileInfo(DomainFile domainFile, int version, boolean forceReadOnly) { // ghidraURL and domainFile use are mutually exclusive
private final URL ghidraURL;
private final DomainFile domainFile;
private URL linkURL; // link URL read from domainFile
private final int version;
private final boolean forceReadOnly;
private Program program;
public OpenProgramRequest(URL ghidraURL) {
if (!GhidraURL.PROTOCOL.equals(ghidraURL.getProtocol())) {
throw new IllegalArgumentException(
"unsupported protocol: " + ghidraURL.getProtocol());
}
this.ghidraURL = ghidraURL;
this.domainFile = null;
this.version = -1;
this.forceReadOnly = true;
}
public OpenProgramRequest(DomainFile domainFile, int version, boolean forceReadOnly) {
this.domainFile = domainFile; this.domainFile = domainFile;
this.ghidraURL = null;
this.version = this.version =
(domainFile.isReadOnly() && domainFile.isVersioned()) ? domainFile.getVersion() (domainFile.isReadOnly() && domainFile.isVersioned()) ? domainFile.getVersion()
: version; : version;
this.forceReadOnly = forceReadOnly; this.forceReadOnly = forceReadOnly;
} }
public boolean isReadOnly() { /**
return forceReadOnly || domainFile.isReadOnly() || * Get the {@link DomainFile} which corresponds to program open request. This will be
version != DomainFile.DEFAULT_VERSION; * null for all URL-based open requests.
} * @return {@link DomainFile} which corresponds to program open request or null.
*/
public DomainFile getDomainFile() { public DomainFile getDomainFile() {
return domainFile; return domainFile;
} }
public int getVersion() { /**
return version; * Get the {@link URL} which corresponds to program open request. This will be
* null for all non-URL-based open requests. URL will be a {@link GhidraURL}.
* @return {@link URL} which corresponds to program open request or null.
*/
public URL getGhidraURL() {
return ghidraURL;
} }
/**
* Get the {@link URL} which corresponds to the link domainFile used to open a program.
* @return {@link URL} which corresponds to the link domainFile used to open a program.
*/
public URL getLinkURL() {
return linkURL;
}
/**
* Get the open Program instance which corresponds to this open request.
* @return program instance or null if never opened.
*/
public Program getProgram() {
return program;
}
/**
* Release opened program. This must be done once, and only once, on a successful
* open request. If handing ownership off to another consumer, they should be added
* as a program consumer prior to invoking this method. Releasing the last consumer
* will close the program instance.
*/
public void release() {
if (program != null) {
program.release(consumer);
}
}
private Program openProgram(DomainFile df, URL url) {
if (version != DomainFile.DEFAULT_VERSION) {
return (Program) openVersionedFile(df, url, version);
}
if (forceReadOnly) {
return (Program) openReadOnlyFile(df, url, version);
}
return openUnversionedFile(df);
}
void open() {
DomainFile df = domainFile;
URL url = ghidraURL;
GhidraURLWrappedContent wrappedContent = null;
Object content = null;
try {
if (df == null && url != null) {
GhidraURLConnection c = (GhidraURLConnection) url.openConnection();
Object obj = c.getContent(); // read-only access
if (c.getStatusCode() == StatusCode.UNAUTHORIZED) {
return; // assume user already notified
}
if (!(obj instanceof GhidraURLWrappedContent)) {
messageBadProgramURL(url);
return;
}
wrappedContent = (GhidraURLWrappedContent) obj;
content = wrappedContent.getContent(this);
if (!(content instanceof DomainFile)) {
messageBadProgramURL(url);
return;
}
df = (DomainFile) content;
if (ProgramLinkContentHandler.PROGRAM_LINK_CONTENT_TYPE
.equals(df.getContentType())) {
Msg.showError(this, null, "Program Multi-Link Error",
"Multi-link Program access not supported: " + url);
return;
}
}
if (!Program.class.isAssignableFrom(df.getDomainObjectClass())) {
Msg.showError(this, null, "Error Opening Program",
"File does not correspond to a Ghidra Program: " + df.getPathname());
return;
}
program = openProgram(df, url);
}
catch (MalformedURLException e) {
Msg.showError(this, null, "Invalid Ghidra URL",
"Improperly formed Ghidra URL: " + url);
}
catch (IOException e) {
Msg.showError(this, null, "Program Open Failed",
"Failed to open Ghidra URL: " + e.getMessage());
}
finally {
if (content != null) {
wrappedContent.release(content, this);
}
}
if (program != null) {
openedProgramList.add(this);
}
}
private void messageBadProgramURL(URL url) {
Msg.error("Invalid Ghidra URL",
"Ghidra URL does not reference a Ghidra Program: " + url);
}
} }
} }
@@ -33,6 +33,7 @@ public class Annotation {
private static final Pattern QUOTATION_PATTERN = private static final Pattern QUOTATION_PATTERN =
Pattern.compile("(?<!\\\\)[\"](.*?)(?<!\\\\)[\"]"); Pattern.compile("(?<!\\\\)[\"](.*?)(?<!\\\\)[\"]");
private static List<AnnotatedStringHandler> ANNOTATED_STRING_HANDLERS;
private static Map<String, AnnotatedStringHandler> ANNOTATED_STRING_MAP; private static Map<String, AnnotatedStringHandler> ANNOTATED_STRING_MAP;
private String annotationText; private String annotationText;
@@ -40,6 +41,13 @@ public class Annotation {
private AnnotatedStringHandler annotatedStringHandler; private AnnotatedStringHandler annotatedStringHandler;
private AttributedString displayString; private AttributedString displayString;
public static List<AnnotatedStringHandler> getAnnotatedStringHandlers() {
if (ANNOTATED_STRING_HANDLERS == null) {
ANNOTATED_STRING_HANDLERS = getSupportedAnnotationHandlers();
}
return ANNOTATED_STRING_HANDLERS;
}
private static Map<String, AnnotatedStringHandler> getAnnotatedStringHandlerMap() { private static Map<String, AnnotatedStringHandler> getAnnotatedStringHandlerMap() {
if (ANNOTATED_STRING_MAP == null) { // lazy init due to our use of ClassSearcher if (ANNOTATED_STRING_MAP == null) { // lazy init due to our use of ClassSearcher
ANNOTATED_STRING_MAP = createAnnotatedStringHandlerMap(); ANNOTATED_STRING_MAP = createAnnotatedStringHandlerMap();
@@ -47,24 +55,28 @@ public class Annotation {
return ANNOTATED_STRING_MAP; return ANNOTATED_STRING_MAP;
} }
// locates AnnotatedStringHandler implementations to handle annotations
private static Map<String, AnnotatedStringHandler> createAnnotatedStringHandlerMap() { private static Map<String, AnnotatedStringHandler> createAnnotatedStringHandlerMap() {
Map<String, AnnotatedStringHandler> map = new HashMap<>(); Map<String, AnnotatedStringHandler> map = new HashMap<>();
for (AnnotatedStringHandler instance : getAnnotatedStringHandlers()) {
// find all instances of AnnotatedString
List<AnnotatedStringHandler> instances =
ClassSearcher.getInstances(AnnotatedStringHandler.class);
for (AnnotatedStringHandler instance : instances) {
String[] supportedAnnotations = instance.getSupportedAnnotations(); String[] supportedAnnotations = instance.getSupportedAnnotations();
for (String supportedAnnotation : supportedAnnotations) { for (String supportedAnnotation : supportedAnnotations) {
map.put(supportedAnnotation, instance); map.put(supportedAnnotation, instance);
} }
} }
return Collections.unmodifiableMap(map); return Collections.unmodifiableMap(map);
} }
// locates AnnotatedStringHandler implementations to handle annotations
private static List<AnnotatedStringHandler> getSupportedAnnotationHandlers() {
List<AnnotatedStringHandler> list = new ArrayList<>();
for (AnnotatedStringHandler h : ClassSearcher.getInstances(AnnotatedStringHandler.class)) {
if (h.getSupportedAnnotations().length != 0) {
list.add(h);
}
}
return Collections.unmodifiableList(list);
}
/** /**
* Constructor * Constructor
* <b>Note</b>: This constructor assumes that the string starts with "{<pre>@</pre>" and ends with '}' * <b>Note</b>: This constructor assumes that the string starts with "{<pre>@</pre>" and ends with '}'
@@ -184,14 +196,6 @@ public class Annotation {
return annotationText; return annotationText;
} }
public static AnnotatedStringHandler[] getAnnotatedStringHandlers() {
Set<AnnotatedStringHandler> annotations =
new HashSet<>(getAnnotatedStringHandlerMap().values());
AnnotatedStringHandler[] retVal = new AnnotatedStringHandler[annotations.size()];
annotations.toArray(retVal);
return retVal;
}
/*package*/ static Set<String> getAnnotationNames() { /*package*/ static Set<String> getAnnotationNames() {
return Collections.unmodifiableSet(getAnnotatedStringHandlerMap().keySet()); return Collections.unmodifiableSet(getAnnotatedStringHandlerMap().keySet());
} }
@@ -0,0 +1,34 @@
/* ###
* 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.app.util.viewer.field;
/**
* This implementation expands {@link URLAnnotatedStringHandler} providing an example form
* of a local project Ghidra URL.
*/
public class GhidraLocalURLAnnotatedStringHandler extends URLAnnotatedStringHandler {
@Override
public String getDisplayString() {
return "Ghidra-URL(local)";
}
@Override
public String getPrototypeString() {
return "{@url \"ghidra:/dirpath/myproject?/folder/program.exe#symbol\" \"display string\"}";
}
}
@@ -0,0 +1,34 @@
/* ###
* 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.app.util.viewer.field;
/**
* This implementation expands {@link URLAnnotatedStringHandler} providing an example form
* of a Ghidra Server URL.
*/
public class GhidraServerURLAnnotatedStringHandler extends URLAnnotatedStringHandler {
@Override
public String getDisplayString() {
return "Ghidra-URL(remote)";
}
@Override
public String getPrototypeString() {
return "{@url \"ghidra://myserver/myrepo/folder/program.exe#symbol\" \"display string\"}";
}
}
@@ -36,8 +36,10 @@ import ghidra.util.Msg;
* displayed. * displayed.
*/ */
public class URLAnnotatedStringHandler implements AnnotatedStringHandler { public class URLAnnotatedStringHandler implements AnnotatedStringHandler {
private static final String INVALID_SYMBOL_TEXT = private static final String INVALID_SYMBOL_TEXT =
"@url annotation must have a URL string " + "optionally followed by a display string"; "@url annotation must have a URL string optionally followed by a display string";
private static final String[] SUPPORTED_ANNOTATIONS = { "url", "hyperlink", "href", "link" }; private static final String[] SUPPORTED_ANNOTATIONS = { "url", "hyperlink", "href", "link" };
@Override @Override
@@ -233,6 +233,13 @@ public class GhidraProject {
return project; return project;
} }
/**
* Returns the underlying ProjectData instance.
*/
public ProjectData getProjectData() {
return projectData;
}
/** /**
* Closes the ghidra project, closing (without saving!) any open programs in * Closes the ghidra project, closing (without saving!) any open programs in
* that project. Also deletes the project if created as a temporary project. * that project. Also deletes the project if created as a temporary project.
@@ -93,7 +93,10 @@ public class DataTreeDialog extends DialogComponentProvider
private Integer treeSelectionMode; private Integer treeSelectionMode;
/** /**
* Construct a new DataTreeDialog. * Construct a new DataTreeDialog. This chooser will show all project files.
* Following linked-folders will only be allowed if a type of {@link #CHOOSE_FOLDER}
* or {@link #OPEN} is specified. If different behavior is required a filter should
* be specified using the other constructor.
* *
* @param parent dialog's parent * @param parent dialog's parent
* @param title title to use * @param title title to use
@@ -101,7 +104,7 @@ public class DataTreeDialog extends DialogComponentProvider
* @throws IllegalArgumentException if invalid type is specified * @throws IllegalArgumentException if invalid type is specified
*/ */
public DataTreeDialog(Component parent, String title, int type) { public DataTreeDialog(Component parent, String title, int type) {
this(parent, title, type, null); this(parent, title, type, getDefaultFilter(type));
} }
/** /**
@@ -119,6 +122,20 @@ public class DataTreeDialog extends DialogComponentProvider
initDataTreeDialog(type, filter); initDataTreeDialog(type, filter);
} }
private static DomainFileFilter getDefaultFilter(int type) {
if (type == CHOOSE_FOLDER || type == OPEN) {
// return filter which forces folder selection and allow navigation into linked-folders
return new DomainFileFilter() {
@Override
public boolean accept(DomainFile df) {
return true; // show all files (legacy behavior)
}
};
}
return null;
}
public void setTreeSelectionMode(int mode) { public void setTreeSelectionMode(int mode) {
if (treePanel != null) { if (treePanel != null) {
treePanel.getTreeSelectionModel().setSelectionMode(mode); treePanel.getTreeSelectionModel().setSelectionMode(mode);
@@ -18,6 +18,7 @@ package ghidra.test;
import java.awt.Dialog; import java.awt.Dialog;
import java.awt.Window; import java.awt.Window;
import java.io.*; import java.io.*;
import java.net.URL;
import java.util.*; import java.util.*;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -47,6 +48,7 @@ import ghidra.framework.plugintool.Plugin;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.util.PluginException; import ghidra.framework.plugintool.util.PluginException;
import ghidra.framework.project.DefaultProjectManager; import ghidra.framework.project.DefaultProjectManager;
import ghidra.framework.protocol.ghidra.GhidraURL;
import ghidra.program.database.ProgramDB; import ghidra.program.database.ProgramDB;
import ghidra.program.model.data.FileDataTypeManager; import ghidra.program.model.data.FileDataTypeManager;
import ghidra.program.model.lang.*; import ghidra.program.model.lang.*;
@@ -874,26 +876,15 @@ public class TestEnv {
* @param domainFile The domain file used to launch the tool; may be null * @param domainFile The domain file used to launch the tool; may be null
* @return the tool that is launched * @return the tool that is launched
*/ */
public PluginTool launchTool(final String toolName, final DomainFile domainFile) { public PluginTool launchTool(String toolName, DomainFile domainFile) {
AtomicReference<PluginTool> ref = new AtomicReference<>(); AtomicReference<PluginTool> ref = new AtomicReference<>();
AbstractGenericTest.runSwing(() -> { AbstractGenericTest.runSwing(() -> {
boolean wasErrorGUIEnabled = AbstractDockingTest.isUseErrorGUI(); PluginTool newTool = doLaunchTool(toolName);
AbstractDockingTest.setErrorGUIEnabled(false); // disable the error GUI while launching the tool
FrontEndTool frontEndToolInstance = getFrontEndTool();
Project project = frontEndToolInstance.getProject();
ToolServices toolServices = project.getToolServices();
PluginTool newTool = toolServices.launchTool(toolName, null);
if (newTool == null) {
// couldn't find the tool in the workspace...check the test area
newTool = launchDefaultToolByName(toolName);
}
ref.set(newTool); ref.set(newTool);
if (newTool != null) {
AbstractDockingTest.setErrorGUIEnabled(wasErrorGUIEnabled); newTool.acceptDomainFiles(new DomainFile[] { domainFile });
newTool.acceptDomainFiles(new DomainFile[] { domainFile }); }
}); });
PluginTool launchedTool = ref.get(); PluginTool launchedTool = ref.get();
@@ -906,6 +897,52 @@ public class TestEnv {
return launchedTool; return launchedTool;
} }
/**
* Launches a tool of the given name using the given Ghidra URL.
* <p>
* Note: the tool returned will have auto save disabled by default.
*
* @param toolName the name of the tool to launch
* @param ghidraUrl The Ghidra URL to be opened in tool (see {@link GhidraURL})
* @return the tool that is launched
*/
public PluginTool launchToolWithURL(String toolName, URL ghidraUrl) {
AtomicReference<PluginTool> ref = new AtomicReference<>();
AbstractGenericTest.runSwing(() -> {
PluginTool newTool = doLaunchTool(toolName);
ref.set(newTool);
if (newTool != null) {
newTool.accept(ghidraUrl);
}
});
PluginTool launchedTool = ref.get();
if (launchedTool == null) {
throw new NullPointerException("Unable to launch the tool: " + toolName);
}
// this will make sure that our tool is closed during disposal
extraTools.add(launchedTool);
return launchedTool;
}
private PluginTool doLaunchTool(String toolName) {
boolean wasErrorGUIEnabled = AbstractDockingTest.isUseErrorGUI();
AbstractDockingTest.setErrorGUIEnabled(false); // disable the error GUI while launching the tool
FrontEndTool frontEndToolInstance = getFrontEndTool();
Project project = frontEndToolInstance.getProject();
ToolServices toolServices = project.getToolServices();
PluginTool newTool = toolServices.launchTool(toolName, null);
if (newTool == null) {
// couldn't find the tool in the workspace...check the test area
newTool = launchDefaultToolByName(toolName);
}
AbstractDockingTest.setErrorGUIEnabled(wasErrorGUIEnabled);
return newTool;
}
/** /**
* Sets the auto-save feature for all tool instances running under the {@link FrontEndTool} * Sets the auto-save feature for all tool instances running under the {@link FrontEndTool}
* created by this TestEnv instance. Auto-save is off by default when testing. * created by this TestEnv instance. Auto-save is off by default when testing.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 410 B

@@ -19,6 +19,8 @@ import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.awt.*; import java.awt.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@@ -37,6 +39,8 @@ import ghidra.framework.model.*;
import ghidra.framework.plugintool.ServiceProvider; import ghidra.framework.plugintool.ServiceProvider;
import ghidra.framework.plugintool.TestDummyServiceProvider; import ghidra.framework.plugintool.TestDummyServiceProvider;
import ghidra.framework.project.ProjectDataService; import ghidra.framework.project.ProjectDataService;
import ghidra.framework.protocol.ghidra.GhidraURLConnection;
import ghidra.framework.store.FileSystem;
import ghidra.program.database.ProgramBuilder; import ghidra.program.database.ProgramBuilder;
import ghidra.program.database.ProgramDB; import ghidra.program.database.ProgramDB;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
@@ -620,6 +624,60 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
assertTrue(spyNavigatable.navigatedTo(otherProgramPath, address)); assertTrue(spyNavigatable.navigatedTo(otherProgramPath, address));
} }
@Test
public void testGhidraLocalUrlAnnotation_Program_WithAddress() {
SpyNavigatable spyNavigatable = new SpyNavigatable();
SpyServiceProvider spyServiceProvider = new SpyServiceProvider();
String addresstring = "1001000";
String pathname = "/a/b/prog";
String url = "ghidra:/folder/project?" + pathname + "#" + addresstring;
String annotationText = "{@url \"" + url + "\"}";
String rawComment = "My comment - " + annotationText;
AttributedString prototype = prototype();
FieldElement element =
CommentUtils.parseTextForAnnotations(rawComment, program, prototype, 0);
String displayString = element.getText();
assertEquals("My comment - " + url, displayString);
AnnotatedTextFieldElement annotatedElement = getAnnotatedTextFieldElement(element);
click(spyNavigatable, spyServiceProvider, annotatedElement);
assertTrue(spyServiceProvider.programOpened(pathname));
// Navigation performed by ProgramManager not tested due to use of spyServiceProvider
}
@Test
public void testGhidraServerUrlAnnotation_Program_WithAddress() {
SpyNavigatable spyNavigatable = new SpyNavigatable();
SpyServiceProvider spyServiceProvider = new SpyServiceProvider();
String addresstring = "1001000";
String pathname = "/a/b/prog";
String url = "ghidra://server/repo" + pathname + "#" + addresstring;
String annotationText = "{@url \"" + url + "\"}";
String rawComment = "My comment - " + annotationText;
AttributedString prototype = prototype();
FieldElement element =
CommentUtils.parseTextForAnnotations(rawComment, program, prototype, 0);
String displayString = element.getText();
assertEquals("My comment - " + url, displayString);
AnnotatedTextFieldElement annotatedElement = getAnnotatedTextFieldElement(element);
click(spyNavigatable, spyServiceProvider, annotatedElement);
assertTrue(spyServiceProvider.programOpened(pathname));
// Navigation performed by ProgramManager not tested due to use of spyServiceProvider
}
@Test @Test
public void testUnknownAnnotation() { public void testUnknownAnnotation() {
String rawComment = "This is a symbol {@syyyybol bob} annotation"; String rawComment = "This is a symbol {@syyyybol bob} annotation";
@@ -903,13 +961,7 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
private Set<String> openedPrograms = new HashSet<>(); private Set<String> openedPrograms = new HashSet<>();
private Set<String> closedPrograms = new HashSet<>(); private Set<String> closedPrograms = new HashSet<>();
@Override private Program generateProgram(String pathname, String name) {
public Program openProgram(DomainFile domainFile, int version, int state) {
String name = domainFile.getName();
String pathname = domainFile.getPathname();
openedPrograms.add(name);
try { try {
ProgramBuilder builder = new ProgramBuilder(); ProgramBuilder builder = new ProgramBuilder();
builder.setName(pathname); builder.setName(pathname);
@@ -923,6 +975,39 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
} }
} }
@Override
public Program openProgram(URL ghidraURL, int state) {
try {
GhidraURLConnection c = new GhidraURLConnection(ghidraURL);
String folderpath = c.getFolderPath();
String name = c.getFolderItemName();
String pathname = folderpath;
if (!pathname.endsWith(FileSystem.SEPARATOR)) {
pathname += FileSystem.SEPARATOR;
}
pathname += name;
openedPrograms.add(name);
Program p = generateProgram(pathname, name);
// NOTE: URL ref navigation not performed
return p;
}
catch (MalformedURLException e) {
failWithException("Bad URL", e);
}
return null;
}
@Override
public Program openProgram(DomainFile domainFile, int version, int state) {
String name = domainFile.getName();
String pathname = domainFile.getPathname();
openedPrograms.add(name);
return generateProgram(pathname, name);
}
@Override @Override
public boolean closeProgram(Program p, boolean ignoreChanges) { public boolean closeProgram(Program p, boolean ignoreChanges) {
String name = FilenameUtils.getName(p.getName()); String name = FilenameUtils.getName(p.getName());
@@ -121,12 +121,7 @@ public class ProjectFileManagerTest extends AbstractGhidraHeadedIntegrationTest
// If there are queued actions, then we have to kick the handling thread and // If there are queued actions, then we have to kick the handling thread and
// let it finish running. // let it finish running.
try { assertTrue(eventManager.flushEvents(DEFAULT_WAIT_TIMEOUT, TimeUnit.MILLISECONDS));
assertTrue(eventManager.flushEvents(DEFAULT_WAIT_TIMEOUT, TimeUnit.MILLISECONDS));
}
catch (InterruptedException e) {
failWithException("Interrupted waiting for filesystem events", e);
}
} }
private void deleteAll(File file) { private void deleteAll(File file) {
@@ -421,15 +421,10 @@ public class DataTreeDialogTest extends AbstractGhidraHeadedIntegrationTest {
assertNotNull(dialog); assertNotNull(dialog);
} }
private DomainFileFilter createStartsWithFilter(String startsWith) { private void showFiltered(final String startsWith) {
return (df) -> df.getName().startsWith(startsWith);
}
private void showFiltered(String startsWith) {
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
dialog = new DataTreeDialog(frontEndTool.getToolFrame(), "Test Data Tree Dialog", dialog = new DataTreeDialog(frontEndTool.getToolFrame(), "Test Data Tree Dialog",
DataTreeDialog.OPEN, createStartsWithFilter(startsWith)); DataTreeDialog.OPEN, f -> f.getName().startsWith(startsWith));
dialog.showComponent(); dialog.showComponent();
}); });
waitForSwing(); waitForSwing();
@@ -79,10 +79,10 @@ public class AddViewToProjectTest extends AbstractGhidraHeadlessIntegrationTest
try { try {
URL view = GhidraURL.makeURL(DIRECTORY_NAME, PROJECT_VIEW1); URL view = GhidraURL.makeURL(DIRECTORY_NAME, PROJECT_VIEW1);
project.addProjectView(view); project.addProjectView(view, true);
// add another view that will be removed to test the remove // add another view that will be removed to test the remove
project.addProjectView(GhidraURL.makeURL(DIRECTORY_NAME, PROJECT_VIEW2)); project.addProjectView(GhidraURL.makeURL(DIRECTORY_NAME, PROJECT_VIEW2), true);
// validate the view was added to project // validate the view was added to project
ProjectLocator[] projViews = project.getProjectViews(); ProjectLocator[] projViews = project.getProjectViews();
@@ -117,7 +117,7 @@ public class CreateDomainObjectTest extends AbstractGhidraHeadedIntegrationTest
project.close(); project.close();
Project project2 = ProjectTestUtils.getProject(testDir, PROJECT_NAME2); Project project2 = ProjectTestUtils.getProject(testDir, PROJECT_NAME2);
try { try {
project2.addProjectView(GhidraURL.makeURL(testDir, PROJECT_NAME1)); project2.addProjectView(GhidraURL.makeURL(testDir, PROJECT_NAME1), true);
} }
catch (Exception e) { catch (Exception e) {
Assert.fail("View Not found"); Assert.fail("View Not found");
@@ -15,15 +15,18 @@
*/ */
package ghidra.base.project; package ghidra.base.project;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import generic.test.TestUtils; import generic.test.AbstractGTest;
import ghidra.framework.model.DomainFile; import ghidra.framework.model.DomainFile;
import ghidra.framework.remote.User; import ghidra.framework.remote.User;
import ghidra.framework.store.local.IndexedV1LocalFileSystem;
import ghidra.framework.store.local.LocalFileSystem; import ghidra.framework.store.local.LocalFileSystem;
import ghidra.test.TestEnv; import ghidra.test.TestEnv;
import utilities.util.FileUtilities;
/** /**
* This class represents the idea of a shared Ghidra repository. This class is meant to be * This class represents the idea of a shared Ghidra repository. This class is meant to be
@@ -54,11 +57,34 @@ public class FakeRepository {
private Map<String, User> usersByName = new HashMap<>(); private Map<String, User> usersByName = new HashMap<>();
private Map<User, FakeSharedProject> projectsByUser = new HashMap<>(); private Map<User, FakeSharedProject> projectsByUser = new HashMap<>();
private File versionedFSDir;
private LocalFileSystem versionedFileSystem; private LocalFileSystem versionedFileSystem;
public FakeRepository() { public FakeRepository() throws IOException {
// validation must be enabled if both environments are utilized by a test // validation must be enabled if both environments are utilized by a test
LocalFileSystem.setValidationRequired(); LocalFileSystem.setValidationRequired();
versionedFSDir =
new File(AbstractGTest.getTestDirectoryPath() + File.separator + "TestRepo.rep");
if (versionedFSDir.exists()) {
FileUtilities.deleteDir(versionedFSDir);
}
if (versionedFSDir.exists() || !FileUtilities.createDir(versionedFSDir)) {
throw new IOException("Failed to create clean repo dir: " + versionedFSDir);
}
versionedFileSystem = new MyVersionedFileSystem(versionedFSDir.getPath());
}
private static class MyVersionedFileSystem extends IndexedV1LocalFileSystem {
MyVersionedFileSystem(String rootPath) throws IOException {
super(rootPath, true, false, true, true);
}
@Override
public boolean isShared() {
// Enables use of asyncronous event dispatching thread
return true;
}
} }
/** /**
@@ -109,11 +135,6 @@ public class FakeRepository {
FakeSharedProject project = new FakeSharedProject(this, user); FakeSharedProject project = new FakeSharedProject(this, user);
projectsByUser.put(user, project); projectsByUser.put(user, project);
if (versionedFileSystem == null) {
versionedFileSystem = project.getVersionedFileSystem();
TestUtils.setInstanceField("isShared", versionedFileSystem, Boolean.TRUE);
}
return project; return project;
} }
@@ -139,6 +160,8 @@ public class FakeRepository {
*/ */
public void dispose() { public void dispose() {
projectsByUser.values().forEach(p -> disposeProject(p)); projectsByUser.values().forEach(p -> disposeProject(p));
versionedFileSystem.dispose();
FileUtilities.deleteDir(versionedFSDir);
} }
private void disposeProject(FakeSharedProject p) { private void disposeProject(FakeSharedProject p) {
@@ -25,7 +25,7 @@ import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import generic.test.AbstractGenericTest; import generic.test.AbstractGTest;
import generic.test.TestUtils; import generic.test.TestUtils;
import ghidra.framework.data.*; import ghidra.framework.data.*;
import ghidra.framework.model.*; import ghidra.framework.model.*;
@@ -39,6 +39,7 @@ import ghidra.test.TestProgramManager;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
import junit.framework.AssertionFailedError; import junit.framework.AssertionFailedError;
import utilities.util.FileUtilities;
/** /**
* This class represents the idea of a shared Ghidra project. Each project is associated with * This class represents the idea of a shared Ghidra project. Each project is associated with
@@ -61,21 +62,18 @@ public class FakeSharedProject {
public FakeSharedProject(FakeRepository repo, User user) throws IOException { public FakeSharedProject(FakeRepository repo, User user) throws IOException {
this.repo = repo; this.repo = repo;
String projectDirPath = AbstractGenericTest.getTestDirectoryPath(); String projectDirPath = AbstractGTest.getTestDirectoryPath();
gProject = gProject =
GhidraProject.createProject(projectDirPath, "TestProject_" + user.getName(), true); GhidraProject.createProject(projectDirPath, "TestProject_" + user.getName(), true);
gProject.setDeleteOnClose(true); gProject.setDeleteOnClose(true);
LocalFileSystem fs = repo.getSharedFileSystem(); // use local shared fake repo versioned file system
if (fs != null) { setVersionedFileSystem(repo.getSharedFileSystem());
// first project will keeps its versioned file system
setVersionedFileSystem(fs);
}
} }
FakeSharedProject(User user) throws IOException { FakeSharedProject(User user) throws IOException {
String projectDirPath = AbstractGenericTest.getTestDirectoryPath(); String projectDirPath = AbstractGTest.getTestDirectoryPath();
gProject = gProject =
GhidraProject.createProject(projectDirPath, "TestProject_" + user.getName(), true); GhidraProject.createProject(projectDirPath, "TestProject_" + user.getName(), true);
} }
@@ -101,7 +99,7 @@ public class FakeSharedProject {
* @return the project file manager * @return the project file manager
*/ */
public ProjectFileManager getProjectFileManager() { public ProjectFileManager getProjectFileManager() {
return (ProjectFileManager) gProject.getProject().getProjectData(); return (ProjectFileManager) gProject.getProjectData();
} }
/** /**
@@ -369,8 +367,11 @@ public class FakeSharedProject {
* @see FakeRepository#dispose() * @see FakeRepository#dispose()
*/ */
public void dispose() { public void dispose() {
ProjectLocator projectLocator = getProjectFileManager().getProjectLocator();
programManager.disposeOpenPrograms(); programManager.disposeOpenPrograms();
gProject.close(); gProject.close();
FileUtilities.deleteDir(projectLocator.getProjectDir());
projectLocator.getMarkerFile().delete();
} }
@Override @Override
@@ -400,12 +401,7 @@ public class FakeSharedProject {
(FileSystemEventManager) TestUtils.getInstanceField("eventManager", (FileSystemEventManager) TestUtils.getInstanceField("eventManager",
versionedFileSystem); versionedFileSystem);
try { eventManager.flushEvents(DEFAULT_WAIT_TIMEOUT, TimeUnit.MILLISECONDS);
eventManager.flushEvents(DEFAULT_WAIT_TIMEOUT, TimeUnit.MILLISECONDS);
}
catch (InterruptedException e) {
failWithException("Interrupted waiting for filesystem events", e);
}
} }
private DomainFolder getFolder(String path) throws Exception { private DomainFolder getFolder(String path) throws Exception {
@@ -99,6 +99,10 @@ public class CollectFailedRelocations extends GhidraScript {
if (monitor.isCancelled()) { if (monitor.isCancelled()) {
return; return;
} }
// Do not follow folder-links or consider program links. Using content type
// to filter is best way to control this. If program links should be considered
// "Program.class.isAssignableFrom(domainFile.getDomainObjectClass())"
// should be used.
if (domainFile.getContentType().equals(ProgramContentHandler.PROGRAM_CONTENT_TYPE)) { if (domainFile.getContentType().equals(ProgramContentHandler.PROGRAM_CONTENT_TYPE)) {
programs.add(domainFile); programs.add(domainFile);
} }
@@ -258,6 +258,10 @@ public class CreateMultipleLibraries extends GhidraScript {
DomainFile[] files = myFolder.getFiles(); DomainFile[] files = myFolder.getFiles();
for (DomainFile domainFile : files) { for (DomainFile domainFile : files) {
monitor.checkCanceled(); monitor.checkCanceled();
// Do not follow folder-links or consider program links. Using content type
// to filter is best way to control this. If program links should be considered
// "Program.class.isAssignableFrom(domainFile.getDomainObjectClass())"
// should be used.
if (domainFile.getContentType().equals(ProgramContentHandler.PROGRAM_CONTENT_TYPE)) { if (domainFile.getContentType().equals(ProgramContentHandler.PROGRAM_CONTENT_TYPE)) {
programs.add(domainFile); programs.add(domainFile);
} }
@@ -372,6 +372,10 @@ public class FidStatistics extends GhidraScript {
DomainFile[] files = folder.getFiles(); DomainFile[] files = folder.getFiles();
for (DomainFile domainFile : files) { for (DomainFile domainFile : files) {
monitor.checkCanceled(); monitor.checkCanceled();
// Do not follow folder-links or consider program links. Using content type
// to filter is best way to control this. If program links should be considered
// "Program.class.isAssignableFrom(domainFile.getDomainObjectClass())"
// should be used.
if (domainFile.getContentType().equals(ProgramContentHandler.PROGRAM_CONTENT_TYPE)) { if (domainFile.getContentType().equals(ProgramContentHandler.PROGRAM_CONTENT_TYPE)) {
programs.add(domainFile); programs.add(domainFile);
} }
@@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -17,6 +16,9 @@
//Opens all programs under a chosen domain folder, grabs their error count, //Opens all programs under a chosen domain folder, grabs their error count,
//then sorts in increasing error order and prints them //then sorts in increasing error order and prints them
//@category FunctionID //@category FunctionID
import java.io.IOException;
import java.util.*;
import generic.stl.Pair; import generic.stl.Pair;
import ghidra.app.script.GhidraScript; import ghidra.app.script.GhidraScript;
import ghidra.framework.model.DomainFile; import ghidra.framework.model.DomainFile;
@@ -27,9 +29,6 @@ import ghidra.util.Msg;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException; import ghidra.util.exception.VersionException;
import java.io.IOException;
import java.util.*;
public class FindErrors extends GhidraScript { public class FindErrors extends GhidraScript {
@Override @Override
@@ -82,6 +81,10 @@ public class FindErrors extends GhidraScript {
if (monitor.isCancelled()) { if (monitor.isCancelled()) {
return; return;
} }
// Do not follow folder-links or consider program links. Using content type
// to filter is best way to control this. If program links should be considered
// "Program.class.isAssignableFrom(domainFile.getDomainObjectClass())"
// should be used.
if (domainFile.getContentType().equals(ProgramContentHandler.PROGRAM_CONTENT_TYPE)) { if (domainFile.getContentType().equals(ProgramContentHandler.PROGRAM_CONTENT_TYPE)) {
programs.add(domainFile); programs.add(domainFile);
} }
@@ -100,6 +100,10 @@ public class FindFunctionByHash extends GhidraScript {
if (monitor.isCancelled()) { if (monitor.isCancelled()) {
return; return;
} }
// Do not follow folder-links or consider program links. Using content type
// to filter is best way to control this. If program links should be considered
// "Program.class.isAssignableFrom(domainFile.getDomainObjectClass())"
// should be used.
if (domainFile.getContentType().equals(ProgramContentHandler.PROGRAM_CONTENT_TYPE)) { if (domainFile.getContentType().equals(ProgramContentHandler.PROGRAM_CONTENT_TYPE)) {
programs.add(domainFile); programs.add(domainFile);
} }
@@ -16,6 +16,9 @@
//Opens all programs under a chosen domain folder, scans them for functions //Opens all programs under a chosen domain folder, scans them for functions
//that match a user supplied name, and prints info about the match. //that match a user supplied name, and prints info about the match.
//@category FunctionID //@category FunctionID
import java.io.IOException;
import java.util.ArrayList;
import ghidra.app.script.GhidraScript; import ghidra.app.script.GhidraScript;
import ghidra.feature.fid.service.FidService; import ghidra.feature.fid.service.FidService;
import ghidra.framework.model.DomainFile; import ghidra.framework.model.DomainFile;
@@ -26,9 +29,6 @@ import ghidra.util.Msg;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException; import ghidra.util.exception.VersionException;
import java.io.IOException;
import java.util.ArrayList;
public class FindNamedFunction extends GhidraScript { public class FindNamedFunction extends GhidraScript {
FidService service; FidService service;
@@ -85,6 +85,10 @@ public class FindNamedFunction extends GhidraScript {
if (monitor.isCancelled()) { if (monitor.isCancelled()) {
return; return;
} }
// Do not follow folder-links or consider program links. Using content type
// to filter is best way to control this. If program links should be considered
// "Program.class.isAssignableFrom(domainFile.getDomainObjectClass())"
// should be used.
if (domainFile.getContentType().equals(ProgramContentHandler.PROGRAM_CONTENT_TYPE)) { if (domainFile.getContentType().equals(ProgramContentHandler.PROGRAM_CONTENT_TYPE)) {
programs.add(domainFile); programs.add(domainFile);
} }

Some files were not shown because too many files have changed in this diff Show More