mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-09-28 17:11:11 -09:00
Merge remote-tracking branch
'origin/GP-6796_ghidra1_GhidraURLAllowListInterface' into Ghidra_12.2
This commit is contained in:
@@ -22,9 +22,9 @@
|
||||
hyperlink.</P>
|
||||
<!-- Annotation Example -->
|
||||
|
||||
<P>The following text shows the syntax of a sample URL annotation:</P>
|
||||
<P>The following text shows the syntax of a sample HTTP URL annotation:</P>
|
||||
<pre><font size="4"><br>
|
||||
<b>{@<i>url</i></b> "<i>http://www.google.com</i>"</b> "Search Web"<b>}</b><br>
|
||||
<b>{@<i>url</i></b> "<i>https://www.google.com</i>"</b> "Search Web"<b>}</b><br>
|
||||
</font><br></pre>
|
||||
|
||||
<P>The bold text is required for all annotations. The italicized text is required but is
|
||||
@@ -38,7 +38,7 @@
|
||||
<!-- image -->
|
||||
|
||||
<P align="center"><IMG border="0" src="images/CommentDialogURLExample.png" alt=""><BR>
|
||||
<I>URL Annotation Example</I></P>
|
||||
<I>HTTP URL Annotation Example</I></P>
|
||||
|
||||
<P>The image above shows a URL annotation in its text form as entered into the EOL Comment
|
||||
tab of the Comments dialog.<BR>
|
||||
@@ -46,11 +46,13 @@
|
||||
The image below shows how the annotation is rendered in Ghidra.</P>
|
||||
|
||||
<P align="center"><IMG border="2" src="images/RenderedURLExample.png" alt=""><BR>
|
||||
<I>Rendered URL Annotation Example</I></P>
|
||||
<I>Rendered HTTP 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., "https://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. </P>
|
||||
|
||||
<I>GHIDRA URL Annotation Example</I></P>
|
||||
|
||||
<P>If the URL text corresponds to a Ghidra URL and attempt will be made to open the referenced
|
||||
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>
|
||||
@@ -64,6 +66,14 @@
|
||||
<i>ghidra:/[<project-path>/]<project-name>?/<program-path>[#<address-or-symbol-ref>]</i><BR>
|
||||
Example: <i>ghidra:/share/MyProject?/notepad.exe#entry</i>
|
||||
</P>
|
||||
|
||||
<P><IMG border="0" src="images/warning.help.png" alt="Note"> Clicking on all remote URL annotations will be
|
||||
will be subject to the <b>Server Allow List</b> resulting in a possible confirmation dialog.
|
||||
This is independent of possible SSL/TLS server authentication which may be required.
|
||||
At anytime the <b>Server Allow List</b> may be cleared via the Project window
|
||||
<b>Edit->Clear Server Allow List...</b> or managed using the <I>support/updateServerAllowList</I> shell script.
|
||||
Execute this script without arguments to see usage information.</P>
|
||||
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2>Valid Annotations</H2>
|
||||
@@ -197,7 +207,7 @@
|
||||
|
||||
<TD valign="top" width="12%">
|
||||
<OL style="margin-left: 15px;">
|
||||
<LI>URL</LI>
|
||||
<LI>HTTP/GHIDRA URL</LI>
|
||||
|
||||
<LI>[display text]<BR>
|
||||
</LI>
|
||||
@@ -219,11 +229,11 @@
|
||||
|
||||
<TD valign="top" width="25%">
|
||||
<UL style="margin-left: 10px;">
|
||||
<LI>{@url "http://www.google.com"}</LI>
|
||||
<LI>{@url "https://www.google.com"}</LI>
|
||||
|
||||
<LI>{@url "http://www.google.com" "google"}</LI>
|
||||
<LI>{@url "https://www.google.com" "google"}</LI>
|
||||
|
||||
<LI>{@url "http://www.google.com" "click here for google"}</LI>
|
||||
<LI>{@url "https://www.google.com" "click here for google"}</LI>
|
||||
|
||||
<LI>{@url "ghidra://myserver/Repo/notepad.exe"}</LI>
|
||||
|
||||
@@ -355,6 +365,10 @@
|
||||
quotes (") around content inside of the annotation tag, excluding the <B>@<I>name</I></B>
|
||||
part of the tag. Further, some annotations require quotes, as listed in the table above
|
||||
(e.g., the <B>Execute</B> annotation requires quotes). It is considered good practice to
|
||||
quote all annotation parameter values.</P><P><IMG border="0" src="images/warning.help.png" alt="Note"> All annotations support double
|
||||
quotes (") around content inside of the annotation tag, excluding the <B>@<I>name</I></B>
|
||||
part of the tag. Further, some annotations require quotes, as listed in the table above
|
||||
(e.g., the <B>Execute</B> annotation requires quotes). It is considered good practice to
|
||||
quote all annotation parameter values.</P>
|
||||
|
||||
|
||||
|
||||
@@ -98,8 +98,6 @@
|
||||
</UL>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H3> </H3>
|
||||
|
||||
<H3><A name="Manage_Certificates"></A>Manage Certificates (Windows and macOS only) </H3>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
@@ -125,18 +123,18 @@
|
||||
When you connect to the server the next time you run Ghidra, you will be prompted for the
|
||||
key-store password associated with this certificate key file. The path to your PKI
|
||||
certificate file is saved as part of your Ghidra preferences.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P><IMG src="help/shared/note.png" border="0"> If the Ghidra Server, or other server,
|
||||
is not using PKI Certificates for user authentication, you can ignore this menu option
|
||||
since the certificate keystore will not be used.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P><IMG src="help/shared/note.png" border="0"> Specifying the single user certificate
|
||||
|
||||
<P><IMG src="help/shared/note.png" border="0"> Setting your user PKI certificate key store
|
||||
may also be required if Ghidra communicates with other web services which rely on PKI user
|
||||
authentication.</P>
|
||||
|
||||
<P><IMG src="help/shared/note.png" border="0"> Specifying the single user certificate
|
||||
keystore in this fashion will prevent the OS managed keystore from being used
|
||||
(applied to Windows and macOS only).</P>
|
||||
(applies to Windows and macOS only).</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H3><A name="Clear_PKI_Certificate"></A>Clear PKI Certificate </H3>
|
||||
@@ -149,6 +147,32 @@
|
||||
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H3><A name="Clear_Server_Allow_List"></A>Server Allow List </H3>
|
||||
<BLOCKQUOTE>
|
||||
<P>When attempting to communicate with various servers based upon a URL, you may be
|
||||
prompted to either allow or disallow the connection and all future connection attempts.
|
||||
The choice will be saved to a <I>Server Allow List</I>. A server entry is identified
|
||||
by the associated communication protocol (e.g., "https", "ghidra"), its host name
|
||||
as specified by a URL and the associated TCP port. The "ghidra" protocol is only
|
||||
identified with its base-port (e.g., 13100) and not the other two related ports.
|
||||
If you change your mind about a server connection it may be neccessary to revise this
|
||||
saved <I>Server Allow List</I>. Two options exist for altering this list:</P>
|
||||
|
||||
<UL>
|
||||
<LI>To clear the entire Server Allow List from the Ghidra GUI Project Window,
|
||||
choose <B>Edit<IMG src="help/shared/arrow.gif" border="0">Clear Server Allow List...</B>.</LI>
|
||||
|
||||
<LI>From a system command prompt, the <B>updateServerAllowList</B> command
|
||||
can be used to view and selectively modify the allow-list. This command can
|
||||
be found within the Ghidra installation <B>support</B> directory.
|
||||
Server Allow List entries may be displayed with the <I>-list</I>
|
||||
option, and added or modified using the <I>-allow or -disallow</I> options.
|
||||
Entries may also removed entirely by using the <I>-clear or -clearAll</I> option.</LI>
|
||||
|
||||
</UL>
|
||||
</BLOCKQUOTE>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2><A name="Exit_Ghidra"></A>Exiting Ghidra</H2>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
|
||||
@@ -25,6 +25,7 @@ import ghidra.*;
|
||||
import ghidra.app.util.importer.LibrarySearchPathManager;
|
||||
import ghidra.app.util.opinion.Loader;
|
||||
import ghidra.framework.*;
|
||||
import ghidra.framework.client.*;
|
||||
import ghidra.framework.model.DomainFolder;
|
||||
import ghidra.framework.protocol.ghidra.Handler;
|
||||
import ghidra.util.Msg;
|
||||
@@ -65,6 +66,7 @@ public class AnalyzeHeadless implements GhidraLaunchable {
|
||||
PASSWORD("-p", false),
|
||||
COMMIT("-commit", false, "[\"<comment>\"]]"),
|
||||
OK_TO_DELETE("-okToDelete", false),
|
||||
ALLOW_ALL_ACCESS("-allowAllAccess", false),
|
||||
MAX_CPU("-max-cpu", true, "<max cpu cores to use>"),
|
||||
LIBRARY_SEARCH_PATHS("-librarySearchPaths", true, "<path1>[;<path2>...]"),
|
||||
LOADER(Loader.COMMAND_LINE_ARG_PREFIX, true, "<desired loader name>"),
|
||||
@@ -186,6 +188,16 @@ public class AnalyzeHeadless implements GhidraLaunchable {
|
||||
HeadlessOptions options = analyzer.getOptions();
|
||||
parseOptions(options, args, optionStartIndex, ghidraURL, filesToImport);
|
||||
|
||||
// Ensure that we do not rely on prompting user for allowing server access
|
||||
if (options.allowAllAccess) {
|
||||
Msg.warn(AnalyzeHeadless.class,
|
||||
"All remote server access is Allowed (" + Arg.ALLOW_ALL_ACCESS + ")");
|
||||
ClientUtil.setAllowListProvider(new AllowAllUrlAllowListProvider());
|
||||
}
|
||||
else {
|
||||
ClientUtil.setAllowListProvider(new DefaultlUrlAllowListProvider());
|
||||
}
|
||||
|
||||
Msg.info(AnalyzeHeadless.class,
|
||||
"Headless startup complete (" + GhidraLauncher.getMillisecondsFromLaunch() + " ms)");
|
||||
ClassSearcher.logStatistics();
|
||||
@@ -199,6 +211,11 @@ public class AnalyzeHeadless implements GhidraLaunchable {
|
||||
analyzer.processLocal(args[0], projectName, rootFolderPath, filesToImport);
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.error(HeadlessAnalyzer.class,
|
||||
"Abort due to error: " + e.getMessage());
|
||||
System.exit(EXIT_CODE_ERROR);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
Msg.error(HeadlessAnalyzer.class,
|
||||
"Abort due to Headless analyzer error: " + e.getMessage(), e);
|
||||
@@ -412,6 +429,9 @@ public class AnalyzeHeadless implements GhidraLaunchable {
|
||||
else if (checkArgument(Arg.OK_TO_DELETE, args, argi)) {
|
||||
options.setOkToDelete(true);
|
||||
}
|
||||
else if (checkArgument(Arg.ALLOW_ALL_ACCESS, args, argi)) {
|
||||
options.setAllowAllAccess(true);
|
||||
}
|
||||
else if (checkArgument(Arg.LIBRARY_SEARCH_PATHS, args, argi)) {
|
||||
LibrarySearchPathManager.setLibraryPaths(args[++argi].split(";"));
|
||||
}
|
||||
@@ -584,4 +604,24 @@ public class AnalyzeHeadless implements GhidraLaunchable {
|
||||
private boolean isExistingArg(String s) {
|
||||
return Arrays.stream(Arg.values()).anyMatch(e -> e.matches(s));
|
||||
}
|
||||
|
||||
private static class AllowAllUrlAllowListProvider implements UrlAllowListProvider {
|
||||
|
||||
@Override
|
||||
public boolean isAllowed(URL url) {
|
||||
return true; // do not cache decision
|
||||
}
|
||||
}
|
||||
|
||||
private static class DefaultlUrlAllowListProvider extends AbstractUrlAllowListProvider {
|
||||
|
||||
@Override
|
||||
public boolean isAllowed(URL url) {
|
||||
Boolean allowed = accessAllowed(url);
|
||||
if (allowed != null) {
|
||||
return allowed;
|
||||
}
|
||||
return false; // do not cache decision
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,8 +36,7 @@ import ghidra.app.util.importer.ProgramLoader;
|
||||
import ghidra.app.util.opinion.*;
|
||||
import ghidra.formats.gfilesystem.*;
|
||||
import ghidra.framework.*;
|
||||
import ghidra.framework.client.ClientUtil;
|
||||
import ghidra.framework.client.RepositoryAdapter;
|
||||
import ghidra.framework.client.*;
|
||||
import ghidra.framework.data.*;
|
||||
import ghidra.framework.main.AppInfo;
|
||||
import ghidra.framework.model.*;
|
||||
@@ -259,10 +258,8 @@ public class HeadlessAnalyzer {
|
||||
throws IOException, MalformedURLException, URISyntaxException {
|
||||
|
||||
if (options.readOnly && options.commit) {
|
||||
Msg.error(this,
|
||||
"Abort due to Headless analyzer error: The requested readOnly option is in conflict " +
|
||||
throw new IllegalArgumentException("The requested readOnly option is in conflict " +
|
||||
"with the commit option");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!"ghidra".equals(ghidraURL.getProtocol())) {
|
||||
@@ -270,9 +267,8 @@ public class HeadlessAnalyzer {
|
||||
}
|
||||
|
||||
if (GhidraURL.isLocalURL(ghidraURL)) {
|
||||
Msg.error(this,
|
||||
throw new IllegalArgumentException(
|
||||
"Ghidra URL command form does not supported local project URLs (ghidra:/path...)");
|
||||
return;
|
||||
}
|
||||
|
||||
String path = ghidraURL.getPath();
|
||||
@@ -293,6 +289,22 @@ public class HeadlessAnalyzer {
|
||||
}
|
||||
}
|
||||
|
||||
if (!options.allowAllAccess) {
|
||||
// Check Server Allow List - add access if not already blocked
|
||||
Boolean hasServerAccess = UrlAllowListManager.getAccess(ghidraURL);
|
||||
if (hasServerAccess == null) {
|
||||
ServerSpecification serverSpec = ServerSpecification.get(ghidraURL);
|
||||
Msg.info(HeadlessAnalyzer.class,
|
||||
"NOTICE: Adding server to allow list: " + serverSpec.toString());
|
||||
UrlAllowListManager.updateAccess(ghidraURL, true);
|
||||
}
|
||||
else if (!hasServerAccess) {
|
||||
ServerSpecification serverSpec = ServerSpecification.get(ghidraURL);
|
||||
throw new IOException(
|
||||
"Access denied by server allow list: " + serverSpec.toString());
|
||||
}
|
||||
}
|
||||
|
||||
BundleHost bundleHost = GhidraScriptUtil.acquireBundleHostReference();
|
||||
bundleHost.add(parseScriptPaths(options.scriptPaths), true, true);
|
||||
try {
|
||||
|
||||
@@ -95,6 +95,10 @@ public class HeadlessOptions {
|
||||
// -p
|
||||
boolean allowPasswordPrompt;
|
||||
|
||||
// -allowAllAccess - Server Allow List will allow all remote server access, otherwise
|
||||
// access may be restricted based upon previously allowed server access.
|
||||
boolean allowAllAccess;
|
||||
|
||||
// -commit
|
||||
boolean commit;
|
||||
String commitComment;
|
||||
@@ -143,6 +147,7 @@ public class HeadlessOptions {
|
||||
keystore = null;
|
||||
connectUserID = null;
|
||||
allowPasswordPrompt = false;
|
||||
allowAllAccess = false;
|
||||
commit = false;
|
||||
commitComment = null;
|
||||
okToDelete = false;
|
||||
@@ -392,6 +397,17 @@ public class HeadlessOptions {
|
||||
this.analyze = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remote Ghidra Server access relies on Server Allow List established by GUI application.
|
||||
* This method can be used to allow all access and ignore Server Allow List.
|
||||
*
|
||||
* @param allowAccess True if all server access should be allowed, otherwise rely on
|
||||
* Allow List previously cached by GUI application.
|
||||
*/
|
||||
public void setAllowAllAccess(boolean allowAccess) {
|
||||
this.allowAllAccess = allowAccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the language and compiler spec from the provided input. Any null value will attempt
|
||||
* a "best-guess" if possible.
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
/* ###
|
||||
* 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.headless;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.GhidraApplicationLayout;
|
||||
import ghidra.GhidraLaunchable;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.framework.ApplicationConfiguration;
|
||||
import ghidra.framework.client.*;
|
||||
import ghidra.framework.protocol.ghidra.Handler;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* {@link UpdateServerAllowList} utility for managing the Server Allow List.
|
||||
* See {@link UrlAllowListManager}.
|
||||
*/
|
||||
public class UpdateServerAllowList implements GhidraLaunchable {
|
||||
|
||||
private static final String INVOCATION_NAME_PROPERTY = "UpdateServerAllowList.Name";
|
||||
|
||||
public UpdateServerAllowList() {
|
||||
// Required for GhidraLaunchable
|
||||
}
|
||||
|
||||
private URL parseURL(String urlString) throws MalformedURLException {
|
||||
URI uri = URI.create(urlString);
|
||||
return uri.toURL();
|
||||
}
|
||||
|
||||
private void checkMoreArgs(int currentArgIndex, String[] args) {
|
||||
if (currentArgIndex == args.length - 1) {
|
||||
usage(args);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launch(GhidraApplicationLayout layout, String[] args) throws IOException {
|
||||
Application.initializeApplication(layout, new ApplicationConfiguration());
|
||||
if (args.length == 0) {
|
||||
usage(args);
|
||||
}
|
||||
|
||||
// NOTE: May need other protocol handlers to be registered to avoid URL exceptions
|
||||
Handler.registerHandler();
|
||||
|
||||
try {
|
||||
boolean printList = false;
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
String arg = args[i];
|
||||
switch (arg) {
|
||||
|
||||
case "-allow":
|
||||
checkMoreArgs(i, args);
|
||||
URL url = parseURL(args[++i]);
|
||||
UrlAllowListManager.updateAccess(url, true);
|
||||
break;
|
||||
|
||||
case "-disallow":
|
||||
checkMoreArgs(i, args);
|
||||
url = parseURL(args[++i]);
|
||||
UrlAllowListManager.updateAccess(url, false);
|
||||
break;
|
||||
|
||||
case "-clear":
|
||||
checkMoreArgs(i, args);
|
||||
url = parseURL(args[++i]);
|
||||
UrlAllowListManager.clearAccessEntry(url);
|
||||
break;
|
||||
|
||||
case "-clearAll":
|
||||
UrlAllowListManager.clearAll();
|
||||
break;
|
||||
|
||||
case "-list":
|
||||
printList = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
usage(args);
|
||||
}
|
||||
}
|
||||
|
||||
if (printList) {
|
||||
Map<ServerSpecification, AccessRecord> accessMap =
|
||||
UrlAllowListManager.getAccessMap();
|
||||
List<ServerSpecification> servers = new ArrayList<>(accessMap.keySet());
|
||||
if (servers.isEmpty()) {
|
||||
System.out.println("Server Allow List is empty.");
|
||||
}
|
||||
else {
|
||||
Collections.sort(servers);
|
||||
System.out.println("Server Allow List:");
|
||||
for (ServerSpecification svr : servers) {
|
||||
|
||||
AccessRecord accessRecord = accessMap.get(svr);
|
||||
String access = accessRecord.accessAllowed() ? "ALLOW " : "DISALLOW";
|
||||
Date date = new Date(accessRecord.time());
|
||||
|
||||
System.out.println(
|
||||
" " + access + " " + svr.toUrlString() + " (" + date + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.error("Exception processing Allow List updates", e);
|
||||
}
|
||||
System.out.println("Done");
|
||||
}
|
||||
|
||||
private static void usage(String[] args) {
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
System.err.println("arg " + i + ": " + args[i]);
|
||||
}
|
||||
String invocationName = System.getProperty(INVOCATION_NAME_PROPERTY);
|
||||
|
||||
StringBuffer buf = new StringBuffer();
|
||||
buf.append("\nUsage: ");
|
||||
buf.append(
|
||||
invocationName != null ? invocationName : UpdateServerAllowList.class.getSimpleName());
|
||||
buf.append(
|
||||
" [-allow <protocol>://<hostname>:<port>] [-disallow <protocol>://<hostname>:<port>] [-clear <protocol>://<hostname>:<port>] [-clearAll] [-list]\n");
|
||||
System.err.println(buf.toString());
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ package ghidra.app.util.task;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import docking.widgets.OptionDialog;
|
||||
import ghidra.app.plugin.core.progmgr.ProgramLocator;
|
||||
@@ -177,16 +176,20 @@ public class ProgramOpener {
|
||||
msg += "Please contact the Ghidra team for assistance.";
|
||||
Msg.showError(this, null, "Error Opening " + filename, msg);
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (domainFile.isInWritableProject() && (e instanceof IOException)) {
|
||||
RepositoryAdapter repo = domainFile.getParent().getProjectData().getRepository();
|
||||
catch (IOException e) {
|
||||
RepositoryAdapter repo = domainFile.getParent().getProjectData().getRepository();
|
||||
if (repo != null && domainFile.isInWritableProject()) {
|
||||
ClientUtil.handleException(repo, e, "Open File", null);
|
||||
}
|
||||
else {
|
||||
Msg.showError(this, null, "Error Opening " + filename,
|
||||
"Getting domain object failed.\n" + e.getMessage(), e);
|
||||
"Getting domain object failed.\n" + e.getMessage());
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.showError(this, null, "Error Opening " + filename,
|
||||
"Getting domain object failed.\n" + e.getMessage(), e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,11 +16,14 @@
|
||||
package ghidra.app.util.viewer.field;
|
||||
|
||||
import java.net.*;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import docking.widgets.fieldpanel.field.AttributedString;
|
||||
import generic.theme.GThemeDefaults.Colors.Messages;
|
||||
import ghidra.app.nav.Navigatable;
|
||||
import ghidra.app.services.ProgramManager;
|
||||
import ghidra.framework.client.ClientUtil;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||
import ghidra.program.model.listing.Program;
|
||||
@@ -33,9 +36,24 @@ import ghidra.util.Msg;
|
||||
* The first string will be treated as a Java {@link URL} and the optional second string will
|
||||
* be treated as display text. If there is not display text, then the URL will be
|
||||
* displayed.
|
||||
* <p>
|
||||
* See {@link GhidraServerURLAnnotatedStringHandler} and {@link GhidraLocalURLAnnotatedStringHandler}
|
||||
* for GHIDRA URL dummy handlers that are used to facilitate supported Comment Editor annotation types.
|
||||
*/
|
||||
public class URLAnnotatedStringHandler implements AnnotatedStringHandler {
|
||||
|
||||
private static final Set<String> allowedProtocols = new TreeSet<>();
|
||||
static {
|
||||
// Set maintains alphabetical order or protocols
|
||||
// The 'ghidra' protocol must be included here since only one shared
|
||||
// annotation handler is used to process all supported URL protocols.
|
||||
allowedProtocols.add("ghidra");
|
||||
allowedProtocols.add("http");
|
||||
allowedProtocols.add("https");
|
||||
}
|
||||
|
||||
private static String allowedProtocolsStr = "ghidra, https or http";
|
||||
|
||||
private static final String INVALID_SYMBOL_TEXT =
|
||||
"@url annotation must have a URL string optionally followed by a display string";
|
||||
|
||||
@@ -53,7 +71,15 @@ public class URLAnnotatedStringHandler implements AnnotatedStringHandler {
|
||||
URL url = getURLForString(text[1]);
|
||||
|
||||
if (url == null) {
|
||||
return new AttributedString("Invalid URL annotations - not a URL: " + text[1],
|
||||
return new AttributedString("Invalid URL annotation - not a valid URL: " + text[1],
|
||||
Messages.ERROR, prototypeString.getFontMetrics(0), false, Messages.ERROR);
|
||||
}
|
||||
|
||||
String protocol = url.getProtocol();
|
||||
if (!allowedProtocols.contains(protocol)) {
|
||||
return new AttributedString(
|
||||
"Unsupported URL annotation protocol - " + allowedProtocolsStr + " required:\n" +
|
||||
text[1],
|
||||
Messages.ERROR, prototypeString.getFontMetrics(0), false, Messages.ERROR);
|
||||
}
|
||||
|
||||
@@ -91,28 +117,45 @@ public class URLAnnotatedStringHandler implements AnnotatedStringHandler {
|
||||
String urlString = annotationParts[1];
|
||||
URL url = getURLForString(urlString);
|
||||
if (url != null) {
|
||||
|
||||
String protocol = url.getProtocol();
|
||||
if (!allowedProtocols.contains(protocol)) {
|
||||
Msg.showError(this, null, "URL Access Not Allowed",
|
||||
"Unsupported URL annotation protocol - " + allowedProtocolsStr +
|
||||
" required:\n\n" +
|
||||
urlString);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ClientUtil.getAllowListProvider().isAllowed(url)) {
|
||||
Msg.showError(this, null, "URL Access Not Allowed",
|
||||
"Access denied by Server Allow List");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (GhidraURL.PROTOCOL.equals(url.getProtocol())) {
|
||||
ProgramManager programManager = serviceProvider.getService(ProgramManager.class);
|
||||
return programManager.openProgram(url, ProgramManager.OPEN_CURRENT) != null;
|
||||
}
|
||||
|
||||
BrowserLoader.display(url, null, serviceProvider);
|
||||
return true;
|
||||
}
|
||||
|
||||
Msg.showError(this, null, "Invalid URL",
|
||||
"Unable to create a Java URL object from string: " + urlString);
|
||||
"Invalid URL annotation - not a valid URL: " + urlString);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayString() {
|
||||
return "URL";
|
||||
return "HTTP-URL";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPrototypeString() {
|
||||
return "{@url http://www.example.com}";
|
||||
return "{@url https://www.example.com}";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -21,8 +21,7 @@ import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
import docking.options.OptionsService;
|
||||
import ghidra.framework.options.OptionsChangeListener;
|
||||
@@ -36,15 +35,6 @@ import ghidra.framework.plugintool.ServiceProvider;
|
||||
*/
|
||||
public class BrowserLoader {
|
||||
|
||||
/**
|
||||
* Display the content specified by url in a web browser window. This call will launch
|
||||
* a new thread and then immediately return.
|
||||
* @param url The URL to show.
|
||||
*/
|
||||
public static void display(URL url) {
|
||||
display(url, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the content specified by url in a web browser window. This call will launch
|
||||
* a new thread and then immediately return.
|
||||
@@ -57,6 +47,8 @@ public class BrowserLoader {
|
||||
if (url == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Objects.requireNonNull(serviceProvider, "serviceProvider instance is required");
|
||||
|
||||
// open the browser in a new thread because the call may block
|
||||
(new Thread(new BrowserRunner(url, fileURL, serviceProvider))).start();
|
||||
@@ -65,12 +57,7 @@ public class BrowserLoader {
|
||||
private static void displayFromBrowserRunner(URL url, URL fileURL,
|
||||
ServiceProvider serviceProvider) {
|
||||
try {
|
||||
if (serviceProvider == null) {
|
||||
displayBrowserForExternalURL(url);
|
||||
}
|
||||
else {
|
||||
displayBrowser(url, fileURL, serviceProvider);
|
||||
}
|
||||
displayBrowser(url, fileURL, serviceProvider);
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.showError(BrowserLoader.class, null, "Error Loading Browser",
|
||||
@@ -78,15 +65,6 @@ public class BrowserLoader {
|
||||
}
|
||||
}
|
||||
|
||||
private static void displayBrowserForExternalURL(URL url) throws Exception {
|
||||
String[] arguments =
|
||||
generateCommandArguments(url, null,
|
||||
ManualViewerCommandWrappedOption.getDefaultBrowserLoaderOptions());
|
||||
Process p = Runtime.getRuntime().exec(arguments);
|
||||
p.waitFor();
|
||||
p.exitValue(); // thought to help memory problems on some versions of windows
|
||||
}
|
||||
|
||||
private static void displayBrowser(URL url, URL fileURL, ServiceProvider serviceProvider) {
|
||||
OptionsService service = serviceProvider.getService(OptionsService.class);
|
||||
ToolOptions options =
|
||||
|
||||
@@ -135,8 +135,8 @@ public class ManualViewerCommandWrappedOption implements CustomOption {
|
||||
option.setFileFormat(DEFAULT_URL_REPLACEMENT_STRING);
|
||||
}
|
||||
else if (Platform.CURRENT_PLATFORM.getOperatingSystem() == OperatingSystem.MAC_OS_X) {
|
||||
option.setCommandString("open");
|
||||
option.setCommandArguments(new String[] {});
|
||||
option.setCommandString("anaconda-navigator");
|
||||
option.setCommandArguments(new String[] { "--url" });
|
||||
option.setFileFormat(DEFAULT_URL_REPLACEMENT_STRING);
|
||||
}
|
||||
else {
|
||||
|
||||
Reference in New Issue
Block a user