diff --git a/Ghidra/Features/PDB/developer_scripts/PdbSymbolServerExamplePrescript.java b/Ghidra/Features/PDB/developer_scripts/PdbSymbolServerExamplePrescript.java index f8b29e9d89..549d88cc9a 100644 --- a/Ghidra/Features/PDB/developer_scripts/PdbSymbolServerExamplePrescript.java +++ b/Ghidra/Features/PDB/developer_scripts/PdbSymbolServerExamplePrescript.java @@ -4,9 +4,9 @@ * 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. @@ -19,13 +19,9 @@ //The ~/symbols directory should already exist and be initialized as a symbol //storage location. //@category PDB +import java.io.File; import java.util.List; -import java.io.File; -import java.net.URI; - -import ghidra.app.plugin.core.analysis.PdbAnalyzer; -import ghidra.app.plugin.core.analysis.PdbUniversalAnalyzer; import ghidra.app.script.GhidraScript; import pdb.PdbPlugin; import pdb.symbolserver.*; @@ -38,16 +34,11 @@ public class PdbSymbolServerExamplePrescript extends GhidraScript { File symDir = new File(homeDir, "symbols"); LocalSymbolStore localSymbolStore = new LocalSymbolStore(symDir); HttpSymbolServer msSymbolServer = - new HttpSymbolServer(URI.create("https://msdl.microsoft.com/download/symbols/")); + HttpSymbolServer.createTrusted("https://msdl.microsoft.com/download/symbols/"); SymbolServerService symbolServerService = new SymbolServerService(localSymbolStore, List.of(msSymbolServer)); PdbPlugin.saveSymbolServerServiceConfig(symbolServerService); - - // You only need to enable the "allow remote" option on the specific - // analyzer you are using - PdbUniversalAnalyzer.setAllowRemoteOption(currentProgram, true); - PdbAnalyzer.setAllowRemoteOption(currentProgram, true); } } diff --git a/Ghidra/Features/PDB/ghidra_scripts/GetMSDownloadLinkScript.java b/Ghidra/Features/PDB/ghidra_scripts/GetMSDownloadLinkScript.java index 541839bbb8..cd572caedc 100644 --- a/Ghidra/Features/PDB/ghidra_scripts/GetMSDownloadLinkScript.java +++ b/Ghidra/Features/PDB/ghidra_scripts/GetMSDownloadLinkScript.java @@ -4,9 +4,9 @@ * 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. @@ -25,7 +25,6 @@ import java.io.File; import java.io.IOException; -import java.net.URI; import java.nio.file.AccessMode; import java.util.List; @@ -44,8 +43,8 @@ public class GetMSDownloadLinkScript extends GhidraScript { @Override protected void run() throws Exception { SymbolServerService symbolService = - new SymbolServerService(new SameDirSymbolStore(null), List.of( - new HttpSymbolServer(URI.create(MS_PUBLIC_SYMBOL_SERVER_URL)))); + new SymbolServerService(new SameDirSymbolStore(null), + List.of(HttpSymbolServer.createTrusted(MS_PUBLIC_SYMBOL_SERVER_URL))); File f = askFile("File To Scan", "Select"); if (f == null) { @@ -63,9 +62,7 @@ public class GetMSDownloadLinkScript extends GhidraScript { ", sizeOfImage: " + Integer.toHexString(sizeOfImage)); SymbolFileInfo symbolFileInfo = SymbolFileInfo.fromValues(f.getName().toLowerCase(), Integer.toHexString(timeDateStamp), sizeOfImage); - List findResults = - symbolService.find(symbolFileInfo, FindOption.of(FindOption.ALLOW_REMOTE), - monitor); + List findResults = symbolService.find(symbolFileInfo, monitor); if (findResults.isEmpty()) { println("Not found on " + MS_PUBLIC_SYMBOL_SERVER_URL); return; diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/plugin/core/analysis/PdbAnalyzer.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/plugin/core/analysis/PdbAnalyzer.java index 4f973139e6..70b2af10a3 100644 --- a/Ghidra/Features/PDB/src/main/java/ghidra/app/plugin/core/analysis/PdbAnalyzer.java +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/plugin/core/analysis/PdbAnalyzer.java @@ -4,9 +4,9 @@ * 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. @@ -42,7 +42,7 @@ public class PdbAnalyzer extends AbstractAnalyzer { private static final String ERROR_TITLE = "Error in PDB Analyzer"; - private boolean searchRemoteLocations = false; + private boolean searchUntrustedLocations = false; // only try once per transaction due to extensive error logging which may get duplicated private long lastTransactionId = -1; @@ -85,7 +85,7 @@ public class PdbAnalyzer extends AbstractAnalyzer { return false; } - File pdbFile = PdbAnalyzerCommon.findPdb(this, program, searchRemoteLocations, monitor); + File pdbFile = PdbAnalyzerCommon.findPdb(this, program, searchUntrustedLocations, monitor); if (pdbFile == null) { // warnings have already been logged return false; @@ -138,15 +138,15 @@ public class PdbAnalyzer extends AbstractAnalyzer { @Override public void registerOptions(Options options, Program program) { - options.registerOption(PdbAnalyzerCommon.OPTION_NAME_SEARCH_REMOTE_LOCATIONS, - searchRemoteLocations, null, - PdbAnalyzerCommon.OPTION_DESCRIPTION_SEARCH_REMOTE_LOCATIONS); + options.registerOption(PdbAnalyzerCommon.OPTION_NAME_SEARCH_UNTRUSTED_LOCATIONS, + searchUntrustedLocations, null, + PdbAnalyzerCommon.OPTION_DESCRIPTION_SEARCH_UNTRUSTED_LOCATIONS); } @Override public void optionsChanged(Options options, Program program) { - searchRemoteLocations = options.getBoolean( - PdbAnalyzerCommon.OPTION_NAME_SEARCH_REMOTE_LOCATIONS, searchRemoteLocations); + searchUntrustedLocations = options.getBoolean( + PdbAnalyzerCommon.OPTION_NAME_SEARCH_UNTRUSTED_LOCATIONS, searchUntrustedLocations); } /** @@ -165,18 +165,18 @@ public class PdbAnalyzer extends AbstractAnalyzer { } /** - * Sets the "allow remote" option that will be used by the analyzer when it is next invoked + * Sets the "allow untrusted" option that will be used by the analyzer when it is next invoked * on the specified program. *

* Normally when the analyzer attempts to locate a matching PDB file it - * will default to NOT searching remote symbol servers. A headless script could - * use this method to allow the analyzer to search remote symbol servers. + * will default to NOT searching untrusted symbol servers. A headless script could + * use this method to allow the analyzer to search untrusted symbol servers. * * @param program {@link Program} - * @param allowRemote boolean flag, true means analyzer can search remote symbol + * @param allowUntrusted boolean flag, true means analyzer can search untrusted symbol * servers */ - public static void setAllowRemoteOption(Program program, boolean allowRemote) { - PdbAnalyzerCommon.setAllowRemoteOption(NAME, program, allowRemote); + public static void setAllowUntrustedOption(Program program, boolean allowUntrusted) { + PdbAnalyzerCommon.setAllowUntrustedOption(NAME, program, allowUntrusted); } } diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/plugin/core/analysis/PdbAnalyzerCommon.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/plugin/core/analysis/PdbAnalyzerCommon.java index 7c4ead5837..c398701ce3 100644 --- a/Ghidra/Features/PDB/src/main/java/ghidra/app/plugin/core/analysis/PdbAnalyzerCommon.java +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/plugin/core/analysis/PdbAnalyzerCommon.java @@ -4,9 +4,9 @@ * 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. @@ -33,9 +33,9 @@ import pdb.symbolserver.SymbolFileInfo; * Shared configuration values and pdb searching logic */ public class PdbAnalyzerCommon { - static final String OPTION_DESCRIPTION_SEARCH_REMOTE_LOCATIONS = - "If checked, allow searching remote symbol servers for PDB files."; - static final String OPTION_NAME_SEARCH_REMOTE_LOCATIONS = "Search remote symbol servers"; + static final String OPTION_DESCRIPTION_SEARCH_UNTRUSTED_LOCATIONS = + "If checked, allow searching untrusted symbol servers for PDB files."; + static final String OPTION_NAME_SEARCH_UNTRUSTED_LOCATIONS = "Search untrusted symbol servers"; static final String OPTION_DESCRIPTION_PDB_FILE = "Path to a manually chosen PDB file."; static final String OPTION_NAME_PDB_FILE = "PDB File"; @@ -101,12 +101,13 @@ public class PdbAnalyzerCommon { * * @param analyzerName name of analyzer * @param program {@link Program} - * @param allowRemote boolean flag, true means the analyzer can search remote + * @param allowUntrusted boolean flag, true means the analyzer can search remote * symbol servers */ - static void setAllowRemoteOption(String analyzerName, Program program, boolean allowRemote) { + static void setAllowUntrustedOption(String analyzerName, Program program, boolean allowUntrusted) { Options options = program.getOptions(Program.ANALYSIS_PROPERTIES); - options.setBoolean(analyzerName + "." + OPTION_NAME_SEARCH_REMOTE_LOCATIONS, allowRemote); + options.setBoolean(analyzerName + "." + OPTION_NAME_SEARCH_UNTRUSTED_LOCATIONS, + allowUntrusted); } /** @@ -141,7 +142,7 @@ public class PdbAnalyzerCommon { : null; if (pdbFile == null) { Set findOpts = allowRemote - ? FindOption.of(FindOption.ALLOW_REMOTE) + ? FindOption.of(FindOption.ALLOW_UNTRUSTED) : FindOption.NO_OPTIONS; pdbFile = PdbPlugin.findPdb(program, findOpts, monitor); } diff --git a/Ghidra/Features/PDB/src/main/java/ghidra/app/plugin/core/analysis/PdbUniversalAnalyzer.java b/Ghidra/Features/PDB/src/main/java/ghidra/app/plugin/core/analysis/PdbUniversalAnalyzer.java index 3b7e08c8ae..ba8c6b8b75 100644 --- a/Ghidra/Features/PDB/src/main/java/ghidra/app/plugin/core/analysis/PdbUniversalAnalyzer.java +++ b/Ghidra/Features/PDB/src/main/java/ghidra/app/plugin/core/analysis/PdbUniversalAnalyzer.java @@ -4,9 +4,9 @@ * 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. @@ -83,7 +83,7 @@ public class PdbUniversalAnalyzer extends AbstractAnalyzer { private File DEFAULT_FORCE_LOAD_FILE = new File(DEFAULT_SYMBOLS_DIR, "sample.pdb"); private File forceLoadFile; - private boolean searchRemoteLocations = false; + private boolean searchUntrustedLocations = false; //============================================================================================== // Additional instance data @@ -164,7 +164,7 @@ public class PdbUniversalAnalyzer extends AbstractAnalyzer { pdbFile = forceLoadFile; } else { - pdbFile = PdbAnalyzerCommon.findPdb(this, program, searchRemoteLocations, monitor); + pdbFile = PdbAnalyzerCommon.findPdb(this, program, searchUntrustedLocations, monitor); } if (pdbFile == null) { // warnings have already been logged @@ -262,9 +262,9 @@ public class PdbUniversalAnalyzer extends AbstractAnalyzer { options.registerOption(OPTION_NAME_FORCELOAD_FILE, OptionType.FILE_TYPE, DEFAULT_FORCE_LOAD_FILE, null, OPTION_DESCRIPTION_FORCELOAD_FILE); } - options.registerOption(PdbAnalyzerCommon.OPTION_NAME_SEARCH_REMOTE_LOCATIONS, - searchRemoteLocations, null, - PdbAnalyzerCommon.OPTION_DESCRIPTION_SEARCH_REMOTE_LOCATIONS); + options.registerOption(PdbAnalyzerCommon.OPTION_NAME_SEARCH_UNTRUSTED_LOCATIONS, + searchUntrustedLocations, null, + PdbAnalyzerCommon.OPTION_DESCRIPTION_SEARCH_UNTRUSTED_LOCATIONS); pdbReaderOptions.registerOptions(options); pdbApplicatorOptions.registerAnalyzerOptions(options); @@ -281,8 +281,8 @@ public class PdbUniversalAnalyzer extends AbstractAnalyzer { forceLoadFile = options.getFile(OPTION_NAME_FORCELOAD_FILE, forceLoadFile); } - searchRemoteLocations = options.getBoolean( - PdbAnalyzerCommon.OPTION_NAME_SEARCH_REMOTE_LOCATIONS, searchRemoteLocations); + searchUntrustedLocations = options.getBoolean( + PdbAnalyzerCommon.OPTION_NAME_SEARCH_UNTRUSTED_LOCATIONS, searchUntrustedLocations); pdbReaderOptions.loadOptions(options); pdbApplicatorOptions.loadAnalyzerOptions(options); @@ -312,19 +312,19 @@ public class PdbUniversalAnalyzer extends AbstractAnalyzer { } /** - * Sets the "allow remote" option that will be used by the analyzer when it is next invoked + * Sets the "allow untrusted" option that will be used by the analyzer when it is next invoked * on the specified program. *

* Normally when the analyzer attempts to locate a matching PDB file it - * will default to NOT searching remote symbol servers. A headless script could - * use this method to allow the analyzer to search remote symbol servers. + * will default to NOT searching untrusted symbol servers. A headless script could + * use this method to allow the analyzer to search untrusted symbol servers. * * @param program {@link Program} - * @param allowRemote boolean flag, true means analyzer can search remote symbol + * @param allowUntrusted boolean flag, true means analyzer can search remote symbol * servers */ - public static void setAllowRemoteOption(Program program, boolean allowRemote) { - PdbAnalyzerCommon.setAllowRemoteOption(NAME, program, allowRemote); + public static void setAllowUntrustedOption(Program program, boolean allowUntrusted) { + PdbAnalyzerCommon.setAllowUntrustedOption(NAME, program, allowUntrusted); } //============================================================================================== diff --git a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ContainerFileSymbolServer.java b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ContainerFileSymbolServer.java index 38751ce3f6..a8ce4c2cdc 100644 --- a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ContainerFileSymbolServer.java +++ b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ContainerFileSymbolServer.java @@ -4,9 +4,9 @@ * 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. @@ -136,11 +136,6 @@ public class ContainerFileSymbolServer implements SymbolServer { return fsFSRL.withPath(filename).toPrettyFullpathString(); } - @Override - public boolean isLocal() { - return true; - } - @Override public String toString() { return "ContainerFileSymbolServer: [ fsrl: %s ]".formatted(fsFSRL); diff --git a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/DisabledSymbolServer.java b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/DisabledSymbolServer.java index 26514b5f6b..5ff95bfa7f 100644 --- a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/DisabledSymbolServer.java +++ b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/DisabledSymbolServer.java @@ -4,9 +4,9 @@ * 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. @@ -114,11 +114,6 @@ public class DisabledSymbolServer implements SymbolServer { return delegate.getFileLocation(filename); } - @Override - public boolean isLocal() { - return delegate.isLocal(); - } - @Override public String toString() { return String.format("DisabledSymbolServer: [ %s ]", delegate.toString()); diff --git a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/FindOption.java b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/FindOption.java index 55c7dbbf05..4548252eb0 100644 --- a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/FindOption.java +++ b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/FindOption.java @@ -4,9 +4,9 @@ * 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. @@ -15,18 +15,16 @@ */ package pdb.symbolserver; -import java.util.EnumSet; -import java.util.List; -import java.util.Set; +import java.util.*; /** * Options that control how Pdb files are searched for on a SymbolServer. */ public enum FindOption { /** - * Allow connections to remote symbol servers + * Allow connections to untrusted symbol servers */ - ALLOW_REMOTE, + ALLOW_UNTRUSTED, /** * Only return the first result */ diff --git a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/HttpSymbolServer.java b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/HttpSymbolServer.java index 1dfec9e067..c8a1ae36bb 100644 --- a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/HttpSymbolServer.java +++ b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/HttpSymbolServer.java @@ -4,9 +4,9 @@ * 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. @@ -25,23 +25,31 @@ import java.net.http.HttpResponse; import java.net.http.HttpResponse.BodyHandlers; import java.time.Duration; import java.util.concurrent.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import ghidra.net.HttpClients; import ghidra.util.Msg; import ghidra.util.exception.CancelledException; import ghidra.util.task.CancelledListener; import ghidra.util.task.TaskMonitor; +import pdb.symbolserver.SymbolServer.MutableTrust; /** * A {@link SymbolServer} that is accessed via HTTP. *

* */ -public class HttpSymbolServer extends AbstractSymbolServer { +public class HttpSymbolServer extends AbstractSymbolServer implements MutableTrust { private static final String GHIDRA_USER_AGENT = "Ghidra_HttpSymbolServer_client"; private static final int HTTP_STATUS_OK = HttpURLConnection.HTTP_OK; private static final int HTTP_REQUEST_TIMEOUT_MS = 10 * 1000; // 10 seconds + /** + * pattern to match an optional "!" in front of a typical url string + */ + private static final Pattern NAMEPAT = Pattern.compile("(\\!?)(http(s?)://.*)"); + /** * Predicate that tests if the location string is an instance of a HttpSymbolServer location. * @@ -49,10 +57,48 @@ public class HttpSymbolServer extends AbstractSymbolServer { * @return boolean true if the string should be handled by the HttpSymbolServer class */ public static boolean isHttpSymbolServerLocation(String locationString) { - return locationString.startsWith("http://") || locationString.startsWith("https://"); + return NAMEPAT.matcher(locationString).matches(); + } + + /** + * Creates a new HttpSymbolServer instance from a locationString. + * + * @param locationString string previously returned by {@link #getName()} + * @param context {@link SymbolServerInstanceCreatorContext} + * @return new instance + */ + public static SymbolServer createInstance(String locationString, + SymbolServerInstanceCreatorContext context) { + Matcher m = NAMEPAT.matcher(locationString); + if (!m.matches()) { + return null; + } + boolean isTrusted = "!".equals(m.group(1)); + String url = m.group(2); + return new HttpSymbolServer(URI.create(url), isTrusted); + } + + /** + * Create a trusted http symbol server + * + * @param url string url + * @return new {@link HttpSymbolServer} instance + */ + public static HttpSymbolServer createTrusted(String url) { + return new HttpSymbolServer(URI.create(url), true); + } + + /** + * Create an untrusted http symbol server + * @param url string url + * @return new {@link HttpSymbolServer} instance + */ + public static HttpSymbolServer createUntrusted(String url) { + return new HttpSymbolServer(URI.create(url), false); } private final URI serverURI; + private boolean trusted; /** * Creates a new instance of a HttpSymbolServer. @@ -60,13 +106,29 @@ public class HttpSymbolServer extends AbstractSymbolServer { * @param serverURI URI / URL of the symbol server */ public HttpSymbolServer(URI serverURI) { + this(serverURI, false); + } + + /** + * Creates a new instance of a HttpSymbolServer. + * + * @param serverURI URI / URL of the symbol server + * @param isTrusted flag, if true the the http server can be trusted when querying and downloading + */ + public HttpSymbolServer(URI serverURI, boolean isTrusted) { String path = serverURI.getPath(); this.serverURI = path.endsWith("/") ? serverURI : serverURI.resolve(serverURI.getPath() + "/"); + this.trusted = isTrusted; } @Override public String getName() { + return (trusted ? "!" : "") + serverURI.toString(); + } + + @Override + public String getDescriptiveName() { return serverURI.toString(); } @@ -170,14 +232,19 @@ public class HttpSymbolServer extends AbstractSymbolServer { } @Override - public boolean isLocal() { - return false; + public boolean isTrusted() { + return trusted; + } + + @Override + public void setTrusted(boolean isTrusted) { + this.trusted = isTrusted; } @Override public String toString() { - return String.format("HttpSymbolServer: [ url: %s, storageLevel: %d]", serverURI.toString(), - storageLevel); + return String.format("HttpSymbolServer: [ url: %s, trusted: %b, storageLevel: %d]", + serverURI.toString(), trusted, storageLevel); } private String logPrefix() { diff --git a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/LocalSymbolStore.java b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/LocalSymbolStore.java index 525013fcb8..1e43e51910 100644 --- a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/LocalSymbolStore.java +++ b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/LocalSymbolStore.java @@ -4,9 +4,9 @@ * 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. @@ -371,11 +371,6 @@ public class LocalSymbolStore extends AbstractSymbolServer implements SymbolStor return new SymbolServerInputStream(new FileInputStream(file), file.length()); } - @Override - public boolean isLocal() { - return true; - } - @Override public String toString() { return String.format("LocalSymbolStore: [ rootDir: %s, storageLevel: %d]", diff --git a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/SameDirSymbolStore.java b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/SameDirSymbolStore.java index 8567c87920..b3d8568733 100644 --- a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/SameDirSymbolStore.java +++ b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/SameDirSymbolStore.java @@ -4,9 +4,9 @@ * 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. @@ -20,6 +20,7 @@ import java.util.*; import ghidra.formats.gfilesystem.FSRL; import ghidra.util.task.TaskMonitor; +import pdb.symbolserver.SymbolServer.StatusRequiresContext; /** * A Pdb symbol server / symbol store, similar to the {@link LocalSymbolStore}, @@ -27,7 +28,7 @@ import ghidra.util.task.TaskMonitor; *

* */ -public class SameDirSymbolStore implements SymbolStore { +public class SameDirSymbolStore implements SymbolStore, StatusRequiresContext { /** * Descriptive string @@ -166,11 +167,6 @@ public class SameDirSymbolStore implements SymbolStore { return getFile(filename).getPath(); } - @Override - public boolean isLocal() { - return true; - } - @Override public String toString() { return String.format("SameDirSymbolStore: [ dir: %s ]", rootDir); diff --git a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/SymbolServer.java b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/SymbolServer.java index 48c6ba4ecf..ec988ba59e 100644 --- a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/SymbolServer.java +++ b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/SymbolServer.java @@ -4,9 +4,9 @@ * 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. @@ -17,8 +17,7 @@ package pdb.symbolserver; import java.io.IOException; import java.io.InputStream; -import java.util.List; -import java.util.Set; +import java.util.*; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; @@ -29,6 +28,27 @@ import ghidra.util.task.TaskMonitor; * */ public interface SymbolServer { + /** + * Optional add-on interface for {@link SymbolServer}s that flag server types as requiring a + * valid context object to be queried for {@link SymbolServer#isValid(TaskMonitor)} + */ + interface StatusRequiresContext { + // empty + } + + /** + * Optional add-on interface for {@link SymbolServer}s that allow their trusted-ness value to + * be modified. + */ + public interface MutableTrust { + + /** + * Sets the trusted attribute of this symbol server. + * + * @param isTrusted boolean flag, if true this symbolserver will be marked as trusted + */ + void setTrusted(boolean isTrusted); + } /** * Name of the symbol server, suitable to use as the identity of this instance, @@ -55,6 +75,16 @@ public interface SymbolServer { */ boolean isValid(TaskMonitor monitor); + /** + * Returns true if this {@link SymbolServer} is 'trusted', meaning + * it can be searched without security issues / warning the user. + * + * @return boolean true if this symbolserver is trusted, false if untrusted + */ + default boolean isTrusted() { + return true; + } + /** * Returns true if the raw filename exists in the symbol server. * @@ -103,10 +133,13 @@ public interface SymbolServer { String getFileLocation(String filename); /** - * Returns true if this {@link SymbolServer} is 'local', meaning - * it can be searched without security issues / warning the user. + * Returns the number of configured symbol servers that are considered 'untrusted'. * - * @return boolean true if this symbolserver is 'local', false if remote + * @param symbolServers list of {@link SymbolServer}s + * @return number of untrusted symbol servers */ - boolean isLocal(); + static int getUntrustedCount(Collection symbolServers) { + return (int) symbolServers.stream().filter(ss -> !ss.isTrusted()).count(); + } + } diff --git a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/SymbolServerInstanceCreatorRegistry.java b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/SymbolServerInstanceCreatorRegistry.java index b3b4417833..5d36d05e11 100644 --- a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/SymbolServerInstanceCreatorRegistry.java +++ b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/SymbolServerInstanceCreatorRegistry.java @@ -4,9 +4,9 @@ * 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. @@ -16,7 +16,6 @@ package pdb.symbolserver; import java.io.File; -import java.net.URI; import java.util.*; import java.util.function.Predicate; @@ -167,7 +166,7 @@ public class SymbolServerInstanceCreatorRegistry { registerSymbolServerInstanceCreator(0, DisabledSymbolServer::isDisabledSymbolServerLocation, DisabledSymbolServer::createInstance); registerSymbolServerInstanceCreator(100, HttpSymbolServer::isHttpSymbolServerLocation, - (loc, context) -> new HttpSymbolServer(URI.create(loc))); + HttpSymbolServer::createInstance); registerSymbolServerInstanceCreator(200, SameDirSymbolStore::isSameDirLocation, SameDirSymbolStore::createInstance); registerSymbolServerInstanceCreator(300, LocalSymbolStore::isLocalSymbolStoreLocation, diff --git a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/SymbolServerService.java b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/SymbolServerService.java index b6c71b7a77..c5c8fa7d3b 100644 --- a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/SymbolServerService.java +++ b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/SymbolServerService.java @@ -4,9 +4,9 @@ * 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. @@ -15,11 +15,10 @@ */ package pdb.symbolserver; -import java.util.*; -import java.util.stream.Collectors; - import java.io.File; import java.io.IOException; +import java.util.*; +import java.util.stream.Collectors; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; @@ -31,8 +30,7 @@ import pdb.PdbUtils; /** * A (lowercase-'S') service that searches for and fetches symbol files - * from a set of local and remote {@link SymbolServer symbolservers}. (not to be - * confused with a Plugin service) + * from a set of {@link SymbolServer symbolservers}. (not to be confused with a Plugin service) *

* Instances of this class are meant to be easily created when needed * and just as easily thrown away when not used or when the search @@ -50,9 +48,8 @@ public class SymbolServerService { /** * Creates a new SymbolServerService instance. *

- * @param symbolStore a {@link SymbolStore} - where all - * remote files are placed when downloaded. Also treated as a SymbolServer - * and searched first + * @param symbolStore a {@link SymbolStore} - where all remote files are placed when + * downloaded. Also treated as a SymbolServer and searched first * @param symbolServers a list of {@link SymbolServer symbol servers} - searched in order */ public SymbolServerService(SymbolStore symbolStore, List symbolServers) { @@ -91,19 +88,6 @@ public class SymbolServerService { return new ArrayList<>(symbolServers.subList(1, symbolServers.size())); } - /** - * Returns the number of configured symbol servers that are considered 'remote'. - * @return number of remote symbol servers - */ - public int getRemoteSymbolServerCount() { - int remoteSymbolServerCount = (int) getSymbolServers() - .stream() - .filter(ss -> !ss.isLocal()) - .count(); - - return remoteSymbolServerCount; - } - /** * Searches all {@link SymbolServer symbol servers} for a matching pdb symbol file. * @@ -150,9 +134,9 @@ public class SymbolServerService { for_each_symbol_server_loop: for (SymbolServer symbolServer : symbolServers) { monitor.checkCancelled(); - if (!symbolServer.isLocal() && !findOptions.contains(FindOption.ALLOW_REMOTE)) { + if (!symbolServer.isTrusted() && !findOptions.contains(FindOption.ALLOW_UNTRUSTED)) { Msg.debug(this, - logPrefix() + ": skipping non-local symbol server " + + logPrefix() + ": skipping untrusted symbol server " + symbolServer.getDescriptiveName()); continue; } diff --git a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/ConfigPdbDialog.java b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/ConfigPdbDialog.java index ce2d6f46d4..c6fb0b10c0 100644 --- a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/ConfigPdbDialog.java +++ b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/ConfigPdbDialog.java @@ -4,9 +4,9 @@ * 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. @@ -15,11 +15,37 @@ */ package pdb.symbolserver.ui; -import java.util.List; +import static pdb.symbolserver.ui.SymbolServerRow.LocationStatus.*; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.io.File; +import java.io.IOException; +import java.util.*; + +import javax.swing.*; import docking.DialogComponentProvider; import docking.DockingWindowManager; +import docking.widgets.OptionDialog; +import docking.widgets.button.BrowseButton; +import docking.widgets.button.GButton; +import docking.widgets.filechooser.GhidraFileChooser; +import docking.widgets.filechooser.GhidraFileChooserMode; +import docking.widgets.label.GHtmlLabel; +import docking.widgets.label.GLabel; +import docking.widgets.table.GTable; +import docking.widgets.textfield.HintTextField; +import generic.theme.GThemeDefaults.Colors.Messages; +import ghidra.framework.preferences.Preferences; +import ghidra.util.*; +import ghidra.util.exception.CancelledException; +import ghidra.util.layout.PairLayout; +import ghidra.util.task.*; +import pdb.PdbPlugin; import pdb.symbolserver.*; +import resources.Icons; +import utilities.util.FileUtilities; /** * Dialog that allows the user to configure the Pdb search locations and symbol directory @@ -32,13 +58,40 @@ public class ConfigPdbDialog extends DialogComponentProvider { return choosePdbDialog.wasSuccess; } + private static final String MS_SYMBOLSERVER_ENVVAR = "_NT_SYMBOL_PATH"; + + private static final Dimension BUTTON_SIZE = new Dimension(32, 32); + + private List knownSymbolServers = + WellKnownSymbolServerLocation.loadAll(); + + private SymbolStore localSymbolStore; + private SymbolServerInstanceCreatorContext symbolServerInstanceCreatorContext = + SymbolServerInstanceCreatorRegistry.getInstance().getContext(); + private SymbolServerTableModel tableModel; + private SymbolServerPanel symbolServerConfigPanel; private boolean wasSuccess; + private boolean configChanged; public ConfigPdbDialog() { - super("Configure Symbol Server Search", true, false, true, false); + super("Configure Symbol Server Search", true, false, true, true); build(); + + tableModel.addTableModelListener(e -> updateButtonEnablement()); + setupInitialSymbolServer(); + } + + private void setupInitialSymbolServer() { + SymbolServerService temporarySymbolServerService = + PdbPlugin.getSymbolServerService(symbolServerInstanceCreatorContext); + if (temporarySymbolServerService + .getSymbolStore() instanceof LocalSymbolStore tempLocalSymbolStore) { + setSymbolStorageLocation(tempLocalSymbolStore.getRootDir(), false); + tableModel.addSymbolServers(temporarySymbolServerService.getSymbolServers()); + setConfigChanged(false); + } } @Override @@ -48,27 +101,33 @@ public class ConfigPdbDialog extends DialogComponentProvider { @Override protected void okCallback() { - if (symbolServerConfigPanel.isConfigChanged()) { - symbolServerConfigPanel.saveConfig(); + if (isConfigChanged()) { + saveConfig(); } wasSuccess = true; close(); } + @Override + protected void dialogShown() { + TableColumnInitializer.initializeTableColumns(symbolServerConfigPanel.table, tableModel); + symbolServerConfigPanel.refreshSymbolServerLocationStatus(true /* only query trusted */); + } + private void build() { - symbolServerConfigPanel = new SymbolServerPanel(this::onSymbolServerServiceChange, - SymbolServerInstanceCreatorRegistry.getInstance().getContext()); + tableModel = new SymbolServerTableModel(); + + symbolServerConfigPanel = new SymbolServerPanel(); addButtons(); addWorkPanel(symbolServerConfigPanel); setRememberSize(false); - okButton.setEnabled(symbolServerConfigPanel.getSymbolServerService() != null); - setMinimumSize(400, 250); + okButton.setEnabled(hasSymbolServer()); } - private void onSymbolServerServiceChange(SymbolServerService newService) { - okButton.setEnabled(newService != null); - rootPanel.revalidate(); + private void updateButtonEnablement() { + okButton.setEnabled(hasSymbolServer()); + symbolServerConfigPanel.updatePanelButtonEnablement(); } private void addButtons() { @@ -81,7 +140,16 @@ public class ConfigPdbDialog extends DialogComponentProvider { * Screen shot usage only */ public void pushAddLocationButton() { - symbolServerConfigPanel.pushAddLocationButton(); + symbolServerConfigPanel.addLocation(); + } + + /** + * Screen shot usage only + * + * @param list fake well known symbol servers + */ + public void setWellknownSymbolServers(List list) { + knownSymbolServers = list; } /** @@ -90,9 +158,520 @@ public class ConfigPdbDialog extends DialogComponentProvider { * @param fakeDirectoryText fake text to display in the storage directory text field * @param symbolServers list of symbol servers to force set */ - public void setSymbolServerService(String fakeDirectoryText, - List symbolServers) { - symbolServerConfigPanel.setSymbolServers(symbolServers); - symbolServerConfigPanel.setSymbolStorageDirectoryTextOnly(fakeDirectoryText); + public void setSymbolServerService(String fakeDirectoryText, List symbolServers) { + setSymbolServers(symbolServers); + setSymbolStorageLocationPath(fakeDirectoryText); } + + private void setSymbolStorageLocationPath(String path) { + symbolServerConfigPanel.symbolStorageLocationTextField.setText(path); + } + + /** + * Returns a new {@link SymbolServerService} instance representing the currently + * displayed configuration, or null if the displayed configuration is not valid. + * + * @return new {@link SymbolServerService} or null + */ + SymbolServerService getSymbolServerService() { + return (localSymbolStore != null) + ? new SymbolServerService(localSymbolStore, tableModel.getSymbolServers()) + : null; + } + + boolean hasSymbolServer() { + return localSymbolStore != null; + } + + void setSymbolServers(List symbolServers) { + tableModel.setSymbolServers(symbolServers); + } + + private void setSymbolStorageLocation(File symbolStorageDir, boolean allowGUIPrompt) { + if (symbolStorageDir == null) { + return; + } + if (!symbolStorageDir.exists()) { + if (!allowGUIPrompt) { + return; + } + + int opt = + OptionDialog.showOptionDialog(rootPanel, "Create Local Symbol Storage Directory?", + "Symbol storage directory
" + + HTMLUtilities.escapeHTML(symbolStorageDir.getPath()) + + "
does not exist. Create?", + "Yes", OptionDialog.QUESTION_MESSAGE); + if (opt == OptionDialog.CANCEL_OPTION) { + return; + } + try { + FileUtilities.checkedMkdirs(symbolStorageDir); + } + catch (IOException e) { + Msg.showError(this, rootPanel, "Failure", + "Failed to create symbol storage directory %s: %s".formatted(symbolStorageDir, + e.getMessage())); + return; + } + } + + if (allowGUIPrompt && isEmptyDirectory(symbolStorageDir)) { + if (OptionDialog.showYesNoDialog(rootPanel, "Initialize Symbol Storage Directory?", + "Initialize new directory as Microsoft symbol storage directory?
" + + "(Answer No to leave as unorganized storage directory)") == OptionDialog.YES_OPTION) { + try { + LocalSymbolStore.create(symbolStorageDir, + 1 /* level1 MS symbol storage directory */); + } + catch (IOException e) { + Msg.showError(this, rootPanel, "Initialize Failure", + "Failed to initialize symbol storage directory " + symbolStorageDir, e); + } + } + } + + localSymbolStore = + symbolServerInstanceCreatorContext.getSymbolServerInstanceCreatorRegistry() + .newSymbolServer(symbolStorageDir.getPath(), symbolServerInstanceCreatorContext, + SymbolStore.class); + setSymbolStorageLocationPath(symbolStorageDir.getPath()); + updateButtonEnablement(); + } + + void executeMonitoredRunnable(String taskTitle, boolean canCancel, boolean hasProgress, + int delay, MonitoredRunnable runnable) { + Task task = new Task(taskTitle, canCancel, hasProgress, false) { + @Override + public void run(TaskMonitor monitor) throws CancelledException { + runnable.monitoredRun(monitor); + } + }; + executeProgressTask(task, delay); + } + + /** + * The union of the changed status of the local storage path and the additional + * search paths table model changed status. + * + * @return boolean true if the config has changed + */ + boolean isConfigChanged() { + return configChanged || tableModel.isDataChanged(); + } + + void setConfigChanged(boolean configChanged) { + this.configChanged = configChanged; + tableModel.setDataChanged(configChanged); + } + + /* package */ void saveConfig() { + SymbolServerService temporarySymbolServerService = getSymbolServerService(); + if (temporarySymbolServerService != null) { + PdbPlugin.saveSymbolServerServiceConfig(temporarySymbolServerService); + Preferences.store(); + setConfigChanged(false); + updateButtonEnablement(); + } + } + + //--------------------------------------------------------------------------------------------- + + class SymbolServerPanel extends JPanel { + + private GTable table; + private JPanel additionalSearchLocationsPanel; + private JPanel defaultConfigNotice; + + private JButton refreshSearchLocationsStatusButton; + private JButton moveLocationUpButton; + private JButton moveLocationDownButton; + private JButton deleteLocationButton; + private JButton addLocationButton; + private JPanel symbolStorageLocationPanel; + private HintTextField symbolStorageLocationTextField; + private JButton chooseSymbolStorageLocationButton; + private JButton saveSearchLocationsButton; + + SymbolServerPanel() { + build(); + + DockingWindowManager.getHelpService() + .registerHelp(this, + new HelpLocation(PdbPlugin.PDB_PLUGIN_HELP_TOPIC, "Symbol Server Config")); + } + + private void build() { + setLayout(new BorderLayout()); + setBorder(BorderFactory.createTitledBorder("Symbol Server Search Config")); + + buildSymbolStorageLocationPanel(); + JPanel tableButtonPanel = buildButtonPanel(); + JScrollPane tableScrollPane = buildTable(); + defaultConfigNotice = new JPanel(); + GHtmlLabel label = new GHtmlLabel("


Missing / invalid configuration.

" + + "Using default search location:
Program's Import Location
"); + label.setHorizontalAlignment(SwingConstants.CENTER); + defaultConfigNotice.add(label); + defaultConfigNotice.setPreferredSize(tableScrollPane.getPreferredSize()); + + additionalSearchLocationsPanel = new JPanel(); + additionalSearchLocationsPanel + .setLayout(new BoxLayout(additionalSearchLocationsPanel, BoxLayout.Y_AXIS)); + additionalSearchLocationsPanel.add(tableButtonPanel); + additionalSearchLocationsPanel.add(tableScrollPane); + + add(symbolStorageLocationPanel, BorderLayout.NORTH); + add(additionalSearchLocationsPanel, BorderLayout.CENTER); + } + + private void updateLayout(boolean showTable) { + if (showTable == (additionalSearchLocationsPanel.getParent() != null)) { + return; + } + + remove(additionalSearchLocationsPanel); + remove(defaultConfigNotice); + add(showTable ? additionalSearchLocationsPanel : defaultConfigNotice, + BorderLayout.CENTER); + invalidate(); + repaint(); + } + + void refreshSymbolServerLocationStatus(boolean trustedOnly) { + executeMonitoredRunnable("Refresh Symbol Server Location Status", true, true, 0, + monitor -> { + List rowsCopy = new ArrayList<>(tableModel.getModelData()); + monitor.initialize(rowsCopy.size(), "Refreshing symbol server status"); + try { + for (SymbolServerRow row : rowsCopy) { + if (monitor.isCancelled()) { + break; + } + monitor.setMessage("Checking " + row.getSymbolServer().getName()); + monitor.incrementProgress(); + + SymbolServer symbolServer = row.getSymbolServer(); + if (symbolServer instanceof SymbolServer.StatusRequiresContext || // we don't have program context here in the config dialog + (trustedOnly && !symbolServer.isTrusted())) { + continue; + } + row.setStatus(symbolServer.isValid(monitor) ? VALID : INVALID); + } + } + finally { + Swing.runLater(() -> tableModel.fireTableDataChanged()); + } + }); + } + + private JScrollPane buildTable() { + table = new GTable(tableModel); + table.setVisibleRowCount(4); + table.setUserSortingEnabled(false); + table.getSelectionManager() + .addListSelectionListener(e -> updatePanelButtonEnablement()); + + table.setPreferredScrollableViewportSize(new Dimension(500, 100)); + + return new JScrollPane(table); + } + + private JPanel buildButtonPanel() { + + refreshSearchLocationsStatusButton = createImageButton(Icons.REFRESH_ICON, + "Refresh Status", "SymbolServerConfig Refresh Status"); + refreshSearchLocationsStatusButton.addActionListener( + e -> refreshSymbolServerLocationStatus(false /* query all */)); + + moveLocationUpButton = + createImageButton(Icons.UP_ICON, "Up", "SymbolServerConfig MoveUpDown"); + moveLocationUpButton.addActionListener(e -> moveLocation(-1)); + moveLocationUpButton.setToolTipText("Move location up"); + + moveLocationDownButton = + createImageButton(Icons.DOWN_ICON, "Down", "SymbolServerConfig MoveUpDown"); + moveLocationDownButton.addActionListener(e -> moveLocation(1)); + moveLocationDownButton.setToolTipText("Move location down"); + + deleteLocationButton = + createImageButton(Icons.DELETE_ICON, "Delete", "SymbolServerConfig Delete"); + deleteLocationButton.addActionListener(e -> deleteLocation()); + + addLocationButton = createImageButton(Icons.ADD_ICON, "Add", "SymbolServerConfig Add"); + addLocationButton.addActionListener(e -> addLocation()); + + saveSearchLocationsButton = + createImageButton(Icons.SAVE_ICON, "Save Configuration", "SymbolServerConfig Save"); + saveSearchLocationsButton.addActionListener(e -> saveConfig()); + + JPanel tableButtonPanel = new JPanel(); + tableButtonPanel.setLayout(new BoxLayout(tableButtonPanel, BoxLayout.X_AXIS)); + tableButtonPanel.add(new GLabel("Additional Search Paths:")); + tableButtonPanel.add(Box.createHorizontalGlue()); + tableButtonPanel.add(addLocationButton); + tableButtonPanel.add(deleteLocationButton); + tableButtonPanel.add(moveLocationUpButton); + tableButtonPanel.add(moveLocationDownButton); + tableButtonPanel.add(refreshSearchLocationsStatusButton); + tableButtonPanel.add(saveSearchLocationsButton); + + return tableButtonPanel; + } + + private JPanel buildSymbolStorageLocationPanel() { + symbolStorageLocationTextField = new HintTextField(" Required "); + symbolStorageLocationTextField.setEditable(false); + symbolStorageLocationTextField.setToolTipText( + "User-specified directory where PDB files are stored. Required."); + + chooseSymbolStorageLocationButton = new BrowseButton(); + chooseSymbolStorageLocationButton.addActionListener(e -> chooseSymbolStorageLocation()); + + symbolStorageLocationPanel = new JPanel(new PairLayout(5, 5)); + GLabel symbolStorageLocLabel = + new GLabel("Local Symbol Storage:", SwingConstants.RIGHT); + symbolStorageLocLabel.setToolTipText(symbolStorageLocationTextField.getToolTipText()); + + symbolStorageLocationPanel.add(symbolStorageLocLabel); + symbolStorageLocationPanel.add(LoadPdbDialog.join(null, symbolStorageLocationTextField, + chooseSymbolStorageLocationButton)); + return symbolStorageLocationPanel; + } + + private void updatePanelButtonEnablement() { + boolean hasLocalSymbolStore = localSymbolStore != null; + boolean singleRow = table.getSelectedRowCount() == 1; + boolean moreThanOneRow = table.getRowCount() > 1; + + refreshSearchLocationsStatusButton + .setEnabled(hasLocalSymbolStore && !tableModel.isEmpty()); + moveLocationUpButton.setEnabled(hasLocalSymbolStore && singleRow && moreThanOneRow); + moveLocationDownButton.setEnabled(hasLocalSymbolStore && singleRow && moreThanOneRow); + addLocationButton.setEnabled(hasLocalSymbolStore); + deleteLocationButton.setEnabled(hasLocalSymbolStore && table.getSelectedRowCount() > 0); + saveSearchLocationsButton.setEnabled(hasLocalSymbolStore && isConfigChanged()); + updateLayout(hasLocalSymbolStore); + } + + private void chooseSymbolStorageLocation() { + GhidraFileChooser chooser = getChooser(); + File f = chooser.getSelectedFile(); + chooser.dispose(); + + if (f != null) { + configChanged = true; + setSymbolStorageLocation(f, true); + updateButtonEnablement(); + } + } + + private void importLocations() { + String envVar = (String) JOptionPane.showInputDialog(this, + "Enter value:

Example: SVR*c:\\symbols*https://msdl.microsoft.com/download/symbols/

", + "Enter Symbol Server Search Path Value", JOptionPane.QUESTION_MESSAGE, null, null, + Objects.requireNonNullElse(System.getenv(MS_SYMBOLSERVER_ENVVAR), "")); + if (envVar == null) { + return; + } + + List symbolServerPaths = getSymbolPathsFromEnvStr(envVar); + if (!symbolServerPaths.isEmpty()) { + // if the first item in the path list looks like a local symbol storage path, + // allow the user to set it as the storage dir (and remove it from the elements + // that will be added to the search list) + String firstSearchPath = symbolServerPaths.get(0); + SymbolServer symbolServer = + symbolServerInstanceCreatorContext.getSymbolServerInstanceCreatorRegistry() + .newSymbolServer(firstSearchPath, symbolServerInstanceCreatorContext); + if (symbolServer instanceof LocalSymbolStore localSymbolStore && + localSymbolStore.isValid()) { + int choice = + OptionDialog.showYesNoCancelDialog(this, "Set Symbol Storage Location", + "Set symbol storage location to " + firstSearchPath + "?"); + if (choice == OptionDialog.CANCEL_OPTION) { + return; + } + if (choice == OptionDialog.YES_OPTION) { + symbolServerPaths.remove(0); + configChanged = true; + setSymbolStorageLocation(localSymbolStore.getRootDir(), true); + } + } + } + + tableModel.addSymbolServers( + symbolServerInstanceCreatorContext.getSymbolServerInstanceCreatorRegistry() + .createSymbolServersFromPathList(symbolServerPaths, + symbolServerInstanceCreatorContext)); + updateButtonEnablement(); + } + + private void addLocation() { + JPopupMenu menu = createAddLocationPopupMenu(); + menu.show(addLocationButton, 0, 0); + } + + private JPopupMenu createAddLocationPopupMenu() { + JPopupMenu menu = new JPopupMenu(); + JMenuItem addDirMenuItem = new JMenuItem("Directory"); + addDirMenuItem.addActionListener(e -> addDirectoryLocation()); + menu.add(addDirMenuItem); + + JMenuItem addURLMenuItem = new JMenuItem("URL"); + addURLMenuItem.addActionListener(e -> addUrlLocation()); + menu.add(addURLMenuItem); + + JMenuItem addProgLocMenuItem = + new JMenuItem(SameDirSymbolStore.PROGRAMS_IMPORT_LOCATION_DESCRIPTION_STR); + addProgLocMenuItem.addActionListener(e -> addSameDirLocation()); + menu.add(addProgLocMenuItem); + + JMenuItem importEnvMenuItem = new JMenuItem("Import _NT_SYMBOL_PATH"); + importEnvMenuItem.addActionListener(e -> importLocations()); + menu.add(importEnvMenuItem); + + if (!knownSymbolServers.isEmpty()) { + menu.add(new JSeparator()); + for (WellKnownSymbolServerLocation ssloc : knownSymbolServers) { + JMenuItem mi = new JMenuItem(ssloc.location()); + mi.addActionListener(e -> addKnownLocation(ssloc)); + mi.setToolTipText(" [from " + ssloc.fileOrigin() + "]"); + menu.add(mi); + } + } + DockingWindowManager.getHelpService() + .registerHelp(menu, new HelpLocation(PdbPlugin.PDB_PLUGIN_HELP_TOPIC, + "SymbolServerConfig_Add")); + return menu; + } + + private void addSameDirLocation() { + SameDirSymbolStore sameDirSymbolStore = + new SameDirSymbolStore(symbolServerInstanceCreatorContext.getRootDir()); + tableModel.addSymbolServer(sameDirSymbolStore); + } + + private void addKnownLocation(WellKnownSymbolServerLocation ssloc) { + SymbolServer symbolServer = + symbolServerInstanceCreatorContext.getSymbolServerInstanceCreatorRegistry() + .newSymbolServer(ssloc.location(), symbolServerInstanceCreatorContext); + if (symbolServer != null) { + tableModel.addSymbolServer(symbolServer); + } + } + + private void addUrlLocation() { + String urlLocationString = OptionDialog.showInputSingleLineDialog(this, "Enter URL", + "Enter the URL of a Symbol Server: ", "https://"); + if (urlLocationString == null || urlLocationString.isBlank()) { + return; + } + urlLocationString = urlLocationString.toLowerCase(); + if (!(urlLocationString.startsWith("http://") || + urlLocationString.startsWith("https://"))) { + Msg.showWarn(this, this, "Bad URL", "Invalid URL: " + urlLocationString); + return; + } + try { + HttpSymbolServer httpSymbolServer = + HttpSymbolServer.createUntrusted(urlLocationString); + tableModel.addSymbolServer(httpSymbolServer); + } + catch (IllegalArgumentException e) { + Msg.showWarn(this, this, "Bad URL", "Invalid URL: " + urlLocationString); + } + } + + private void addDirectoryLocation() { + File dir = + FilePromptDialog.chooseDirectory("Enter Path", "Symbol Storage Location: ", null); + if (dir == null) { + return; + } + if (!dir.exists() || !dir.isDirectory()) { + Msg.showError(this, this, "Bad path", "Invalid path: " + dir); + return; + } + LocalSymbolStore symbolStore = new LocalSymbolStore(dir); + tableModel.addSymbolServer(symbolStore); + } + + private void deleteLocation() { + int selectedRow = table.getSelectedRow(); + tableModel.deleteRows(table.getSelectedRows()); + if (selectedRow >= 0 && selectedRow < table.getRowCount()) { + table.selectRow(selectedRow); + } + } + + private void moveLocation(int delta) { + if (table.getSelectedRowCount() == 1) { + tableModel.moveRow(table.getSelectedRow(), delta); + } + } + + private GhidraFileChooser getChooser() { + + GhidraFileChooser chooser = new GhidraFileChooser(this); + chooser.setMultiSelectionEnabled(false); + chooser.setApproveButtonText("Choose"); + chooser.setFileSelectionMode(GhidraFileChooserMode.DIRECTORIES_ONLY); + chooser.setTitle("Select Symbol Storage Dir"); + + return chooser; + } + + } + + //--------------------------------------------------------------------------------------------- + + private static JButton createImageButton(Icon buttonIcon, String alternateText, + String helpLoc) { + + JButton button = new GButton(buttonIcon); + button.setToolTipText(alternateText); + button.setPreferredSize(BUTTON_SIZE); + + DockingWindowManager.getHelpService() + .registerHelp(button, new HelpLocation(PdbPlugin.PDB_PLUGIN_HELP_TOPIC, helpLoc)); + + return button; + } + + /** + * Returns true if the given file path is a directory that contains no files. + *

+ * + * @param directory path to a location on the file system + * @return true if is a directory and it contains no files + */ + private static boolean isEmptyDirectory(File directory) { + if (directory.isDirectory()) { + File[] dirContents = directory.listFiles(); + return dirContents != null && dirContents.length == 0; + } + return false; + } + + private static List getSymbolPathsFromEnvStr(String envString) { + // Expect the environment string to be in the MS symbol server search path form: + // srv*[local cache]*[private symbol server]*https://msdl.microsoft.com/download/symbols + // srv*c:\symbols*https://msdl.microsoft.com/download/symbols;srv*c:\additional*https://symbol.server.tld/ + String[] envParts = envString.split("[*;]"); + List results = new ArrayList<>(); + Set locationStringDeduplicationSet = new HashSet<>(); + for (String envPart : envParts) { + String locationString = envPart.trim(); + if (!locationString.isBlank() && !locationString.equalsIgnoreCase("srv") && + !locationStringDeduplicationSet.contains(locationString)) { + results.add(locationString); + locationStringDeduplicationSet.add(locationString); + } + } + + return results; + } + } diff --git a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/LoadPdbDialog.java b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/LoadPdbDialog.java index a4b7c0040d..9392f637fa 100644 --- a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/LoadPdbDialog.java +++ b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/LoadPdbDialog.java @@ -4,9 +4,9 @@ * 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. @@ -72,6 +72,8 @@ public class LoadPdbDialog extends DialogComponentProvider { ExtensionFileFilter.forExtensions("Microsoft Program Databases", "pdb", "pd_", "pdb.xml"); private static final SymbolFileInfo UNKNOWN_SYMFILE = makeUnknownSymbolFileInstance(""); + private static final List knownSymbolServers = + WellKnownSymbolServerLocation.loadAll(); public static class LoadPdbResults { public File pdbFile; @@ -270,7 +272,7 @@ public class LoadPdbDialog extends DialogComponentProvider { return SymbolFileInfo.fromValues(pdbPath, uid, age); } - private void searchForPdbs(boolean allowRemote) { + private void searchForPdbs(boolean allowUntrusted) { if (pdbAgeTextField.getText().isBlank() || pdbAgeTextField.getValue() > NumericUtilities.MAX_UNSIGNED_INT32_AS_LONG) { Msg.showWarn(this, null, "Bad PDB Age", "Invalid PDB Age value"); @@ -282,8 +284,8 @@ public class LoadPdbDialog extends DialogComponentProvider { return; } Set findOptions = symbolFilePanel.getFindOptions(); - if (allowRemote) { - findOptions.add(FindOption.ALLOW_REMOTE); + if (allowUntrusted) { + findOptions.add(FindOption.ALLOW_UNTRUSTED); } executeMonitoredRunnable("Search for PDBs", true, true, 0, monitor -> { try { @@ -316,10 +318,11 @@ public class LoadPdbDialog extends DialogComponentProvider { setHelpLocation(new HelpLocation(PdbPlugin.PDB_PLUGIN_HELP_TOPIC, "Load PDB File")); addStatusTextSupplier(() -> lastSearchOptions != null && advancedToggleButton.isSelected() - ? SymbolServerPanel.getSymbolServerWarnings(symbolServerService.getSymbolServers()) + ? WellKnownSymbolServerLocation.getWarningsFor(knownSymbolServers, + symbolServerService.getSymbolServers()) : null); addStatusTextSupplier(this::getSelectedPdbNoticeText); - addStatusTextSupplier(this::getAllowRemoteWarning); + addStatusTextSupplier(this::getAllowUntrustedWarning); addStatusTextSupplier(this::getFoundCountInfo); addButtons(); @@ -600,12 +603,13 @@ public class LoadPdbDialog extends DialogComponentProvider { } } - private StatusText getAllowRemoteWarning() { - int remoteSymbolServerCount = symbolServerService.getRemoteSymbolServerCount(); + private StatusText getAllowUntrustedWarning() { + int untrustedSymbolServerCount = + SymbolServer.getUntrustedCount(symbolServerService.getSymbolServers()); return lastSearchOptions != null && advancedToggleButton.isSelected() && - remoteSymbolServerCount != 0 && !lastSearchOptions.contains(FindOption.ALLOW_REMOTE) + untrustedSymbolServerCount != 0 && !lastSearchOptions.contains(FindOption.ALLOW_UNTRUSTED) ? new StatusText( - "Remote servers were excluded. Use \"Search All\" button to also search remote servers.", + "Untrusted servers were excluded. Use \"Search All\" button to also include untrusted servers.", MessageType.INFO, false) : null; } diff --git a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/SymbolFilePanel.java b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/SymbolFilePanel.java index 34a6c01536..cee8a3ec3b 100644 --- a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/SymbolFilePanel.java +++ b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/SymbolFilePanel.java @@ -4,9 +4,9 @@ * 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. @@ -39,7 +39,7 @@ import pdb.symbolserver.FindOption; */ class SymbolFilePanel extends JPanel { interface SearchCallback { - void searchForPdbs(boolean allowRemote); + void searchForPdbs(boolean allowUntrusted); } static final String SEARCH_OPTIONS_HELP_ANCHOR = "PDB_Search_Search_Options"; @@ -149,10 +149,10 @@ class SymbolFilePanel extends JPanel { } private JPanel buildButtonPanel() { - searchLocalButton = new JButton("Search Local"); - searchLocalButton.setToolTipText("Search local symbol servers only."); + searchLocalButton = new JButton("Search"); + searchLocalButton.setToolTipText("Search trusted symbol servers only."); searchAllButton = new JButton("Search All"); - searchAllButton.setToolTipText("Search local and remote symbol servers."); + searchAllButton.setToolTipText("Search trusted and untrusted symbol servers."); ignorePdbUid = new GCheckBox("Ignore GUID/ID"); ignorePdbUid.setToolTipText( diff --git a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/SymbolServerPanel.java b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/SymbolServerPanel.java deleted file mode 100644 index a0e4de296d..0000000000 --- a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/SymbolServerPanel.java +++ /dev/null @@ -1,590 +0,0 @@ -/* ### - * 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 pdb.symbolserver.ui; - -import java.awt.BorderLayout; -import java.awt.Dimension; -import java.io.File; -import java.io.IOException; -import java.net.URI; -import java.util.*; -import java.util.function.Consumer; -import java.util.stream.Collectors; - -import javax.swing.*; -import javax.swing.table.TableColumn; - -import docking.DockingWindowManager; -import docking.widgets.OptionDialog; -import docking.widgets.button.BrowseButton; -import docking.widgets.button.GButton; -import docking.widgets.filechooser.GhidraFileChooser; -import docking.widgets.filechooser.GhidraFileChooserMode; -import docking.widgets.label.GHtmlLabel; -import docking.widgets.label.GLabel; -import docking.widgets.table.GTable; -import docking.widgets.textfield.HintTextField; -import generic.theme.GThemeDefaults.Colors.Messages; -import ghidra.framework.preferences.Preferences; -import ghidra.util.*; -import ghidra.util.layout.PairLayout; -import pdb.PdbPlugin; -import pdb.symbolserver.*; -import pdb.symbolserver.ui.LoadPdbDialog.StatusText; -import resources.Icons; -import utilities.util.FileUtilities; - -/** - * Panel that allows the user to configure a SymbolServerService: a local - * symbol storage directory and a list of search locations. - */ -class SymbolServerPanel extends JPanel { - private static final String MS_SYMBOLSERVER_ENVVAR = "_NT_SYMBOL_PATH"; - - private static final Dimension BUTTON_SIZE = new Dimension(32, 32); - - private static List knownSymbolServers = - WellKnownSymbolServerLocation.loadAll(); - - private SymbolStore localSymbolStore; - private SymbolServerInstanceCreatorContext symbolServerInstanceCreatorContext; - - private SymbolServerTableModel tableModel; - private GTable table; - private JPanel additionalSearchLocationsPanel; - private JPanel defaultConfigNotice; - private Consumer changeCallback; - - private JButton refreshSearchLocationsStatusButton; - private JButton moveLocationUpButton; - private JButton moveLocationDownButton; - private JButton deleteLocationButton; - private JButton addLocationButton; - private JPanel symbolStorageLocationPanel; - private HintTextField symbolStorageLocationTextField; - private JButton chooseSymbolStorageLocationButton; - private JButton saveSearchLocationsButton; - private boolean configChanged; - - SymbolServerPanel(Consumer changeCallback, - SymbolServerInstanceCreatorContext symbolServerInstanceCreatorContext) { - this.symbolServerInstanceCreatorContext = symbolServerInstanceCreatorContext; - - build(); - - DockingWindowManager.getHelpService() - .registerHelp(this, - new HelpLocation(PdbPlugin.PDB_PLUGIN_HELP_TOPIC, "Symbol Server Config")); - - SymbolServerService temporarySymbolServerService = - PdbPlugin.getSymbolServerService(symbolServerInstanceCreatorContext); - if (temporarySymbolServerService - .getSymbolStore() instanceof LocalSymbolStore tempLocalSymbolStore) { - setSymbolStorageLocation(tempLocalSymbolStore.getRootDir(), false); - } - tableModel.addSymbolServers(temporarySymbolServerService.getSymbolServers()); - setConfigChanged(false); - - this.changeCallback = changeCallback; - } - - private void build() { - setLayout(new BorderLayout()); - setBorder(BorderFactory.createTitledBorder("Symbol Server Search Config")); - - buildSymbolStorageLocationPanel(); - JPanel buttonPanel = buildButtonPanel(); - JScrollPane tableScrollPane = buildTable(); - defaultConfigNotice = new JPanel(); - GHtmlLabel label = new GHtmlLabel("


Missing / invalid configuration.

" + - "Using default search location:
Program's Import Location
"); - label.setHorizontalAlignment(SwingConstants.CENTER); - defaultConfigNotice.add(label); - defaultConfigNotice.setPreferredSize(tableScrollPane.getPreferredSize()); - - additionalSearchLocationsPanel = new JPanel(); - additionalSearchLocationsPanel - .setLayout(new BoxLayout(additionalSearchLocationsPanel, BoxLayout.Y_AXIS)); - additionalSearchLocationsPanel.add(buttonPanel); - additionalSearchLocationsPanel.add(tableScrollPane); - - add(symbolStorageLocationPanel, BorderLayout.NORTH); - add(additionalSearchLocationsPanel, BorderLayout.CENTER); - } - - private void updateLayout(boolean showTable) { - if (showTable == (additionalSearchLocationsPanel.getParent() != null)) { - return; - } - - remove(additionalSearchLocationsPanel); - remove(defaultConfigNotice); - add(showTable ? additionalSearchLocationsPanel : defaultConfigNotice, BorderLayout.CENTER); - invalidate(); - } - - /** - * Returns a new {@link SymbolServerService} instance representing the currently - * displayed configuration, or null if the displayed configuration is not valid. - * - * @return new {@link SymbolServerService} or null - */ - SymbolServerService getSymbolServerService() { - return (localSymbolStore != null) - ? new SymbolServerService(localSymbolStore, tableModel.getSymbolServers()) - : null; - } - - void setSymbolServers(List symbolServers) { - tableModel.setSymbolServers(symbolServers); - } - - /** - * The union of the changed status of the local storage path and the additional - * search paths table model changed status. - * - * @return boolean true if the config has changed - */ - boolean isConfigChanged() { - return configChanged || tableModel.isDataChanged(); - } - - void setConfigChanged(boolean configChanged) { - this.configChanged = configChanged; - tableModel.setDataChanged(configChanged); - } - - private JScrollPane buildTable() { - tableModel = new SymbolServerTableModel(); - table = new GTable(tableModel); - table.setVisibleRowCount(4); - table.setUserSortingEnabled(false); - table.getSelectionManager().addListSelectionListener(e -> { - updateButtonEnablement(); - }); - tableModel.addTableModelListener(e -> { - updateButtonEnablement(); - fireChanged(); - }); - - TableColumn enabledColumn = table.getColumnModel().getColumn(0); - enabledColumn.setResizable(false); - enabledColumn.setPreferredWidth(32); - enabledColumn.setMaxWidth(32); - enabledColumn.setMinWidth(32); - - TableColumn statusColumn = table.getColumnModel().getColumn(1); - statusColumn.setResizable(false); - statusColumn.setPreferredWidth(32); - statusColumn.setMaxWidth(32); - statusColumn.setMinWidth(32); - - table.setPreferredScrollableViewportSize(new Dimension(100, 100)); - - return new JScrollPane(table); - } - - private JPanel buildButtonPanel() { - - refreshSearchLocationsStatusButton = - createImageButton(Icons.REFRESH_ICON, "Refresh Status"); - refreshSearchLocationsStatusButton.addActionListener(e -> refreshSearchLocationStatus()); - DockingWindowManager.getHelpService() - .registerHelp(refreshSearchLocationsStatusButton, new HelpLocation( - PdbPlugin.PDB_PLUGIN_HELP_TOPIC, "SymbolServerConfig Refresh Status")); - - moveLocationUpButton = createImageButton(Icons.UP_ICON, "Up"); - moveLocationUpButton.addActionListener(e -> moveLocation(-1)); - moveLocationUpButton.setToolTipText("Move location up"); - DockingWindowManager.getHelpService() - .registerHelp(moveLocationUpButton, new HelpLocation( - PdbPlugin.PDB_PLUGIN_HELP_TOPIC, "SymbolServerConfig MoveUpDown")); - - moveLocationDownButton = createImageButton(Icons.DOWN_ICON, "Down"); - moveLocationDownButton.addActionListener(e -> moveLocation(1)); - moveLocationDownButton.setToolTipText("Move location down"); - DockingWindowManager.getHelpService() - .registerHelp(moveLocationDownButton, new HelpLocation( - PdbPlugin.PDB_PLUGIN_HELP_TOPIC, "SymbolServerConfig MoveUpDown")); - - deleteLocationButton = createImageButton(Icons.DELETE_ICON, "Delete"); - deleteLocationButton.addActionListener(e -> deleteLocation()); - DockingWindowManager.getHelpService() - .registerHelp(deleteLocationButton, - new HelpLocation(PdbPlugin.PDB_PLUGIN_HELP_TOPIC, "SymbolServerConfig Delete")); - - addLocationButton = createImageButton(Icons.ADD_ICON, "Add"); - addLocationButton.addActionListener(e -> addLocation()); - DockingWindowManager.getHelpService() - .registerHelp(addLocationButton, - new HelpLocation(PdbPlugin.PDB_PLUGIN_HELP_TOPIC, "SymbolServerConfig Add")); - - saveSearchLocationsButton = createImageButton(Icons.SAVE_ICON, "Save Configuration"); - saveSearchLocationsButton.addActionListener(e -> saveConfig()); - DockingWindowManager.getHelpService() - .registerHelp(saveSearchLocationsButton, - new HelpLocation(PdbPlugin.PDB_PLUGIN_HELP_TOPIC, "SymbolServerConfig Save")); - - JPanel buttonPanel = new JPanel(); - buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS)); - buttonPanel.add(new GLabel("Additional Search Paths:")); - buttonPanel.add(Box.createHorizontalGlue()); - buttonPanel.add(addLocationButton); - buttonPanel.add(deleteLocationButton); - buttonPanel.add(moveLocationUpButton); - buttonPanel.add(moveLocationDownButton); - buttonPanel.add(refreshSearchLocationsStatusButton); - buttonPanel.add(saveSearchLocationsButton); - - return buttonPanel; - } - - private JPanel buildSymbolStorageLocationPanel() { - symbolStorageLocationTextField = new HintTextField(" Required "); - symbolStorageLocationTextField.setEditable(false); - symbolStorageLocationTextField - .setToolTipText("User-specified directory where PDB files are stored. Required."); - - chooseSymbolStorageLocationButton = new BrowseButton(); - chooseSymbolStorageLocationButton.addActionListener(e -> chooseSymbolStorageLocation()); - - symbolStorageLocationPanel = new JPanel(new PairLayout(5, 5)); - GLabel symbolStorageLocLabel = new GLabel("Local Symbol Storage:", SwingConstants.RIGHT); - symbolStorageLocLabel.setToolTipText(symbolStorageLocationTextField.getToolTipText()); - - symbolStorageLocationPanel.add(symbolStorageLocLabel); - symbolStorageLocationPanel.add(LoadPdbDialog.join(null, symbolStorageLocationTextField, - chooseSymbolStorageLocationButton)); - return symbolStorageLocationPanel; - } - - private void updateButtonEnablement() { - boolean hasLocalSymbolStore = localSymbolStore != null; - boolean singleRow = table.getSelectedRowCount() == 1; - boolean moreThanOneRow = table.getRowCount() > 1; - - refreshSearchLocationsStatusButton.setEnabled(hasLocalSymbolStore && !tableModel.isEmpty()); - moveLocationUpButton.setEnabled(hasLocalSymbolStore && singleRow && moreThanOneRow); - moveLocationDownButton.setEnabled(hasLocalSymbolStore && singleRow && moreThanOneRow); - addLocationButton.setEnabled(hasLocalSymbolStore); - deleteLocationButton.setEnabled(hasLocalSymbolStore && table.getSelectedRowCount() > 0); - saveSearchLocationsButton.setEnabled(hasLocalSymbolStore && isConfigChanged()); - updateLayout(hasLocalSymbolStore); - } - - private void setSymbolStorageLocation(File symbolStorageDir, boolean allowGUIPrompt) { - if (symbolStorageDir == null) { - return; - } - if (!symbolStorageDir.exists()) { - if (!allowGUIPrompt) { - return; - } - - int opt = OptionDialog.showOptionDialog(this, "Create Local Symbol Storage Directory?", - "Symbol storage directory
" + - HTMLUtilities.escapeHTML(symbolStorageDir.getPath()) + - "
does not exist. Create?", - "Yes", OptionDialog.QUESTION_MESSAGE); - if (opt == OptionDialog.CANCEL_OPTION) { - return; - } - try { - FileUtilities.checkedMkdirs(symbolStorageDir); - } - catch (IOException e) { - Msg.showError(this, this, "Failure", "Failed to create symbol storage directory " + - symbolStorageDir + ": " + e.getMessage()); - return; - } - } - - if (allowGUIPrompt && isEmptyDirectory(symbolStorageDir)) { - if (OptionDialog.showYesNoDialog(this, "Initialize Symbol Storage Directory?", - "Initialize new directory as Microsoft symbol storage directory?") == OptionDialog.YES_OPTION) { - try { - LocalSymbolStore.create(symbolStorageDir, - 1 /* level1 MS symbol storage directory */); - } - catch (IOException e) { - Msg.showError(this, this, "Initialize Failure", - "Failed to initialize symbol storage directory " + symbolStorageDir, e); - } - } - } - - localSymbolStore = - symbolServerInstanceCreatorContext.getSymbolServerInstanceCreatorRegistry() - .newSymbolServer(symbolStorageDir.getPath(), symbolServerInstanceCreatorContext, - SymbolStore.class); - symbolStorageLocationTextField.setText(symbolStorageDir.getPath()); - fireChanged(); - } - - private void fireChanged() { - if (changeCallback != null) { - changeCallback.accept(getSymbolServerService()); - } - } - - private void chooseSymbolStorageLocation() { - configChanged = true; - GhidraFileChooser chooser = getChooser(); - setSymbolStorageLocation(chooser.getSelectedFile(), true); - updateButtonEnablement(); - chooser.dispose(); - } - - private void importLocations() { - String envVar = (String) JOptionPane.showInputDialog(this, - "Enter value:

Example: SVR*c:\\symbols*https://msdl.microsoft.com/download/symbols/

", - "Enter Symbol Server Search Path Value", JOptionPane.QUESTION_MESSAGE, null, null, - Objects.requireNonNullElse(System.getenv(MS_SYMBOLSERVER_ENVVAR), "")); - if (envVar == null) { - return; - } - - List symbolServerPaths = getSymbolPathsFromEnvStr(envVar); - if (!symbolServerPaths.isEmpty()) { - // if the first item in the path list looks like a local symbol storage path, - // allow the user to set it as the storage dir (and remove it from the elements - // that will be added to the search list) - String firstSearchPath = symbolServerPaths.get(0); - SymbolServer symbolServer = - symbolServerInstanceCreatorContext.getSymbolServerInstanceCreatorRegistry() - .newSymbolServer(firstSearchPath, symbolServerInstanceCreatorContext); - if (symbolServer instanceof LocalSymbolStore localSymbolStore && - localSymbolStore.isValid()) { - int choice = OptionDialog.showYesNoCancelDialog(this, "Set Symbol Storage Location", - "Set symbol storage location to " + firstSearchPath + "?"); - if (choice == OptionDialog.CANCEL_OPTION) { - return; - } - if (choice == OptionDialog.YES_OPTION) { - symbolServerPaths.remove(0); - configChanged = true; - setSymbolStorageLocation(localSymbolStore.getRootDir(), true); - symbolStorageLocationTextField.setText(symbolServer.getName()); - } - } - } - - tableModel.addSymbolServers( - symbolServerInstanceCreatorContext.getSymbolServerInstanceCreatorRegistry() - .createSymbolServersFromPathList(symbolServerPaths, - symbolServerInstanceCreatorContext)); - fireChanged(); - } - - private List getSymbolPathsFromEnvStr(String envString) { - // Expect the environment string to be in the MS symbol server search path form: - // srv*[local cache]*[private symbol server]*https://msdl.microsoft.com/download/symbols - // srv*c:\symbols*https://msdl.microsoft.com/download/symbols;srv*c:\additional*https://symbol.server.tld/ - String[] envParts = envString.split("[*;]"); - List results = new ArrayList<>(); - Set locationStringDeduplicationSet = new HashSet<>(); - for (String envPart : envParts) { - String locationString = envPart.trim(); - if (!locationString.isBlank() && !locationString.equalsIgnoreCase("srv") && - !locationStringDeduplicationSet.contains(locationString)) { - results.add(locationString); - locationStringDeduplicationSet.add(locationString); - } - } - - return results; - } - - private void addLocation() { - JPopupMenu menu = createAddLocationPopupMenu(); - menu.show(addLocationButton, 0, 0); - } - - private JPopupMenu createAddLocationPopupMenu() { - JPopupMenu menu = new JPopupMenu(); - JMenuItem addDirMenuItem = new JMenuItem("Directory"); - addDirMenuItem.addActionListener(e -> addDirectoryLocation()); - menu.add(addDirMenuItem); - - JMenuItem addURLMenuItem = new JMenuItem("URL"); - addURLMenuItem.addActionListener(e -> addUrlLocation()); - menu.add(addURLMenuItem); - - JMenuItem addProgLocMenuItem = - new JMenuItem(SameDirSymbolStore.PROGRAMS_IMPORT_LOCATION_DESCRIPTION_STR); - addProgLocMenuItem.addActionListener(e -> addSameDirLocation()); - menu.add(addProgLocMenuItem); - - JMenuItem importEnvMenuItem = new JMenuItem("Import _NT_SYMBOL_PATH"); - importEnvMenuItem.addActionListener(e -> importLocations()); - menu.add(importEnvMenuItem); - - if (!knownSymbolServers.isEmpty()) { - menu.add(new JSeparator()); - for (WellKnownSymbolServerLocation ssloc : knownSymbolServers) { - JMenuItem mi = new JMenuItem(ssloc.getLocation()); - mi.addActionListener(e -> addKnownLocation(ssloc)); - mi.setToolTipText(" [from " + ssloc.getFileOrigin() + "]"); - menu.add(mi); - } - } - DockingWindowManager.getHelpService() - .registerHelp(menu, - new HelpLocation(PdbPlugin.PDB_PLUGIN_HELP_TOPIC, "SymbolServerConfig_Add")); - return menu; - } - - private void addSameDirLocation() { - SameDirSymbolStore sameDirSymbolStore = - new SameDirSymbolStore(symbolServerInstanceCreatorContext.getRootDir()); - tableModel.addSymbolServer(sameDirSymbolStore); - } - - private void addKnownLocation(WellKnownSymbolServerLocation ssloc) { - SymbolServer symbolServer = - symbolServerInstanceCreatorContext.getSymbolServerInstanceCreatorRegistry() - .newSymbolServer(ssloc.getLocation(), symbolServerInstanceCreatorContext); - if (symbolServer != null) { - tableModel.addSymbolServer(symbolServer); - } - } - - private void addUrlLocation() { - String urlLocationString = OptionDialog.showInputSingleLineDialog(this, "Enter URL", - "Enter the URL of a Symbol Server: ", "https://"); - if (urlLocationString == null || urlLocationString.isBlank()) { - return; - } - urlLocationString = urlLocationString.toLowerCase(); - if (!(urlLocationString.startsWith("http://") || - urlLocationString.startsWith("https://"))) { - Msg.showWarn(this, this, "Bad URL", "Invalid URL: " + urlLocationString); - return; - } - try { - HttpSymbolServer httpSymbolServer = new HttpSymbolServer(URI.create(urlLocationString)); - tableModel.addSymbolServer(httpSymbolServer); - } - catch (IllegalArgumentException e) { - Msg.showWarn(this, this, "Bad URL", "Invalid URL: " + urlLocationString); - } - } - - private void addDirectoryLocation() { - File dir = - FilePromptDialog.chooseDirectory("Enter Path", "Symbol Storage Location: ", null); - if (dir == null) { - return; - } - if (!dir.exists() || !dir.isDirectory()) { - Msg.showError(this, this, "Bad path", "Invalid path: " + dir); - return; - } - LocalSymbolStore symbolStore = new LocalSymbolStore(dir); - tableModel.addSymbolServer(symbolStore); - } - - private void deleteLocation() { - int selectedRow = table.getSelectedRow(); - tableModel.deleteRows(table.getSelectedRows()); - if (selectedRow >= 0 && selectedRow < table.getRowCount()) { - table.selectRow(selectedRow); - } - } - - private void moveLocation(int delta) { - if (table.getSelectedRowCount() == 1) { - tableModel.moveRow(table.getSelectedRow(), delta); - } - } - - private void refreshSearchLocationStatus() { - tableModel.refreshSymbolServerLocationStatus(); - updateButtonEnablement(); - } - - /* package */ void saveConfig() { - SymbolServerService temporarySymbolServerService = getSymbolServerService(); - if (temporarySymbolServerService != null) { - PdbPlugin.saveSymbolServerServiceConfig(temporarySymbolServerService); - Preferences.store(); - setConfigChanged(false); - fireChanged(); - updateButtonEnablement(); - } - } - - private GhidraFileChooser getChooser() { - - GhidraFileChooser chooser = new GhidraFileChooser(this); - chooser.setMultiSelectionEnabled(false); - chooser.setApproveButtonText("Choose"); - chooser.setFileSelectionMode(GhidraFileChooserMode.DIRECTORIES_ONLY); - chooser.setTitle("Select Symbol Storage Dir"); - - return chooser; - } - - /* screen shot usage */ void pushAddLocationButton() { - addLocation(); - } - - /* screen shot usage */ void setSymbolStorageDirectoryTextOnly(String pathStr) { - symbolStorageLocationTextField.setText(pathStr); - } - - /** - * Returns true if the given file path is a directory that contains no files. - *

- * - * @param directory path to a location on the file system - * @return true if is a directory and it contains no files - */ - private static boolean isEmptyDirectory(File directory) { - if (directory.isDirectory()) { - File[] dirContents = directory.listFiles(); - return dirContents != null && dirContents.length == 0; - } - return false; - } - - private static JButton createImageButton(Icon buttonIcon, String alternateText) { - - JButton button = new GButton(buttonIcon); - button.setToolTipText(alternateText); - button.setPreferredSize(BUTTON_SIZE); - - return button; - } - - static StatusText getSymbolServerWarnings(List symbolServers) { - Map warningsByLocation = new HashMap<>(); - for (WellKnownSymbolServerLocation ssloc : knownSymbolServers) { - if (ssloc.getWarning() != null && !ssloc.getWarning().isBlank()) { - warningsByLocation.put(ssloc.getLocation(), ssloc.getWarning()); - } - } - String warning = symbolServers.stream() - .map(symbolServer -> warningsByLocation.get(symbolServer.getName())) - .filter(Objects::nonNull) - .distinct() - .collect(Collectors.joining("
\n")); - - return !warning.isEmpty() ? new StatusText(warning, MessageType.WARNING, false) : null; - } - -} diff --git a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/SymbolServerRow.java b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/SymbolServerRow.java index 58de169690..63eab61c59 100644 --- a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/SymbolServerRow.java +++ b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/SymbolServerRow.java @@ -4,9 +4,9 @@ * 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. @@ -15,8 +15,12 @@ */ package pdb.symbolserver.ui; +import static pdb.symbolserver.ui.SymbolServerRow.LocationStatus.*; + +import ghidra.util.task.TaskMonitor; import pdb.symbolserver.DisabledSymbolServer; import pdb.symbolserver.SymbolServer; +import pdb.symbolserver.SymbolServer.MutableTrust; /** * Represents a row in the {@link SymbolServerTableModel} @@ -59,6 +63,16 @@ class SymbolServerRow { } } + boolean isTrusted() { + return symbolServer.isTrusted(); + } + + void setTrusted(boolean isTrusted) { + if (symbolServer instanceof MutableTrust sswt) { + sswt.setTrusted(isTrusted); + } + } + LocationStatus getStatus() { return status; } @@ -67,6 +81,12 @@ class SymbolServerRow { this.status = status; } + void updateStatus(TaskMonitor monitor) { + if (!(symbolServer instanceof SymbolServer.StatusRequiresContext)) { + this.status = symbolServer.isValid(monitor) ? VALID : INVALID; + } + } + @Override public String toString() { return String.format("SymbolServerRow: [ status: %s, server: %s]", status.toString(), diff --git a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/SymbolServerTableModel.java b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/SymbolServerTableModel.java index bc33ebb483..331e3724a7 100644 --- a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/SymbolServerTableModel.java +++ b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/SymbolServerTableModel.java @@ -4,9 +4,9 @@ * 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. @@ -15,30 +15,28 @@ */ package pdb.symbolserver.ui; -import static java.util.stream.Collectors.toList; -import static pdb.symbolserver.ui.SymbolServerRow.LocationStatus.INVALID; -import static pdb.symbolserver.ui.SymbolServerRow.LocationStatus.VALID; +import static java.util.stream.Collectors.*; +import java.awt.Component; +import java.awt.FontMetrics; import java.util.ArrayList; import java.util.List; -import java.awt.Component; - import javax.swing.*; +import javax.swing.table.TableColumn; import docking.widgets.table.*; +import generic.theme.GIcon; import ghidra.docking.settings.Settings; import ghidra.framework.plugintool.ServiceProvider; import ghidra.framework.plugintool.ServiceProviderStub; -import ghidra.util.Swing; import ghidra.util.table.column.AbstractGColumnRenderer; import ghidra.util.table.column.GColumnRenderer; -import ghidra.util.task.TaskLauncher; import pdb.symbolserver.SymbolServer; import resources.Icons; /** - * Table model for the {@link SymbolServerPanel} table + * Table model for the {@link ConfigPdbDialog} table */ class SymbolServerTableModel extends GDynamicColumnTableModel> { @@ -64,9 +62,7 @@ class SymbolServerTableModel } List getSymbolServers() { - return rows.stream() - .map(SymbolServerRow::getSymbolServer) - .collect(toList()); + return rows.stream().map(SymbolServerRow::getSymbolServer).collect(toList()); } void addSymbolServer(SymbolServer ss) { @@ -92,26 +88,6 @@ class SymbolServerTableModel fireTableDataChanged(); } - void refreshSymbolServerLocationStatus() { - List rowsCopy = new ArrayList<>(this.rows); - TaskLauncher.launchNonModal("Refresh Symbol Server Location Status", monitor -> { - monitor.initialize(rowsCopy.size()); - monitor.setMessage("Refreshing symbol server status"); - try { - for (SymbolServerRow row : rowsCopy) { - if (monitor.isCancelled()) { - break; - } - monitor.setMessage("Checking " + row.getSymbolServer().getName()); - row.setStatus(row.getSymbolServer().isValid(monitor) ? VALID : INVALID); - } - } - finally { - Swing.runLater(SymbolServerTableModel.this::fireTableDataChanged); - } - }); - } - void moveRow(int rowIndex, int deltaIndex) { int destIndex = rowIndex + deltaIndex; if (rowIndex < 0 || rowIndex >= rows.size() || destIndex < 0 || destIndex >= rows.size()) { @@ -159,18 +135,24 @@ class SymbolServerTableModel @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { DynamicTableColumn column = getColumn(columnIndex); - if (column instanceof EnabledColumn) { + if (column instanceof EnabledColumn && aValue instanceof Boolean) { SymbolServerRow row = getRowObject(rowIndex); row.setEnabled((Boolean) aValue); dataChanged = true; fireTableDataChanged(); } + else if (column instanceof TrustedColumn && aValue instanceof Boolean) { + SymbolServerRow row = getRowObject(rowIndex); + row.setTrusted((Boolean) aValue); + dataChanged = true; + fireTableDataChanged(); + } } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { DynamicTableColumn column = getColumn(columnIndex); - return column instanceof EnabledColumn; + return column instanceof EnabledColumn || column instanceof TrustedColumn; } @Override @@ -178,6 +160,7 @@ class SymbolServerTableModel TableColumnDescriptor descriptor = new TableColumnDescriptor<>(); descriptor.addVisibleColumn(new EnabledColumn()); + descriptor.addVisibleColumn(new TrustedColumn()); descriptor.addVisibleColumn(new StatusColumn()); descriptor.addVisibleColumn(new LocationColumn()); @@ -187,9 +170,10 @@ class SymbolServerTableModel //------------------------------------------------------------------------------------------- private static class StatusColumn extends - AbstractDynamicTableColumnStub { + AbstractDynamicTableColumnStub + implements TableColumnInitializer { - private static final Icon VALID_ICON = Icons.get("images/checkmark_green.gif"); + private static final Icon VALID_ICON = new GIcon("icon.checkmark.green"); private static final Icon INVALID_ICON = Icons.ERROR_ICON; private static Icon[] icons = new Icon[] { null, VALID_ICON, INVALID_ICON }; @@ -206,7 +190,7 @@ class SymbolServerTableModel @Override public String getColumnDisplayName(Settings settings) { - return ""; + return "Status"; } @Override @@ -219,14 +203,23 @@ class SymbolServerTableModel return renderer; } + @Override + public void initializeTableColumn(TableColumn col, FontMetrics fm, int padding) { + int colWidth = fm.stringWidth("Status") + padding; + col.setPreferredWidth(colWidth); + col.setMaxWidth(colWidth * 2); + col.setMinWidth(colWidth); + } + } private static class EnabledColumn - extends AbstractDynamicTableColumnStub { + extends AbstractDynamicTableColumnStub + implements TableColumnInitializer { @Override public String getColumnDisplayName(Settings settings) { - return ""; + return "Enabled"; } @Override @@ -240,6 +233,43 @@ class SymbolServerTableModel return "Enabled"; } + @Override + public void initializeTableColumn(TableColumn col, FontMetrics fm, int padding) { + int colWidth = fm.stringWidth("Enabled") + padding; + col.setPreferredWidth(colWidth); + col.setMaxWidth(colWidth * 2); + col.setMinWidth(colWidth); + } + + } + + private static class TrustedColumn + extends AbstractDynamicTableColumnStub + implements TableColumnInitializer { + + @Override + public String getColumnDisplayName(Settings settings) { + return "Trusted"; + } + + @Override + public Boolean getValue(SymbolServerRow rowObject, Settings settings, + ServiceProvider serviceProvider) throws IllegalArgumentException { + return rowObject.isTrusted(); + } + + @Override + public String getColumnName() { + return "Trusted"; + } + + @Override + public void initializeTableColumn(TableColumn col, FontMetrics fm, int padding) { + int colWidth = fm.stringWidth("Trusted") + padding; + col.setPreferredWidth(colWidth); + col.setMaxWidth(colWidth * 2); + col.setMinWidth(colWidth); + } } private static class LocationColumn @@ -305,5 +335,6 @@ class SymbolServerTableModel public String getFilterString(E t, Settings settings) { return t == null ? "" : t.toString(); } + } } diff --git a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/TableColumnInitializer.java b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/TableColumnInitializer.java new file mode 100644 index 0000000000..f4b6cc7c96 --- /dev/null +++ b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/TableColumnInitializer.java @@ -0,0 +1,64 @@ +/* ### + * 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 pdb.symbolserver.ui; + +import java.awt.FontMetrics; + +import javax.swing.table.TableColumn; +import javax.swing.table.TableColumnModel; + +import docking.ComponentProvider; +import docking.DialogComponentProvider; +import docking.widgets.table.*; + +/** + * For Pdb symbolserver gui stuff only. + * + * Add on interface for DynamicTableColumn classes that let them control aspects of the + * matching TableColumn. + */ +public interface TableColumnInitializer { + /** + * Best called during {@link DialogComponentProvider#dialogShown} or + * {@link ComponentProvider#componentShown} + * + * @param table table component + * @param model table model + */ + static void initializeTableColumns(GTable table, GDynamicColumnTableModel model) { + TableColumnModel colModel = table.getColumnModel(); + + FontMetrics fm = table.getTableHeader().getFontMetrics(table.getTableHeader().getFont()); + int padding = fm.stringWidth("WW"); // w.a.g. for the left+right padding on the header column component + + for (int colIndex = 0; colIndex < model.getColumnCount(); colIndex++) { + DynamicTableColumn dtableCol = model.getColumn(colIndex); + if (dtableCol instanceof TableColumnInitializer colInitializer) { + TableColumn tableCol = colModel.getColumn(colIndex); + colInitializer.initializeTableColumn(tableCol, fm, padding); + } + } + } + + /** + * Called to allow the initializer to modify the specified TableColumn + * + * @param col {@link TableColumn} + * @param fm {@link FontMetrics} used by the table header gui component + * @param padding padding to use in the column + */ + void initializeTableColumn(TableColumn col, FontMetrics fm, int padding); +} diff --git a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/WellKnownSymbolServerLocation.java b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/WellKnownSymbolServerLocation.java index c452fd3683..cd5d033d31 100644 --- a/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/WellKnownSymbolServerLocation.java +++ b/Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/WellKnownSymbolServerLocation.java @@ -4,9 +4,9 @@ * 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. @@ -17,68 +17,27 @@ package pdb.symbolserver.ui; import java.io.IOException; import java.util.*; +import java.util.stream.Collectors; import generic.jar.ResourceFile; import ghidra.framework.Application; +import ghidra.util.MessageType; import ghidra.util.Msg; +import pdb.symbolserver.SymbolServer; +import pdb.symbolserver.ui.LoadPdbDialog.StatusText; import utilities.util.FileUtilities; /** * Represents a well-known symbol server location. *

* See the PDB_SYMBOL_SERVER_URLS.pdburl file. + * @param location url string + * @param locationCategory grouping criteria + * @param warning string + * @param fileOrigin file name that contained this info */ -class WellKnownSymbolServerLocation { - private String locationCategory; - private String location; - private String warning; - private String fileOrigin; - - WellKnownSymbolServerLocation(String location, String locationCategory, String warning, - String fileOrigin) { - this.location = location; - this.locationCategory = locationCategory; - this.warning = warning; - this.fileOrigin = fileOrigin; - } - - String getLocationCategory() { - return locationCategory; - } - - String getLocation() { - return location; - } - - String getWarning() { - return warning; - } - - String getFileOrigin() { - return fileOrigin; - } - - @Override - public int hashCode() { - return Objects.hash(location, locationCategory, warning); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - WellKnownSymbolServerLocation other = (WellKnownSymbolServerLocation) obj; - return Objects.equals(location, other.location) && - Objects.equals(locationCategory, other.locationCategory) && - Objects.equals(warning, other.warning); - } +public record WellKnownSymbolServerLocation(String location, String locationCategory, + String warning, String fileOrigin) { /** * Loads all symbol server location files (*.pdburl) and returns a list of entries. @@ -103,10 +62,37 @@ class WellKnownSymbolServerLocation { } } catch (IOException e) { - Msg.warn(WellKnownSymbolServerLocation.class, "Unable to read pdburl file: " + file); + Msg.warn(WellKnownSymbolServerLocation.class, + "Unable to read pdburl file: " + file); } } return results; } + /** + * Returns a formatted StatusText containing all the warnings published by any untrusted + * {@link WellKnownSymbolServerLocation} found in the list of symbolservers. + * + * @param knownSymbolServers list + * @param symbolServers list + * @return StatusText + */ + public static StatusText getWarningsFor(List knownSymbolServers, + List symbolServers) { + Map warningsByLocation = new HashMap<>(); + for (WellKnownSymbolServerLocation ssloc : knownSymbolServers) { + if (ssloc.warning() != null && !ssloc.warning().isBlank()) { + warningsByLocation.put(ssloc.location(), ssloc.warning()); + } + } + String warning = symbolServers.stream() + .filter(symbolServer -> !symbolServer.isTrusted()) + .map(symbolServer -> warningsByLocation.get(symbolServer.getName())) + .filter(Objects::nonNull) + .distinct() + .collect(Collectors.joining("
\n")); + + return !warning.isEmpty() ? new StatusText(warning, MessageType.WARNING, false) : null; + } + } diff --git a/Ghidra/Features/PDB/src/test/java/pdb/symbolserver/DummySymbolServer.java b/Ghidra/Features/PDB/src/test/java/pdb/symbolserver/DummySymbolServer.java index ce1d1f608c..21f6c5894a 100644 --- a/Ghidra/Features/PDB/src/test/java/pdb/symbolserver/DummySymbolServer.java +++ b/Ghidra/Features/PDB/src/test/java/pdb/symbolserver/DummySymbolServer.java @@ -4,9 +4,9 @@ * 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. @@ -21,14 +21,16 @@ import java.util.List; import java.util.Set; import ghidra.util.task.TaskMonitor; +import pdb.symbolserver.SymbolServer.MutableTrust; /** * A "remote" symbol server that answers affirmatively for any query. */ -public class DummySymbolServer implements SymbolServer { +public class DummySymbolServer implements SymbolServer, MutableTrust { private final byte[] dummyPayload; private final boolean returnCompressedFilenames; + private boolean trusted; public DummySymbolServer(String dummyPayload) { this(dummyPayload.getBytes(), false); @@ -78,8 +80,13 @@ public class DummySymbolServer implements SymbolServer { } @Override - public boolean isLocal() { - return false; + public boolean isTrusted() { + return trusted; + } + + @Override + public void setTrusted(boolean isTrusted) { + this.trusted = isTrusted; } } diff --git a/Ghidra/Features/PDB/src/test/java/pdb/symbolserver/SymbolServerServiceTest.java b/Ghidra/Features/PDB/src/test/java/pdb/symbolserver/SymbolServerServiceTest.java index c32f8ca3c7..3cac67d858 100644 --- a/Ghidra/Features/PDB/src/test/java/pdb/symbolserver/SymbolServerServiceTest.java +++ b/Ghidra/Features/PDB/src/test/java/pdb/symbolserver/SymbolServerServiceTest.java @@ -4,9 +4,9 @@ * 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. @@ -15,16 +15,14 @@ */ package pdb.symbolserver; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; +import static org.junit.Assert.*; import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; import org.junit.Before; import org.junit.Test; @@ -128,14 +126,14 @@ public class SymbolServerServiceTest extends AbstractGenericTest { } @Test - public void test_Remote() throws IOException, CancelledException { + public void test_Trusted() throws IOException, CancelledException { String payload = "testdummy"; SymbolServerService symbolServerService = new SymbolServerService(localSymbolStore1, List.of(localSymbolStore2, new DummySymbolServer(payload))); SymbolFileInfo searchPdb = SymbolFileInfo.fromValues("file1.pdb", "11223344", 0); List results = - symbolServerService.find(searchPdb, FindOption.of(FindOption.ALLOW_REMOTE), + symbolServerService.find(searchPdb, FindOption.of(FindOption.ALLOW_UNTRUSTED), TaskMonitor.DUMMY); assertEquals(1, results.size()); @@ -148,8 +146,9 @@ public class SymbolServerServiceTest extends AbstractGenericTest { @Test public void test_NoRemote() throws CancelledException { String payload = "testdummy"; + DummySymbolServer dummySymbolServer = new DummySymbolServer(payload); SymbolServerService symbolServerService = - new SymbolServerService(localSymbolStore1, List.of(new DummySymbolServer(payload))); + new SymbolServerService(localSymbolStore1, List.of(dummySymbolServer)); SymbolFileInfo searchPdb = SymbolFileInfo.fromValues("file1.pdb", "11223344", 0); List results = symbolServerService.find(searchPdb, FindOption.NO_OPTIONS, TaskMonitor.DUMMY); diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/PdbScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/PdbScreenShots.java index d9127a6c9f..e3f3cd46fa 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/PdbScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/PdbScreenShots.java @@ -4,9 +4,9 @@ * 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. @@ -31,8 +31,7 @@ import ghidra.framework.options.Options; import ghidra.program.model.listing.Program; import pdb.PdbPlugin; import pdb.symbolserver.*; -import pdb.symbolserver.ui.ConfigPdbDialog; -import pdb.symbolserver.ui.LoadPdbDialog; +import pdb.symbolserver.ui.*; public class PdbScreenShots extends GhidraScreenShotGenerator { @@ -64,7 +63,7 @@ public class PdbScreenShots extends GhidraScreenShotGenerator { } @Test - public void testSymbolServerConfig_Screenshot() throws IOException { + public void testSymbolServerConfig_Screenshot() { PdbPlugin.saveSymbolServerServiceConfig(null); ConfigPdbDialog configPdbDialog = new ConfigPdbDialog(); showDialogWithoutBlocking(tool, configPdbDialog); @@ -79,7 +78,7 @@ public class PdbScreenShots extends GhidraScreenShotGenerator { LocalSymbolStore localSymbolStore1 = new LocalSymbolStore(localSymbolStore1Root); SameDirSymbolStore sameDirSymbolStore = new SameDirSymbolStore(null); List symbolServers = List.of(sameDirSymbolStore, - new HttpSymbolServer(URI.create("https://msdl.microsoft.com/download/symbols/"))); + HttpSymbolServer.createTrusted("https://msdl.microsoft.com/download/symbols/")); SymbolServerService symbolServerService = new SymbolServerService(localSymbolStore1, symbolServers); PdbPlugin.saveSymbolServerServiceConfig(symbolServerService); @@ -88,7 +87,7 @@ public class PdbScreenShots extends GhidraScreenShotGenerator { configPdbDialog.setSymbolServerService("/home/user/symbols", symbolServers); showDialogWithoutBlocking(tool, configPdbDialog); waitForSwing(); - captureDialog(ConfigPdbDialog.class, 410, 280); + captureDialog(ConfigPdbDialog.class, 520, 280); } @Test @@ -112,12 +111,29 @@ public class PdbScreenShots extends GhidraScreenShotGenerator { showDialogWithoutBlocking(tool, configPdbDialog); waitForSwing(); runSwing(() -> { + configPdbDialog.setWellknownSymbolServers(createFakeWellKnowns()); configPdbDialog.pushAddLocationButton(); }); waitForSwing(); captureMenu(); } + List createFakeWellKnowns() { + // due to module dependencies, this screen shot test can't see the contents of the normal + // PDBURL file loaded from the 'z public release' module. + return List.of( // should be same as the PDB_SYMBOL_SERVERS.PDBURL file + new WellKnownSymbolServerLocation("", "https://msdl.microsoft.com/download/symbols/", + "WARNING: Check your organization's security policy before downloading files from the internet.", + "screen shot"), + new WellKnownSymbolServerLocation("", + "https://chromium-browser-symsrv.commondatastorage.googleapis.com", + "WARNING: Check your organization's security policy before downloading files from the internet.", + "screen shot"), + new WellKnownSymbolServerLocation("", "https://symbols.mozilla.org/", + "WARNING: Check your organization's security policy before downloading files from the internet.", + "screen shot")); + } + @Test public void testLoadPdb_Advanced_NeedsConfig() { PdbPlugin.saveSymbolServerServiceConfig(null); @@ -158,7 +174,7 @@ public class PdbScreenShots extends GhidraScreenShotGenerator { localSymbolStore1, SymbolFileInfo.fromValues("HelloWorld.pdb", GUID1_STR, 2)), new SymbolFileLocation("HelloWorld.pdb", sameDirSymbolStoreWithFakePath, SymbolFileInfo.fromValues("HelloWorld.pdb", GUID1_STR, 1))); - Set findOptions = FindOption.of(FindOption.ALLOW_REMOTE, FindOption.ANY_AGE); + Set findOptions = FindOption.of(FindOption.ALLOW_UNTRUSTED, FindOption.ANY_AGE); runSwing(() -> { loadPdbDialog.setSearchOptions(findOptions); loadPdbDialog.setSearchResults(symbolFileLocations, findOptions); diff --git a/Ghidra/Test/IntegrationTest/src/test/java/pdb/symbolserver/SymbolServerService2Test.java b/Ghidra/Test/IntegrationTest/src/test/java/pdb/symbolserver/SymbolServerService2Test.java index 947d47b932..c0a7e18db3 100644 --- a/Ghidra/Test/IntegrationTest/src/test/java/pdb/symbolserver/SymbolServerService2Test.java +++ b/Ghidra/Test/IntegrationTest/src/test/java/pdb/symbolserver/SymbolServerService2Test.java @@ -4,9 +4,9 @@ * 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. @@ -99,7 +99,7 @@ public class SymbolServerService2Test extends AbstractGhidraHeadedIntegrationTes List results = symbolServerService.find(SymbolFileInfo.fromValues("test.pdb", "11223344", 1), - FindOption.of(FindOption.ALLOW_REMOTE), TaskMonitor.DUMMY); + FindOption.of(FindOption.ALLOW_UNTRUSTED), TaskMonitor.DUMMY); assertEquals(1, results.size()); System.out.println(results.get(0).getLocationStr()); @@ -118,7 +118,7 @@ public class SymbolServerService2Test extends AbstractGhidraHeadedIntegrationTes List results = symbolServerService.find(SymbolFileInfo.fromValues("test.pdb", "11223344", 1), - FindOption.of(FindOption.ALLOW_REMOTE), TaskMonitor.DUMMY); + FindOption.of(FindOption.ALLOW_UNTRUSTED), TaskMonitor.DUMMY); assertEquals(1, results.size()); System.out.println(results.get(0).getLocationStr());