From dcc56457ea58347cf232fc056159fa9a736aece2 Mon Sep 17 00:00:00 2001 From: dev747368 <48332326+dev747368@users.noreply.github.com> Date: Mon, 12 Aug 2024 12:49:58 -0400 Subject: [PATCH] GP-4735 refactor pdb symbol server 'remote' to 'untrusted' Change name of symbolserver 'remote' property to 'untrusted' to reflectits intended usage.Add column in config table to allow user to toggle trusted status onhttp:// symbol servers (the only type that currently supports thisconcept) --- .../PdbSymbolServerExamplePrescript.java | 17 +- .../GetMSDownloadLinkScript.java | 13 +- .../app/plugin/core/analysis/PdbAnalyzer.java | 30 +- .../core/analysis/PdbAnalyzerCommon.java | 19 +- .../core/analysis/PdbUniversalAnalyzer.java | 30 +- .../ContainerFileSymbolServer.java | 9 +- .../symbolserver/DisabledSymbolServer.java | 9 +- .../java/pdb/symbolserver/FindOption.java | 12 +- .../pdb/symbolserver/HttpSymbolServer.java | 83 ++- .../pdb/symbolserver/LocalSymbolStore.java | 9 +- .../pdb/symbolserver/SameDirSymbolStore.java | 12 +- .../java/pdb/symbolserver/SymbolServer.java | 49 +- .../SymbolServerInstanceCreatorRegistry.java | 7 +- .../pdb/symbolserver/SymbolServerService.java | 34 +- .../pdb/symbolserver/ui/ConfigPdbDialog.java | 615 +++++++++++++++++- .../pdb/symbolserver/ui/LoadPdbDialog.java | 26 +- .../pdb/symbolserver/ui/SymbolFilePanel.java | 12 +- .../symbolserver/ui/SymbolServerPanel.java | 590 ----------------- .../pdb/symbolserver/ui/SymbolServerRow.java | 24 +- .../ui/SymbolServerTableModel.java | 111 ++-- .../ui/TableColumnInitializer.java | 64 ++ .../ui/WellKnownSymbolServerLocation.java | 94 ++- .../pdb/symbolserver/DummySymbolServer.java | 17 +- .../symbolserver/SymbolServerServiceTest.java | 21 +- .../java/help/screenshot/PdbScreenShots.java | 32 +- .../SymbolServerService2Test.java | 8 +- 26 files changed, 1057 insertions(+), 890 deletions(-) delete mode 100644 Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/SymbolServerPanel.java create mode 100644 Ghidra/Features/PDB/src/main/java/pdb/symbolserver/ui/TableColumnInitializer.java 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());