GT-3547 - Patch dir fix - review fixes

This commit is contained in:
dragonmacher 2020-02-19 18:50:52 -05:00
parent 3dced733df
commit 87bda2b34d
14 changed files with 149 additions and 531 deletions

View file

@ -53,31 +53,6 @@
class files in that directory will be used (not jar files within that directory).
If the path is a jar file, then classes within the jar file will be used.
</P>
<P>
The <I>User Plugin Jar Directory</I> shows the directory that contains jar files to
search.
<P>
<IMG SRC="images/note.png" />
In addition to the above, Ghidra also searches in the installation directory, in the
<code>&lt;home&gt;/.ghidra/.ghidra-&lt;version&gt;/plugins</code> directory, if it exists.
</P>
<P>
The directories noted above, as well as any found jar files, are added to Ghidra's
classpath. The search order of these paths is:<A name="SearchOrder"></A></P>
</P>
<BLOCKQUOTE>
<OL>
<LI>Jar files in <I>User Plugin Jar Directory</I> (Plugin Path preference)</LI>
<LI>Jar files in the Ghidra <code>plugins</code> installation directory</LI>
<LI><I>User Plugin Paths</I> from the Plugin Paths preference</LI>
</OL>
</BLOCKQUOTE>
<H2>Editing Plugin Paths</H2>
@ -142,29 +117,6 @@
class that is loaded is the one that you will be using when you run Ghidra.&nbsp;</P>
</BLOCKQUOTE>
<H3>Set the User Plugin Jar Directory</H3>
<UL>
<LI>To set the User Plugin Jar Directory,</LI>
</UL>
<BLOCKQUOTE>
<OL>
<LI>Enter the absolute directory path in the <I>User Plugin Jar Directory</I> field, OR
click on the <B>...</B> button to choose a directory from the file system.</LI>
<LI>
Select the <B>Apply or OK</B> button<B>.</B>
<UL>
<LI><B>Apply</B> applies the changes and leaves the dialog up.</LI>
<LI><B>OK</B> applies the changes and dismisses the dialog.&nbsp;</LI>
</UL>
</LI>
</OL>
</BLOCKQUOTE>
<H3>Remove Paths</H3>
<UL>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -20,7 +20,6 @@ import java.util.List;
import generic.jar.ResourceFile;
import ghidra.GhidraClassLoader;
import ghidra.GhidraLauncher;
import ghidra.framework.preferences.Preferences;
import ghidra.net.ApplicationTrustManagerFactory;
import ghidra.util.Msg;
@ -62,22 +61,8 @@ public class HeadlessGhidraApplicationConfiguration extends ApplicationConfigura
if (!(ClassLoader.getSystemClassLoader() instanceof GhidraClassLoader)) {
return;
}
GhidraClassLoader loader = (GhidraClassLoader) ClassLoader.getSystemClassLoader();
// Add user jars
String userJarDir = Preferences.getProperty(Preferences.USER_PLUGIN_JAR_DIRECTORY);
if (userJarDir != null) {
GhidraLauncher.findJarsInDir(new ResourceFile(userJarDir)).forEach(
p -> loader.addPath(p));
}
// Add plugins from user settings directory
String userSettingsPath = Application.getUserSettingsDirectory().getAbsolutePath();
String pluginPath = userSettingsPath + File.separatorChar + "plugins";
loader.addPath(pluginPath);
GhidraLauncher.findJarsInDir(new ResourceFile(pluginPath)).forEach(p -> loader.addPath(p));
// Add user plugins
for (String path : Preferences.getPluginPaths()) {
loader.addPath(path);
}

View file

@ -40,11 +40,6 @@ public class Preferences {
*/
private final static String USER_PLUGIN_PATH = "UserPluginPath";
/**
* Preference name of the user plugin jar directory.
*/
public final static String USER_PLUGIN_JAR_DIRECTORY = "UserPluginJarDirectory";
/**
* Preference name for the last opened archive directory.
*/
@ -194,6 +189,8 @@ public class Preferences {
* <p>
* Note: all <code>getProperty(...)</code> methods will first check {@link System#getProperty(String)}
* for a value first. This allows users to override preferences from the command-line.
* @param name the property name
* @return the current property value; null if not set
*/
public static String getProperty(String name) {
// prefer system properties, which enables uses to override preferences from the command-line
@ -210,6 +207,9 @@ public class Preferences {
* <p>
* Note: all <code>getProperty(...)</code> methods will first check {@link System#getProperty(String)}
* for a value first. This allows users to override preferences from the command-line.
* @param name the property name
* @param defaultValue the default value
* @return the property value; default value if not set
*
* @see #getProperty(String, String, boolean)
*/
@ -289,6 +289,7 @@ public class Preferences {
/**
* Get the filename that will be used in the store() method.
* @return the filename
*/
public static String getFilename() {
return filename;
@ -297,7 +298,7 @@ public class Preferences {
/**
* Set the filename so that when the store() method is called, the
* preferences are written to this file.
* @param name
* @param name the filename
*/
public static void setFilename(String name) {
filename = name;
@ -305,6 +306,7 @@ public class Preferences {
/**
* Store the preferences in a file for the current filename.
* @return true if the file was written
* @throws RuntimeException if the preferences filename was not set
*/
public static boolean store() {
@ -346,6 +348,7 @@ public class Preferences {
/**
* Return the paths in the UserPluginPath property.
* Return zero length array if this property is not set.
* @return the paths
*
*/
public static String[] getPluginPaths() {
@ -359,6 +362,7 @@ public class Preferences {
/**
* Set the paths to be used as the UserPluginPath property.
* @param paths the paths
*/
public static void setPluginPaths(String[] paths) {
if (paths == null || paths.length == 0) {
@ -376,55 +380,6 @@ public class Preferences {
properties.setProperty(USER_PLUGIN_PATH, sb.toString());
}
/**
* Set the plugin path property.
* @param pathProperty A string of paths separated by {@link File#pathSeparator} characters
*/
public static void setPluginPathProperty(String pathProperty) {
properties.setProperty(USER_PLUGIN_PATH, pathProperty);
}
/**
* Append path to the plugin path.
* @param path the plugin path to add
*/
public static void addPluginPath(String path) {
List<String> list = getPluginPathList();
if (list == null) {
setPluginPaths(new String[] { path });
return;
}
if (!list.contains(path)) {
list.add(path);
String[] p = new String[list.size()];
setPluginPaths(list.toArray(p));
}
}
/**
* Append paths to the plugin path.
* @param paths the plugin paths to add
*/
public static void addPluginPaths(String[] paths) {
List<String> list = getPluginPathList();
if (list == null) {
setPluginPaths(paths);
return;
}
boolean listChanged = false;
for (String path : paths) {
if (!list.contains(path)) {
list.add(path);
listChanged = true;
}
}
// update plugin path property only if we added a path to the list
if (listChanged) {
String[] p = new String[list.size()];
setPluginPaths(list.toArray(p));
}
}
private static List<String> getPluginPathList() {
String path = properties.getProperty(USER_PLUGIN_PATH);
if (path == null) {

View file

@ -17,8 +17,7 @@ package ghidra.util.classfinder;
import java.io.File;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Set;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
@ -28,7 +27,9 @@ import org.apache.commons.io.FilenameUtils;
import generic.jar.ResourceFile;
import ghidra.framework.Application;
import ghidra.framework.preferences.Preferences;
import ghidra.util.Msg;
import ghidra.util.SystemUtilities;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
import utility.application.ApplicationLayout;
@ -46,20 +47,13 @@ class ClassJar extends ClassLocation {
Pattern.compile(".*/(.*)/(?:lib|build/libs)/(.+).jar");
private static final String PATCH_DIR_PATH_FORWARD_SLASHED = getPatchDirPath();
private static String getPatchDirPath() {
ApplicationLayout layout = Application.getApplicationLayout();
ResourceFile installDir = layout.getApplicationInstallationDir();
ResourceFile patchDir = new ResourceFile(installDir, "Ghidra/patch");
String patchPath = patchDir.getAbsolutePath();
String forwardSlashed = patchPath.replaceAll("\\\\", "/");
return forwardSlashed;
}
private static final Set<String> USER_PLUGIN_PATHS = loadUserPluginPaths();
private String path;
ClassJar(String path, TaskMonitor monitor) throws CancelledException {
this.path = path;
loadUserPluginPaths();
scanJar(monitor);
}
@ -105,10 +99,22 @@ class ClassJar extends ClassLocation {
if (pathName.contains("ExternalLibraries")) {
return true;
}
//
// Dev and Production Mode
//
String forwardSlashedPathName = pathName.replaceAll("\\\\", "/");
if (isUserPluginJar(forwardSlashedPathName)) {
return false;
}
if (SystemUtilities.isInDevelopmentMode()) {
return false;
}
//
// Production Mode - allow users to enter code in the 'patch' directory
//
String forwardSlashedPathName = pathName.replaceAll("\\\\", "/");
if (isPatchJar(forwardSlashedPathName)) {
return false;
}
@ -123,6 +129,10 @@ class ClassJar extends ClassLocation {
return true;
}
private static boolean isUserPluginJar(String pathName) {
return USER_PLUGIN_PATHS.contains(pathName);
}
// Note: the path is expected to be using forward slashes
private static boolean isPatchJar(String pathName) {
String jarDirectory = FilenameUtils.getFullPathNoEndSeparator(pathName);
@ -168,4 +178,27 @@ class ClassJar extends ClassLocation {
public String toString() {
return path;
}
private static String getPatchDirPath() {
ApplicationLayout layout = Application.getApplicationLayout();
ResourceFile patchDir = layout.getPatchDir();
if (patchDir == null) {
return "<no patch dir>"; // not in a distribution
}
String patchPath = patchDir.getAbsolutePath();
String forwardSlashed = patchPath.replaceAll("\\\\", "/");
return forwardSlashed;
}
private static Set<String> loadUserPluginPaths() {
Set<String> result = new HashSet<>();
String[] paths = Preferences.getPluginPaths();
for (String pathName : paths) {
// note: lower case because our client uses lower case for paths
String forwardSlashed = pathName.replaceAll("\\\\", "/").toLowerCase();
result.add(forwardSlashed);
}
return Collections.unmodifiableSet(result);
}
}

View file

@ -1,205 +0,0 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.framework.main;
import java.awt.BorderLayout;
import java.awt.event.*;
import java.io.File;
import javax.swing.*;
import docking.DockingUtils;
import docking.options.editor.ButtonPanelFactory;
import docking.widgets.filechooser.GhidraFileChooser;
/**
* Helper class that restricts the width of the textField to the size of the
* scrolled paths list; also provides the listener for the textfield if user
* presses Enter or Tab in a textfield.
*
*/
class BrowsePathPanel extends JPanel {
private boolean changed;
private GhidraFileChooser fileChooser;
private JTextField pathTextField;
private EditPluginPathDialog dialog;
private JButton browseButton;
/**
* Construct a new BrowsePathPanel.
* @param editDialog parent dialog
* @param sizeComp component to use for size in creating text field
* @param button browse button
* @param dirOnly
* @param textFieldLabel
* @param fieldName name of text field component
*/
BrowsePathPanel(EditPluginPathDialog editDialog, ActionListener buttonListener, String fieldName) {
super();
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
dialog = editDialog;
create(fieldName);
addListeners(buttonListener);
}
/**
* Create the components
* @param sizeComp component to use when creating the text field to get the
* size
* @param textFieldLabel label for the field
*/
private void create(String fieldName) {
pathTextField = new JTextField();
pathTextField.setName(fieldName);
pathTextField.setEditable(false);
pathTextField.setBackground(getBackground());
browseButton = ButtonPanelFactory.createButton(ButtonPanelFactory.BROWSE_TYPE);
browseButton.setToolTipText("Choose Directory");
// construct the panel with text field and browse button
JPanel browsePathPanel = new JPanel(new BorderLayout(5, 5));
browsePathPanel.add(pathTextField, BorderLayout.CENTER);
browsePathPanel.add(browseButton, BorderLayout.EAST);
add(browsePathPanel);
}
private void createFileChooser() {
// create the fileChooser this panel will use based on its input criteria
fileChooser = new GhidraFileChooser(dialog.getComponent());
fileChooser.setCurrentDirectory(new File(System.getProperty("user.home")));
fileChooser.setFileSelectionMode(GhidraFileChooser.DIRECTORIES_ONLY);
fileChooser.setApproveButtonToolTipText("Choose Directory With Plugin JAR Files");
fileChooser.setApproveButtonText("Choose JAR Directory");
}
/**
* Add listeners.
* @param listener listener for the browse button
*/
private void addListeners(ActionListener listener) {
browseButton.addActionListener(listener);
pathTextField.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
// when Esc or Ctrl-C is pressed, reset the plugin
// jar directory to what is saved in preferences
if (keyCode == KeyEvent.VK_ESCAPE ||
(DockingUtils.isControlModifier(e) && keyCode == KeyEvent.VK_C)) {
dialog.initJarDirectory();
}
else {
dialog.setApplyEnabled(true);
}
}
});
}
String getPath() {
return pathTextField.getText().trim();
}
boolean isChanged() {
return changed;
}
@Override
public boolean hasFocus() {
return pathTextField.hasFocus();
}
@Override
public void requestFocus() {
pathTextField.requestFocus();
pathTextField.selectAll();
}
/**
* Pop up the file chooser.
*/
void showFileChooser() {
if (fileChooser == null) {
createFileChooser();
}
// reset the status message
dialog.setStatusMessage(EditPluginPathDialog.EMPTY_STATUS);
File pluginFile = fileChooser.getSelectedFile();
if (pluginFile != null) {
setPath(pluginFile);
}
else {
pathTextField.requestFocus();
pathTextField.selectAll();
}
}
/**
* Set whether something has changed.
* @param changed true if something changed
*/
void setChanged(boolean changed) {
this.changed = changed;
}
/**
* Set the path field.
* @param path filename for the path field
* @return boolean true if the path is valid
*/
private boolean setPath(File path) {
boolean pathOK = false;
dialog.setStatusMessage(EditPluginPathDialog.EMPTY_STATUS);
if (!path.canRead()) {
pathTextField.selectAll();
dialog.setStatusMessage("Cannot read path: " + path.toString());
}
else {
pathTextField.setText(path.getAbsolutePath());
pathOK = (pathTextField.getText().trim().length() > 0);
}
if (pathOK) {
dialog.setStatusMessage("Press Apply or OK to set JAR directory.");
}
changed = changed || pathOK;
dialog.enableApply();
return pathOK;
}
/**
* sets the text of the text field of the panel without
* any error checking
*/
void setText(String text) {
pathTextField.setText(text);
}
}

View file

@ -18,8 +18,6 @@
package ghidra.framework.main;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
@ -34,6 +32,7 @@ import javax.swing.event.ListSelectionListener;
import docking.DialogComponentProvider;
import docking.options.editor.ButtonPanelFactory;
import docking.widgets.filechooser.GhidraFileChooser;
import docking.widgets.filechooser.GhidraFileChooserMode;
import docking.widgets.label.GDLabel;
import docking.widgets.list.GListCellRenderer;
import ghidra.framework.plugintool.PluginTool;
@ -45,20 +44,18 @@ import ghidra.util.filechooser.GhidraFileFilter;
/**
* Dialog for editing the Plugin path and Jar directory path preferences.
*
* <p>The Plugin Path and Jar directory path are locations where Ghidra searches
* for plugins to load. The Plugin Path is specified exactly as a Java Classpath
* is specified. The Jar directory is searched only for Jar files containing
* Plugins. When changes are made to these fields in the dialog, the
* is specified. When changes are made to these fields in the dialog, the
* preferences file is updated and written to disk. The preferences file is
* located in the .ghidra directory in the user's home directory.
* </P>
* <p> The preferences file also contains the last project that was opened,
* and the positions of the Ghidra Project Window and other tools that were
* running when the user last exited Ghidra.
* </P>
*
*/
class EditPluginPathDialog extends DialogComponentProvider {
static final String ADD_DIR_BUTTON_TEXT = "Add Dir ...";
static final String ADD_JAR_BUTTON_TEXT = "Add Jar ...";
private final static int SIDE_MARGIN = 5;
private final static Color INVALID_PATH_COLOR = Color.red.brighter();
private final static Color INVALID_SELECTED_PATH_COLOR = Color.pink;
@ -80,7 +77,6 @@ class EditPluginPathDialog extends DialogComponentProvider {
// gui members needed for dis/enabling and other state-dependent actions
private JScrollPane scrollPane; // need for preferred size when resizing
private JList<String> pluginPathsList;
private BrowsePathPanel jarPathPanel;
private GhidraFileChooser fileChooser;
private JButton upButton;
private JButton downButton;
@ -94,7 +90,6 @@ class EditPluginPathDialog extends DialogComponentProvider {
* Creates a non-modal dialog with OK, Apply, Cancel buttons.
* The OK and Apply buttons will be enabled when user makes unapplied
* changes to the UserPluginPath or UserPluginJarDirectory property values.
* @param parent parent to this dialog
*/
EditPluginPathDialog() {
super("Edit Plugin Path", true, false, true, false);
@ -134,8 +129,6 @@ class EditPluginPathDialog extends DialogComponentProvider {
// subsequent panels
mainPanel.add(buildPluginPathsPanel());
mainPanel.add(Box.createVerticalStrut(10));
mainPanel.add(buildJarDirectoryPanel());
mainPanel.add(Box.createVerticalStrut(10));
mainPanel.add(Box.createVerticalGlue());
mainPanel.add(statusMessagePanel);
mainPanel.invalidate();
@ -147,33 +140,15 @@ class EditPluginPathDialog extends DialogComponentProvider {
return mainPanel;
}
/**
* Gets called when the user selects Apply
*/
@Override
protected void applyCallback() {
// validate the jar path before applying changes, since the user
// is pressing the Apply button to save this setting
String jarPathname = jarPathPanel.getPath();
if (jarPathname.length() > 0) {
File jarPath = new File(jarPathname);
if (!jarPath.isDirectory() || !jarPath.canRead()) {
setStatusMessage("Bad Jar Directory: " + jarPathname);
jarPathPanel.requestFocus();
return;
}
}
// do the things we need to do to handle the applied changes
handleApply();
}
/**
* Gets called when the user selects Cancel
*/
@Override
protected void cancelCallback() {
close();
// reset original state of dialog for next display of dialog
enableButtons(false);
setStatusMessage(EMPTY_STATUS);
@ -181,17 +156,6 @@ class EditPluginPathDialog extends DialogComponentProvider {
errorMsg = null;
}
/**
* if the jar directory field has focus, don't let the base dialog
* handle it.
*/
@Override
protected void escapeCallback() {
if (!jarPathPanel.hasFocus()) {
super.escapeCallback();
}
}
/**
* Gets called when the user selects Ok
*/
@ -206,30 +170,21 @@ class EditPluginPathDialog extends DialogComponentProvider {
}
/**
* re-set the list of paths each time the dialog is shown
* Reset the list of paths each time the dialog is shown
* @param tool the tool
*/
public void show(PluginTool tool) {
setPluginPathsListData(Preferences.getPluginPaths());
initJarDirectory();
setApplyEnabled(pluginPathsChanged);
setStatusMessage(EMPTY_STATUS);
// setting the path enables the apply, but we know we haven't
// made any changes yet, so disable
setApplyEnabled(false);
tool.showDialog(this);
}
/**
* Method enableApply.
*/
void enableApply() {
setApplyEnabled(pluginPathsChanged || jarPathPanel.isChanged());
}
void initJarDirectory() {
setApplyEnabled(pluginPathsChanged);
setStatusMessage(EMPTY_STATUS);
}
void setStatusMessage(String msg) {
private void setStatusMessage(String msg) {
if (msg == null || msg.length() == 0) {
msg = EMPTY_STATUS;
}
@ -237,15 +192,7 @@ class EditPluginPathDialog extends DialogComponentProvider {
statusMessage.invalidate();
}
/**
* @see ghidra.util.bean.GhidraDialog#setApplyEnabled(boolean)
*/
@Override
protected void setApplyEnabled(boolean state) {
super.setApplyEnabled(state);
}
void addJarCallback() {
private void addJarCallback() {
setStatusMessage(EditPluginPathDialog.EMPTY_STATUS);
@ -253,7 +200,7 @@ class EditPluginPathDialog extends DialogComponentProvider {
fileChooser = new GhidraFileChooser(getComponent());
fileChooser.setCurrentDirectory(new File(System.getProperty("user.home")));
}
fileChooser.setFileSelectionMode(GhidraFileChooser.FILES_ONLY);
fileChooser.setFileSelectionMode(GhidraFileChooserMode.FILES_ONLY);
fileChooser.setFileFilter(JAR_FILTER);
fileChooser.setApproveButtonToolTipText("Choose Plugin Jar File");
fileChooser.setApproveButtonText("Add Jar File");
@ -277,7 +224,7 @@ class EditPluginPathDialog extends DialogComponentProvider {
}
}
void addDirCallback() {
private void addDirCallback() {
setStatusMessage(EditPluginPathDialog.EMPTY_STATUS);
@ -285,7 +232,7 @@ class EditPluginPathDialog extends DialogComponentProvider {
fileChooser = new GhidraFileChooser(getComponent());
fileChooser.setCurrentDirectory(new File(System.getProperty("user.home")));
}
fileChooser.setFileSelectionMode(GhidraFileChooser.DIRECTORIES_ONLY);
fileChooser.setFileSelectionMode(GhidraFileChooserMode.DIRECTORIES_ONLY);
fileChooser.setFileFilter(GhidraFileFilter.ALL);
fileChooser.setApproveButtonToolTipText("Choose Directory with Plugin class Files");
fileChooser.setApproveButtonText("Add Directory");
@ -310,63 +257,31 @@ class EditPluginPathDialog extends DialogComponentProvider {
}
}
/**
* Returns an array of pathnames where plugins can be found; used by custom
* class loader when searching for plugins.
*/
private String[] getUserPluginPaths() {
String[] pluginsArray = new String[listModel.size()];
listModel.copyInto(pluginsArray);
return pluginsArray;
}
/**
* construct the plugin paths button panel
*/
private JPanel buildPluginPathsPanel() {
// create the UP and DOWN arrows panel
upButton = ButtonPanelFactory.createButton(ButtonPanelFactory.ARROW_UP_TYPE);
upButton.setName("UpArrow");
upButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
handleSelection(UP);
}
});
upButton.addActionListener(e -> handleSelection(UP));
downButton = ButtonPanelFactory.createButton(ButtonPanelFactory.ARROW_DOWN_TYPE);
downButton.setName("DownArrow");
downButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
handleSelection(DOWN);
}
});
downButton.addActionListener(e -> handleSelection(DOWN));
JPanel arrowButtonsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 10));
arrowButtonsPanel.add(upButton);
arrowButtonsPanel.add(downButton);
// create the Add and Remove panel
JButton addJarButton = ButtonPanelFactory.createButton("Add Jar...");
addJarButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
addJarCallback();
}
});
JButton addDirButton = ButtonPanelFactory.createButton("Add Dir...");
addDirButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
addDirCallback();
}
});
JButton addJarButton = ButtonPanelFactory.createButton(ADD_JAR_BUTTON_TEXT);
addJarButton.addActionListener(e -> addJarCallback());
JButton addDirButton = ButtonPanelFactory.createButton(ADD_DIR_BUTTON_TEXT);
addDirButton.addActionListener(e -> addDirCallback());
removeButton = ButtonPanelFactory.createButton("Remove");
removeButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
handleSelection(REMOVE);
}
});
removeButton.addActionListener(e -> handleSelection(REMOVE));
Dimension d = addJarButton.getPreferredSize();
addDirButton.setPreferredSize(d);
removeButton.setPreferredSize(d);
@ -415,26 +330,6 @@ class EditPluginPathDialog extends DialogComponentProvider {
return pluginPathListPanel;
}
/**
* construct the jar directory panel
*/
private JPanel buildJarDirectoryPanel() {
ActionListener listener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
jarPathPanel.showFileChooser();
enableApply();
}
};
jarPathPanel = new BrowsePathPanel(this, listener, "UserPluginJarDirectory");
jarPathPanel.setText(Preferences.getProperty(Preferences.USER_PLUGIN_JAR_DIRECTORY));
jarPathPanel.setBorder(new TitledBorder("User Plugin Jar Directory"));
return jarPathPanel;
}
private void enableButtons(boolean enabled) {
upButton.setEnabled(enabled);
downButton.setEnabled(enabled);
@ -452,22 +347,12 @@ class EditPluginPathDialog extends DialogComponentProvider {
// update Ghidra Preferences with new paths
Preferences.setPluginPaths(userPluginPaths);
// Get user Jar directory
String jarDirectoryName = jarPathPanel.getPath();
if (jarDirectoryName.trim().length() == 0) {
jarDirectoryName = null;
}
// update Ghidra Preferences with new Jar path
Preferences.setProperty(Preferences.USER_PLUGIN_JAR_DIRECTORY, jarDirectoryName);
errorMsg = null;
// save the new values
if (Preferences.store()) {
setStatusMessage("Saved plugin paths successfully!");
// indicate to user all changes have been applied
setApplyEnabled(false);
jarPathPanel.setChanged(false);
Msg.showInfo(getClass(), rootPanel, "Restart Ghidra",
"You must restart Ghidra in order\n" + "for path changes to take effect.");
@ -479,10 +364,6 @@ class EditPluginPathDialog extends DialogComponentProvider {
}
}
/**
* dispatched method for handling button actions on the
* dialog
*/
private void handleSelection(byte whichAction) {
// if nothing selected, nothing to do
if (selectedInList == null) {
@ -574,8 +455,8 @@ class EditPluginPathDialog extends DialogComponentProvider {
private void setPluginPathsListData(String[] pluginPathNames) {
listModel.clear();
for (int p = 0; p < pluginPathNames.length; p++) {
listModel.addElement(pluginPathNames[p]);
for (String pluginPathName : pluginPathNames) {
listModel.addElement(pluginPathName);
}
}

View file

@ -64,6 +64,9 @@ public class GhidraApplicationLayout extends ApplicationLayout {
// Extensions
extensionInstallationDir = findExtensionInstallationDirectory();
extensionArchiveDir = findExtensionArchiveDirectory();
// Patch directory
patchDir = findPatchDirectory();
}
/**
@ -142,7 +145,7 @@ public class GhidraApplicationLayout extends ApplicationLayout {
// Find standard module root directories from within the application root directories
Collection<ResourceFile> moduleRootDirectories =
ModuleUtilities.findModuleRootDirectories(applicationRootDirs, new ArrayList<>());
// Examine the classpath to look for modules outside of the application root directories.
// These might exist if Ghidra was launched from an Eclipse project that resides
// external to the Ghidra installation.
@ -156,8 +159,9 @@ public class GhidraApplicationLayout extends ApplicationLayout {
// Skip classpath entries that live in an application root directory...we've already
// found those.
if (applicationRootDirs.stream().anyMatch(dir -> FileUtilities.isPathContainedWithin(
dir.getFile(false), classpathEntry.getFile(false)))) {
if (applicationRootDirs.stream()
.anyMatch(dir -> FileUtilities.isPathContainedWithin(
dir.getFile(false), classpathEntry.getFile(false)))) {
continue;
}
@ -173,6 +177,24 @@ public class GhidraApplicationLayout extends ApplicationLayout {
return ModuleUtilities.findModules(applicationRootDirs, moduleRootDirectories);
}
/**
* Returns the directory that allows users to add jar and class files to override existing
* distribution files
* @return the patch dir; null if not in a distribution
*/
protected ResourceFile findPatchDirectory() {
if (SystemUtilities.isInDevelopmentMode()) {
return null;
}
if (applicationInstallationDir == null) {
return null;
}
return new ResourceFile(applicationInstallationDir, "Ghidra/patch");
}
/**
* Returns the directory where all Ghidra extension archives are stored.
* This should be at the following location:<br>

View file

@ -31,7 +31,7 @@ import ghidra.util.Msg;
*
*/
public class GhidraClassLoader extends URLClassLoader {
private static final String CP = "java.class.path";
/**
@ -45,7 +45,7 @@ public class GhidraClassLoader extends URLClassLoader {
}
@Override
public void addURL(URL url) {
public void addURL(URL url) {
super.addURL(url);
try {
System.setProperty(CP,
@ -56,23 +56,6 @@ public class GhidraClassLoader extends URLClassLoader {
}
}
/**
* Places the given path first in the classpath
* @param path the path
*/
void prependPath(String path) {
try {
URL url = new File(path).toURI().toURL();
super.addURL(url);
File file = new File(url.toURI());
System.setProperty(CP, file + File.pathSeparator + System.getProperty(CP));
}
catch (MalformedURLException | URISyntaxException e) {
Msg.debug(this, "Invalid path: " + path, e);
}
}
/**
* Converts the specified path to a {@link URL} and adds it to the classpath.
*

View file

@ -53,7 +53,7 @@ public class GhidraLauncher {
GhidraClassLoader loader = (GhidraClassLoader) ClassLoader.getSystemClassLoader();
// Build the classpath
List<String> classpathList = new ArrayList<String>();
List<String> classpathList = new ArrayList<>();
Map<String, GModule> modules = getOrderedModules(layout);
if (SystemUtilities.isInDevelopmentMode()) {
@ -61,7 +61,7 @@ public class GhidraLauncher {
addExternalJarPaths(classpathList, layout.getApplicationRootDirs());
}
else {
addPatchJarPaths(loader, layout.getApplicationInstallationDir());
addPatchPaths(classpathList, layout.getPatchDir());
addModuleJarPaths(classpathList, modules);
}
classpathList = orderClasspath(classpathList, modules);
@ -83,28 +83,24 @@ public class GhidraLauncher {
}
/**
* Add patch dir and jars to the given path list. This should be done first so they take
* precedence in the classpath.
* Add patch jars to the given path list. This should be done first so they take precedence in
* the classpath.
*
* @param loader The loader to which paths will be added.
* @param installDir The application installation directory.
* @param pathList The list of paths to add to
* @param patchDir The application installation directory
*/
private static void addPatchJarPaths(GhidraClassLoader loader, ResourceFile installDir) {
ResourceFile patchDir = new ResourceFile(installDir, "Ghidra/patch");
private static void addPatchPaths(List<String> pathList, ResourceFile patchDir) {
if (!patchDir.exists()) {
return;
}
List<String> patchJars = findJarsInDir(patchDir);
Collections.sort(patchJars);
// this will allow for unbundled class files
pathList.add(patchDir.getAbsolutePath());
// add in reverse order, since we are prepending
for (int i = patchJars.size() - 1; i >= 0; i--) {
loader.prependPath(patchJars.get(i));
}
// put last; paths are prepended in list order
loader.prependPath(patchDir.getAbsolutePath());
// this is each jar file, sorted for loading consistency
List<String> jars = findJarsInDir(patchDir);
Collections.sort(jars);
pathList.addAll(jars);
}
/**
@ -276,7 +272,7 @@ public class GhidraLauncher {
.flatMap(m -> m.getFatJars().stream())
.collect(Collectors.toSet());
List<String> orderedList = new ArrayList<String>(pathList);
List<String> orderedList = new ArrayList<>(pathList);
for (String path : pathList) {
if (fatJars.contains(new File(path).getName())) {

View file

@ -55,4 +55,10 @@ public class GhidraTestApplicationLayout extends GhidraApplicationLayout {
File installDir = new File(getUserTempDir(), "ExtensionInstallDir");
return new ResourceFile(installDir);
}
@Override
protected ResourceFile findPatchDirectory() {
File dir = new File(getUserTempDir(), "patch");
return new ResourceFile(dir);
}
}

View file

@ -42,6 +42,7 @@ public abstract class ApplicationLayout {
protected File userTempDir;
protected File userCacheDir;
protected File userSettingsDir;
protected ResourceFile patchDir;
protected ResourceFile extensionArchiveDir;
protected ResourceFile extensionInstallationDir;
@ -109,10 +110,10 @@ public abstract class ApplicationLayout {
}
/**
* Returns the directory where archived Ghidra Extensions are stored.
* Returns the directory where archived application Extensions are stored.
*
* @return The Ghidra Extensions archive directory. Could be null if the
* {@link ApplicationLayout} does not support Ghidra Extensions.
* @return the application Extensions archive directory. Could be null if the
* {@link ApplicationLayout} does not support application Extensions.
*
*/
public final ResourceFile getExtensionArchiveDir() {
@ -120,15 +121,24 @@ public abstract class ApplicationLayout {
}
/**
* Returns the Ghidra Extensions installation folder.
* Returns the application Extensions installation folder.
*
* @return The Ghidra Extensions installation directory. Could be null if the
* {@link ApplicationLayout} does not support Ghidra Extensions.
* @return the application Extensions installation directory. Could be null if the
* {@link ApplicationLayout} does not support application Extensions.
*/
public final ResourceFile getExtensionInstallationDir() {
return extensionInstallationDir;
}
/**
* Returns the location of the application patch directory. The patch directory can be
* used to modify existing code within a distribution.
* @return the patch directory; may be null
*/
public final ResourceFile getPatchDir() {
return patchDir;
}
/**
* Creates the application's user directories (or ensures they already exist).
*

View file

@ -255,7 +255,6 @@ public class FrontEndPluginScreenShots extends GhidraScreenShotGenerator {
@Test
public void testEditPluginPath() {
Preferences.setProperty(Preferences.USER_PLUGIN_JAR_DIRECTORY, "/MyPlugins");
Preferences.setPluginPaths(new String[] { "/myJar.jar", "/MyPlugins/classes" });
performAction("Edit Plugin Path", "FrontEndPlugin", false);
DialogComponentProvider dialog = getDialog();

View file

@ -1,6 +1,7 @@
Into this directory may be added compiled Java class files, either inside of a jar file or
in a directory structure. This directory and the contained jar files will be prepended to
the classpath, allowing them to override any existing classes in any module.
Compiled Java class files, either inside of a jar file or in a directory structure may be inserted
into this directory. This directory and the contained jar files will be prepended to
the classpath, allowing them to override any existing classes in any module (except those from
the Utility module).
The jar files will be sorted by name before being added to the classpath in order to present
predictable class loading between Ghidra runs. This directory will be prepended on the classpath