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). 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. If the path is a jar file, then classes within the jar file will be used.
</P> </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> <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> class that is loaded is the one that you will be using when you run Ghidra.&nbsp;</P>
</BLOCKQUOTE> </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> <H3>Remove Paths</H3>
<UL> <UL>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

View file

@ -20,7 +20,6 @@ import java.util.List;
import generic.jar.ResourceFile; import generic.jar.ResourceFile;
import ghidra.GhidraClassLoader; import ghidra.GhidraClassLoader;
import ghidra.GhidraLauncher;
import ghidra.framework.preferences.Preferences; import ghidra.framework.preferences.Preferences;
import ghidra.net.ApplicationTrustManagerFactory; import ghidra.net.ApplicationTrustManagerFactory;
import ghidra.util.Msg; import ghidra.util.Msg;
@ -62,22 +61,8 @@ public class HeadlessGhidraApplicationConfiguration extends ApplicationConfigura
if (!(ClassLoader.getSystemClassLoader() instanceof GhidraClassLoader)) { if (!(ClassLoader.getSystemClassLoader() instanceof GhidraClassLoader)) {
return; return;
} }
GhidraClassLoader loader = (GhidraClassLoader) ClassLoader.getSystemClassLoader(); 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()) { for (String path : Preferences.getPluginPaths()) {
loader.addPath(path); loader.addPath(path);
} }

View file

@ -40,11 +40,6 @@ public class Preferences {
*/ */
private final static String USER_PLUGIN_PATH = "UserPluginPath"; 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. * Preference name for the last opened archive directory.
*/ */
@ -194,6 +189,8 @@ public class Preferences {
* <p> * <p>
* Note: all <code>getProperty(...)</code> methods will first check {@link System#getProperty(String)} * 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. * 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) { public static String getProperty(String name) {
// prefer system properties, which enables uses to override preferences from the command-line // prefer system properties, which enables uses to override preferences from the command-line
@ -210,6 +207,9 @@ public class Preferences {
* <p> * <p>
* Note: all <code>getProperty(...)</code> methods will first check {@link System#getProperty(String)} * 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. * 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) * @see #getProperty(String, String, boolean)
*/ */
@ -289,6 +289,7 @@ public class Preferences {
/** /**
* Get the filename that will be used in the store() method. * Get the filename that will be used in the store() method.
* @return the filename
*/ */
public static String getFilename() { public static String getFilename() {
return filename; return filename;
@ -297,7 +298,7 @@ public class Preferences {
/** /**
* Set the filename so that when the store() method is called, the * Set the filename so that when the store() method is called, the
* preferences are written to this file. * preferences are written to this file.
* @param name * @param name the filename
*/ */
public static void setFilename(String name) { public static void setFilename(String name) {
filename = name; filename = name;
@ -305,6 +306,7 @@ public class Preferences {
/** /**
* Store the preferences in a file for the current filename. * 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 * @throws RuntimeException if the preferences filename was not set
*/ */
public static boolean store() { public static boolean store() {
@ -346,6 +348,7 @@ public class Preferences {
/** /**
* Return the paths in the UserPluginPath property. * Return the paths in the UserPluginPath property.
* Return zero length array if this property is not set. * Return zero length array if this property is not set.
* @return the paths
* *
*/ */
public static String[] getPluginPaths() { public static String[] getPluginPaths() {
@ -359,6 +362,7 @@ public class Preferences {
/** /**
* Set the paths to be used as the UserPluginPath property. * Set the paths to be used as the UserPluginPath property.
* @param paths the paths
*/ */
public static void setPluginPaths(String[] paths) { public static void setPluginPaths(String[] paths) {
if (paths == null || paths.length == 0) { if (paths == null || paths.length == 0) {
@ -376,55 +380,6 @@ public class Preferences {
properties.setProperty(USER_PLUGIN_PATH, sb.toString()); 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() { private static List<String> getPluginPathList() {
String path = properties.getProperty(USER_PLUGIN_PATH); String path = properties.getProperty(USER_PLUGIN_PATH);
if (path == null) { if (path == null) {

View file

@ -17,8 +17,7 @@ package ghidra.util.classfinder;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Enumeration; import java.util.*;
import java.util.Set;
import java.util.jar.JarEntry; import java.util.jar.JarEntry;
import java.util.jar.JarFile; import java.util.jar.JarFile;
import java.util.regex.Matcher; import java.util.regex.Matcher;
@ -28,7 +27,9 @@ import org.apache.commons.io.FilenameUtils;
import generic.jar.ResourceFile; import generic.jar.ResourceFile;
import ghidra.framework.Application; import ghidra.framework.Application;
import ghidra.framework.preferences.Preferences;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.SystemUtilities;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
import utility.application.ApplicationLayout; import utility.application.ApplicationLayout;
@ -46,20 +47,13 @@ class ClassJar extends ClassLocation {
Pattern.compile(".*/(.*)/(?:lib|build/libs)/(.+).jar"); Pattern.compile(".*/(.*)/(?:lib|build/libs)/(.+).jar");
private static final String PATCH_DIR_PATH_FORWARD_SLASHED = getPatchDirPath(); private static final String PATCH_DIR_PATH_FORWARD_SLASHED = getPatchDirPath();
private static final Set<String> USER_PLUGIN_PATHS = loadUserPluginPaths();
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 String path; private String path;
ClassJar(String path, TaskMonitor monitor) throws CancelledException { ClassJar(String path, TaskMonitor monitor) throws CancelledException {
this.path = path; this.path = path;
loadUserPluginPaths();
scanJar(monitor); scanJar(monitor);
} }
@ -105,10 +99,22 @@ class ClassJar extends ClassLocation {
if (pathName.contains("ExternalLibraries")) { if (pathName.contains("ExternalLibraries")) {
return true; 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 // Production Mode - allow users to enter code in the 'patch' directory
// //
String forwardSlashedPathName = pathName.replaceAll("\\\\", "/");
if (isPatchJar(forwardSlashedPathName)) { if (isPatchJar(forwardSlashedPathName)) {
return false; return false;
} }
@ -123,6 +129,10 @@ class ClassJar extends ClassLocation {
return true; return true;
} }
private static boolean isUserPluginJar(String pathName) {
return USER_PLUGIN_PATHS.contains(pathName);
}
// Note: the path is expected to be using forward slashes // Note: the path is expected to be using forward slashes
private static boolean isPatchJar(String pathName) { private static boolean isPatchJar(String pathName) {
String jarDirectory = FilenameUtils.getFullPathNoEndSeparator(pathName); String jarDirectory = FilenameUtils.getFullPathNoEndSeparator(pathName);
@ -168,4 +178,27 @@ class ClassJar extends ClassLocation {
public String toString() { public String toString() {
return path; 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; package ghidra.framework.main;
import java.awt.*; import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
@ -34,6 +32,7 @@ import javax.swing.event.ListSelectionListener;
import docking.DialogComponentProvider; import docking.DialogComponentProvider;
import docking.options.editor.ButtonPanelFactory; import docking.options.editor.ButtonPanelFactory;
import docking.widgets.filechooser.GhidraFileChooser; import docking.widgets.filechooser.GhidraFileChooser;
import docking.widgets.filechooser.GhidraFileChooserMode;
import docking.widgets.label.GDLabel; import docking.widgets.label.GDLabel;
import docking.widgets.list.GListCellRenderer; import docking.widgets.list.GListCellRenderer;
import ghidra.framework.plugintool.PluginTool; 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. * Dialog for editing the Plugin path and Jar directory path preferences.
*
* <p>The Plugin Path and Jar directory path are locations where Ghidra searches * <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 * 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 * is specified. When changes are made to these fields in the dialog, the
* Plugins. When changes are made to these fields in the dialog, the
* preferences file is updated and written to disk. The preferences file is * preferences file is updated and written to disk. The preferences file is
* located in the .ghidra directory in the user's home directory. * 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 { 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 int SIDE_MARGIN = 5;
private final static Color INVALID_PATH_COLOR = Color.red.brighter(); private final static Color INVALID_PATH_COLOR = Color.red.brighter();
private final static Color INVALID_SELECTED_PATH_COLOR = Color.pink; 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 // gui members needed for dis/enabling and other state-dependent actions
private JScrollPane scrollPane; // need for preferred size when resizing private JScrollPane scrollPane; // need for preferred size when resizing
private JList<String> pluginPathsList; private JList<String> pluginPathsList;
private BrowsePathPanel jarPathPanel;
private GhidraFileChooser fileChooser; private GhidraFileChooser fileChooser;
private JButton upButton; private JButton upButton;
private JButton downButton; private JButton downButton;
@ -94,7 +90,6 @@ class EditPluginPathDialog extends DialogComponentProvider {
* Creates a non-modal dialog with OK, Apply, Cancel buttons. * Creates a non-modal dialog with OK, Apply, Cancel buttons.
* The OK and Apply buttons will be enabled when user makes unapplied * The OK and Apply buttons will be enabled when user makes unapplied
* changes to the UserPluginPath or UserPluginJarDirectory property values. * changes to the UserPluginPath or UserPluginJarDirectory property values.
* @param parent parent to this dialog
*/ */
EditPluginPathDialog() { EditPluginPathDialog() {
super("Edit Plugin Path", true, false, true, false); super("Edit Plugin Path", true, false, true, false);
@ -134,8 +129,6 @@ class EditPluginPathDialog extends DialogComponentProvider {
// subsequent panels // subsequent panels
mainPanel.add(buildPluginPathsPanel()); mainPanel.add(buildPluginPathsPanel());
mainPanel.add(Box.createVerticalStrut(10)); mainPanel.add(Box.createVerticalStrut(10));
mainPanel.add(buildJarDirectoryPanel());
mainPanel.add(Box.createVerticalStrut(10));
mainPanel.add(Box.createVerticalGlue()); mainPanel.add(Box.createVerticalGlue());
mainPanel.add(statusMessagePanel); mainPanel.add(statusMessagePanel);
mainPanel.invalidate(); mainPanel.invalidate();
@ -147,33 +140,15 @@ class EditPluginPathDialog extends DialogComponentProvider {
return mainPanel; return mainPanel;
} }
/**
* Gets called when the user selects Apply
*/
@Override @Override
protected void applyCallback() { 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(); handleApply();
} }
/**
* Gets called when the user selects Cancel
*/
@Override @Override
protected void cancelCallback() { protected void cancelCallback() {
close(); close();
// reset original state of dialog for next display of dialog // reset original state of dialog for next display of dialog
enableButtons(false); enableButtons(false);
setStatusMessage(EMPTY_STATUS); setStatusMessage(EMPTY_STATUS);
@ -181,17 +156,6 @@ class EditPluginPathDialog extends DialogComponentProvider {
errorMsg = null; 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 * 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) { public void show(PluginTool tool) {
setPluginPathsListData(Preferences.getPluginPaths()); setPluginPathsListData(Preferences.getPluginPaths());
initJarDirectory(); setApplyEnabled(pluginPathsChanged);
setStatusMessage(EMPTY_STATUS);
// setting the path enables the apply, but we know we haven't // setting the path enables the apply, but we know we haven't
// made any changes yet, so disable // made any changes yet, so disable
setApplyEnabled(false); setApplyEnabled(false);
tool.showDialog(this); tool.showDialog(this);
} }
/** private void setStatusMessage(String msg) {
* Method enableApply.
*/
void enableApply() {
setApplyEnabled(pluginPathsChanged || jarPathPanel.isChanged());
}
void initJarDirectory() {
setApplyEnabled(pluginPathsChanged);
setStatusMessage(EMPTY_STATUS);
}
void setStatusMessage(String msg) {
if (msg == null || msg.length() == 0) { if (msg == null || msg.length() == 0) {
msg = EMPTY_STATUS; msg = EMPTY_STATUS;
} }
@ -237,15 +192,7 @@ class EditPluginPathDialog extends DialogComponentProvider {
statusMessage.invalidate(); statusMessage.invalidate();
} }
/** private void addJarCallback() {
* @see ghidra.util.bean.GhidraDialog#setApplyEnabled(boolean)
*/
@Override
protected void setApplyEnabled(boolean state) {
super.setApplyEnabled(state);
}
void addJarCallback() {
setStatusMessage(EditPluginPathDialog.EMPTY_STATUS); setStatusMessage(EditPluginPathDialog.EMPTY_STATUS);
@ -253,7 +200,7 @@ class EditPluginPathDialog extends DialogComponentProvider {
fileChooser = new GhidraFileChooser(getComponent()); fileChooser = new GhidraFileChooser(getComponent());
fileChooser.setCurrentDirectory(new File(System.getProperty("user.home"))); fileChooser.setCurrentDirectory(new File(System.getProperty("user.home")));
} }
fileChooser.setFileSelectionMode(GhidraFileChooser.FILES_ONLY); fileChooser.setFileSelectionMode(GhidraFileChooserMode.FILES_ONLY);
fileChooser.setFileFilter(JAR_FILTER); fileChooser.setFileFilter(JAR_FILTER);
fileChooser.setApproveButtonToolTipText("Choose Plugin Jar File"); fileChooser.setApproveButtonToolTipText("Choose Plugin Jar File");
fileChooser.setApproveButtonText("Add Jar File"); fileChooser.setApproveButtonText("Add Jar File");
@ -277,7 +224,7 @@ class EditPluginPathDialog extends DialogComponentProvider {
} }
} }
void addDirCallback() { private void addDirCallback() {
setStatusMessage(EditPluginPathDialog.EMPTY_STATUS); setStatusMessage(EditPluginPathDialog.EMPTY_STATUS);
@ -285,7 +232,7 @@ class EditPluginPathDialog extends DialogComponentProvider {
fileChooser = new GhidraFileChooser(getComponent()); fileChooser = new GhidraFileChooser(getComponent());
fileChooser.setCurrentDirectory(new File(System.getProperty("user.home"))); fileChooser.setCurrentDirectory(new File(System.getProperty("user.home")));
} }
fileChooser.setFileSelectionMode(GhidraFileChooser.DIRECTORIES_ONLY); fileChooser.setFileSelectionMode(GhidraFileChooserMode.DIRECTORIES_ONLY);
fileChooser.setFileFilter(GhidraFileFilter.ALL); fileChooser.setFileFilter(GhidraFileFilter.ALL);
fileChooser.setApproveButtonToolTipText("Choose Directory with Plugin class Files"); fileChooser.setApproveButtonToolTipText("Choose Directory with Plugin class Files");
fileChooser.setApproveButtonText("Add Directory"); 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() { private String[] getUserPluginPaths() {
String[] pluginsArray = new String[listModel.size()]; String[] pluginsArray = new String[listModel.size()];
listModel.copyInto(pluginsArray); listModel.copyInto(pluginsArray);
return pluginsArray; return pluginsArray;
} }
/**
* construct the plugin paths button panel
*/
private JPanel buildPluginPathsPanel() { private JPanel buildPluginPathsPanel() {
// create the UP and DOWN arrows panel // create the UP and DOWN arrows panel
upButton = ButtonPanelFactory.createButton(ButtonPanelFactory.ARROW_UP_TYPE); upButton = ButtonPanelFactory.createButton(ButtonPanelFactory.ARROW_UP_TYPE);
upButton.setName("UpArrow"); upButton.setName("UpArrow");
upButton.addActionListener(new ActionListener() { upButton.addActionListener(e -> handleSelection(UP));
@Override
public void actionPerformed(ActionEvent e) {
handleSelection(UP);
}
});
downButton = ButtonPanelFactory.createButton(ButtonPanelFactory.ARROW_DOWN_TYPE); downButton = ButtonPanelFactory.createButton(ButtonPanelFactory.ARROW_DOWN_TYPE);
downButton.setName("DownArrow"); downButton.setName("DownArrow");
downButton.addActionListener(new ActionListener() { downButton.addActionListener(e -> handleSelection(DOWN));
@Override
public void actionPerformed(ActionEvent e) {
handleSelection(DOWN);
}
});
JPanel arrowButtonsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 10)); JPanel arrowButtonsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 10));
arrowButtonsPanel.add(upButton); arrowButtonsPanel.add(upButton);
arrowButtonsPanel.add(downButton); arrowButtonsPanel.add(downButton);
// create the Add and Remove panel // create the Add and Remove panel
JButton addJarButton = ButtonPanelFactory.createButton("Add Jar..."); JButton addJarButton = ButtonPanelFactory.createButton(ADD_JAR_BUTTON_TEXT);
addJarButton.addActionListener(new ActionListener() { addJarButton.addActionListener(e -> addJarCallback());
@Override JButton addDirButton = ButtonPanelFactory.createButton(ADD_DIR_BUTTON_TEXT);
public void actionPerformed(ActionEvent e) { addDirButton.addActionListener(e -> addDirCallback());
addJarCallback();
}
});
JButton addDirButton = ButtonPanelFactory.createButton("Add Dir...");
addDirButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
addDirCallback();
}
});
removeButton = ButtonPanelFactory.createButton("Remove"); removeButton = ButtonPanelFactory.createButton("Remove");
removeButton.addActionListener(new ActionListener() { removeButton.addActionListener(e -> handleSelection(REMOVE));
@Override
public void actionPerformed(ActionEvent e) {
handleSelection(REMOVE);
}
});
Dimension d = addJarButton.getPreferredSize(); Dimension d = addJarButton.getPreferredSize();
addDirButton.setPreferredSize(d); addDirButton.setPreferredSize(d);
removeButton.setPreferredSize(d); removeButton.setPreferredSize(d);
@ -415,26 +330,6 @@ class EditPluginPathDialog extends DialogComponentProvider {
return pluginPathListPanel; 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) { private void enableButtons(boolean enabled) {
upButton.setEnabled(enabled); upButton.setEnabled(enabled);
downButton.setEnabled(enabled); downButton.setEnabled(enabled);
@ -452,22 +347,12 @@ class EditPluginPathDialog extends DialogComponentProvider {
// update Ghidra Preferences with new paths // update Ghidra Preferences with new paths
Preferences.setPluginPaths(userPluginPaths); 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; errorMsg = null;
// save the new values // save the new values
if (Preferences.store()) { if (Preferences.store()) {
setStatusMessage("Saved plugin paths successfully!"); setStatusMessage("Saved plugin paths successfully!");
// indicate to user all changes have been applied // indicate to user all changes have been applied
setApplyEnabled(false); setApplyEnabled(false);
jarPathPanel.setChanged(false);
Msg.showInfo(getClass(), rootPanel, "Restart Ghidra", Msg.showInfo(getClass(), rootPanel, "Restart Ghidra",
"You must restart Ghidra in order\n" + "for path changes to take effect."); "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) { private void handleSelection(byte whichAction) {
// if nothing selected, nothing to do // if nothing selected, nothing to do
if (selectedInList == null) { if (selectedInList == null) {
@ -574,8 +455,8 @@ class EditPluginPathDialog extends DialogComponentProvider {
private void setPluginPathsListData(String[] pluginPathNames) { private void setPluginPathsListData(String[] pluginPathNames) {
listModel.clear(); listModel.clear();
for (int p = 0; p < pluginPathNames.length; p++) { for (String pluginPathName : pluginPathNames) {
listModel.addElement(pluginPathNames[p]); listModel.addElement(pluginPathName);
} }
} }

View file

@ -64,6 +64,9 @@ public class GhidraApplicationLayout extends ApplicationLayout {
// Extensions // Extensions
extensionInstallationDir = findExtensionInstallationDirectory(); extensionInstallationDir = findExtensionInstallationDirectory();
extensionArchiveDir = findExtensionArchiveDirectory(); 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 // Find standard module root directories from within the application root directories
Collection<ResourceFile> moduleRootDirectories = Collection<ResourceFile> moduleRootDirectories =
ModuleUtilities.findModuleRootDirectories(applicationRootDirs, new ArrayList<>()); ModuleUtilities.findModuleRootDirectories(applicationRootDirs, new ArrayList<>());
// Examine the classpath to look for modules outside of the application root directories. // 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 // These might exist if Ghidra was launched from an Eclipse project that resides
// external to the Ghidra installation. // 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 // Skip classpath entries that live in an application root directory...we've already
// found those. // found those.
if (applicationRootDirs.stream().anyMatch(dir -> FileUtilities.isPathContainedWithin( if (applicationRootDirs.stream()
dir.getFile(false), classpathEntry.getFile(false)))) { .anyMatch(dir -> FileUtilities.isPathContainedWithin(
dir.getFile(false), classpathEntry.getFile(false)))) {
continue; continue;
} }
@ -173,6 +177,24 @@ public class GhidraApplicationLayout extends ApplicationLayout {
return ModuleUtilities.findModules(applicationRootDirs, moduleRootDirectories); 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. * Returns the directory where all Ghidra extension archives are stored.
* This should be at the following location:<br> * This should be at the following location:<br>

View file

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

View file

@ -55,4 +55,10 @@ public class GhidraTestApplicationLayout extends GhidraApplicationLayout {
File installDir = new File(getUserTempDir(), "ExtensionInstallDir"); File installDir = new File(getUserTempDir(), "ExtensionInstallDir");
return new ResourceFile(installDir); 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 userTempDir;
protected File userCacheDir; protected File userCacheDir;
protected File userSettingsDir; protected File userSettingsDir;
protected ResourceFile patchDir;
protected ResourceFile extensionArchiveDir; protected ResourceFile extensionArchiveDir;
protected ResourceFile extensionInstallationDir; 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 * @return the application Extensions archive directory. Could be null if the
* {@link ApplicationLayout} does not support Ghidra Extensions. * {@link ApplicationLayout} does not support application Extensions.
* *
*/ */
public final ResourceFile getExtensionArchiveDir() { 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 * @return the application Extensions installation directory. Could be null if the
* {@link ApplicationLayout} does not support Ghidra Extensions. * {@link ApplicationLayout} does not support application Extensions.
*/ */
public final ResourceFile getExtensionInstallationDir() { public final ResourceFile getExtensionInstallationDir() {
return extensionInstallationDir; 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). * Creates the application's user directories (or ensures they already exist).
* *

View file

@ -255,7 +255,6 @@ public class FrontEndPluginScreenShots extends GhidraScreenShotGenerator {
@Test @Test
public void testEditPluginPath() { public void testEditPluginPath() {
Preferences.setProperty(Preferences.USER_PLUGIN_JAR_DIRECTORY, "/MyPlugins");
Preferences.setPluginPaths(new String[] { "/myJar.jar", "/MyPlugins/classes" }); Preferences.setPluginPaths(new String[] { "/myJar.jar", "/MyPlugins/classes" });
performAction("Edit Plugin Path", "FrontEndPlugin", false); performAction("Edit Plugin Path", "FrontEndPlugin", false);
DialogComponentProvider dialog = getDialog(); 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 Compiled Java class files, either inside of a jar file or in a directory structure may be inserted
in a directory structure. This directory and the contained jar files will be prepended to 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. 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 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 predictable class loading between Ghidra runs. This directory will be prepended on the classpath