style fixes in ghidra.app. {plugin.core.script script}

This commit is contained in:
Jason P. Leasure 2020-06-08 13:00:26 -04:00
parent 6bc33bdf65
commit 2015f13542
22 changed files with 442 additions and 372 deletions

View file

@ -24,6 +24,7 @@ import java.util.*;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipFile; import java.util.zip.ZipFile;
import javax.swing.Icon;
import javax.swing.KeyStroke; import javax.swing.KeyStroke;
import docking.ActionContext; import docking.ActionContext;
@ -49,18 +50,15 @@ class GhidraScriptActionManager {
KeyEvent.VK_R, DockingUtils.CONTROL_KEY_MODIFIER_MASK | InputEvent.SHIFT_DOWN_MASK); KeyEvent.VK_R, DockingUtils.CONTROL_KEY_MODIFIER_MASK | InputEvent.SHIFT_DOWN_MASK);
private static final String SCRIPT_ACTIONS_KEY = "Scripts_Actions_Key"; private static final String SCRIPT_ACTIONS_KEY = "Scripts_Actions_Key";
private static final String RESOURCE_FILE_ACTION_RUN_GROUP = "1";
private GhidraScriptComponentProvider provider; private GhidraScriptComponentProvider provider;
private GhidraScriptMgrPlugin plugin; private GhidraScriptMgrPlugin plugin;
private GhidraScriptInfoManager infoManager; private GhidraScriptInfoManager infoManager;
private DockingAction refreshAction; private DockingAction showBundleStatusAction;
private DockingAction bundleStatusAction;
private DockingAction newAction; private DockingAction newAction;
private DockingAction runAction;
private DockingAction runLastAction; private DockingAction runLastAction;
private DockingAction globalRunLastAction; private DockingAction globalRunLastAction;
private DockingAction editAction;
private DockingAction eclipseAction;
private DockingAction deleteAction;
private DockingAction renameAction; private DockingAction renameAction;
private DockingAction keyBindingAction; private DockingAction keyBindingAction;
private DockingAction helpAction; private DockingAction helpAction;
@ -174,15 +172,12 @@ class GhidraScriptActionManager {
globalRunLastAction.firePropertyChanged(DockingActionIf.DESCRIPTION_PROPERTY, "", newDesc); globalRunLastAction.firePropertyChanged(DockingActionIf.DESCRIPTION_PROPERTY, "", newDesc);
} }
private void createActions() { private DockingAction createScriptAction(String name, String menuEntry,
// String actionDescription, Icon icon, String toolBarGroup, Runnable runnable) {
// 'run' actions DockingAction action = new DockingAction(name, plugin.getName()) {
//
String runGroup = "1";
runAction = new DockingAction("Run", plugin.getName()) {
@Override @Override
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
provider.runScript(); runnable.run();
} }
@Override @Override
@ -191,188 +186,79 @@ class GhidraScriptActionManager {
return contextObject instanceof ResourceFile; return contextObject instanceof ResourceFile;
} }
}; };
runAction.setPopupMenuData(new MenuData(new String[] { "Run" }, action.setPopupMenuData(new MenuData(new String[] { menuEntry }, icon));
ResourceManager.loadImage("images/play.png"), null)); action.setToolBarData(new ToolBarData(icon, toolBarGroup));
runAction.setToolBarData(
new ToolBarData(ResourceManager.loadImage("images/play.png"), runGroup));
runAction.setDescription("Run Script"); action.setDescription(actionDescription);
runAction.setEnabled(false); action.setEnabled(false);
plugin.getTool().addLocalAction(provider, runAction);
runLastAction = new RerunLastScriptAction(runGroup); plugin.getTool().addLocalAction(provider, action);
return action;
}
private DockingAction createScriptTableAction(String name, String actionDescription, Icon icon,
Runnable runnable) {
DockingAction action = new DockingAction(name, plugin.getName()) {
@Override
public void actionPerformed(ActionContext context) {
runnable.run();
}
@Override
public boolean isAddToPopup(ActionContext context) {
Object contextObject = context.getContextObject();
return (contextObject instanceof GTable) || (contextObject instanceof ResourceFile);
}
};
action.setPopupMenuData(new MenuData(new String[] { name }, icon));
action.setToolBarData(new ToolBarData(icon, null));
action.setDescription(actionDescription);
action.setEnabled(true);
plugin.getTool().addLocalAction(provider, action);
return action;
}
private void createActions() {
createScriptAction("Run", "Run Script", "Run Script",
ResourceManager.loadImage("images/play.png"), RESOURCE_FILE_ACTION_RUN_GROUP,
provider::runScript);
runLastAction = new RerunLastScriptAction(RESOURCE_FILE_ACTION_RUN_GROUP);
plugin.getTool().addLocalAction(provider, runLastAction); plugin.getTool().addLocalAction(provider, runLastAction);
globalRunLastAction = new RerunLastScriptAction("Xtra"); globalRunLastAction = new RerunLastScriptAction("Xtra");
plugin.getTool().addAction(globalRunLastAction); plugin.getTool().addAction(globalRunLastAction);
// createScriptAction("Edit", "Edit with basic editor", "Edit Script with basic editor",
// End 'run' actions ResourceManager.loadImage("images/accessories-text-editor.png"), null,
// provider::editScriptBuiltin);
editAction = new DockingAction("Edit", plugin.getName()) { createScriptAction("EditEclipse", "Edit with Eclipse", "Edit Script with Eclipse",
@Override ResourceManager.loadImage("images/eclipse.png"), null, provider::editScriptEclipse);
public void actionPerformed(ActionContext context) {
provider.editScriptBuiltin();
}
@Override keyBindingAction =
public boolean isEnabledForContext(ActionContext context) { createScriptAction("Key Binding", "Assign Key Binding", "Assign Key Binding",
Object contextObject = context.getContextObject(); ResourceManager.loadImage("images/key.png"), null, provider::assignKeyBinding);
return contextObject instanceof ResourceFile;
}
};
editAction.setPopupMenuData(new MenuData(new String[] { "Edit with basic editor" },
ResourceManager.loadImage("images/accessories-text-editor.png"), null));
editAction.setToolBarData(
new ToolBarData(ResourceManager.loadImage("images/accessories-text-editor.png"), null));
editAction.setDescription("Edit Script with basic editor");
editAction.setEnabled(false);
plugin.getTool().addLocalAction(provider, editAction);
eclipseAction = new DockingAction("EditEclipse", plugin.getName()) { createScriptAction("Delete", "Delete", "Delete Script",
@Override ResourceManager.loadImage("images/edit-delete.png"), null, provider::deleteScript);
public void actionPerformed(ActionContext context) {
provider.editScriptEclipse();
}
@Override renameAction = createScriptAction("Rename", "Rename", "Rename Script",
public boolean isEnabledForContext(ActionContext context) { ResourceManager.loadImage("images/textfield_rename.png"), null, provider::renameScript);
Object contextObject = context.getContextObject();
return contextObject instanceof ResourceFile;
}
};
eclipseAction.setPopupMenuData(new MenuData(new String[] { "Edit with Eclipse" },
ResourceManager.loadImage("images/eclipse.png"), null));
eclipseAction.setToolBarData(
new ToolBarData(ResourceManager.loadImage("images/eclipse.png"), null));
eclipseAction.setDescription("Edit Script with Eclipse");
eclipseAction.setEnabled(false);
plugin.getTool().addLocalAction(provider, eclipseAction);
keyBindingAction = new DockingAction("Key Binding", plugin.getName()) { newAction = createScriptTableAction("New", "Create New Script",
@Override ResourceManager.loadImage("images/script_add.png"), provider::newScript);
public void actionPerformed(ActionContext context) {
provider.assignKeyBinding();
}
@Override createScriptTableAction("Refresh", "Refresh Script List",
public boolean isEnabledForContext(ActionContext context) { Icons.REFRESH_ICON, provider::refresh);
Object contextObject = context.getContextObject();
return contextObject instanceof ResourceFile;
}
};
keyBindingAction.setPopupMenuData(new MenuData(new String[] { "Assign Key Binding" },
ResourceManager.loadImage("images/key.png"), null));
keyBindingAction.setToolBarData(
new ToolBarData(ResourceManager.loadImage("images/key.png"), null));
keyBindingAction.setDescription("Assign Key Binding"); showBundleStatusAction = createScriptTableAction("Script Directories",
keyBindingAction.setEnabled(false); "Manage Script Directories", ResourceManager.loadImage("images/text_list_bullets.png"),
plugin.getTool().addLocalAction(provider, keyBindingAction); provider::showBundleStatusComponent);
deleteAction = new DockingAction("Delete", plugin.getName()) {
@Override
public void actionPerformed(ActionContext context) {
provider.deleteScript();
}
@Override
public boolean isEnabledForContext(ActionContext context) {
Object contextObject = context.getContextObject();
return contextObject instanceof ResourceFile;
}
};
deleteAction.setPopupMenuData(new MenuData(new String[] { "Delete" },
ResourceManager.loadImage("images/edit-delete.png"), null));
deleteAction.setToolBarData(
new ToolBarData(ResourceManager.loadImage("images/edit-delete.png"), null));
deleteAction.setDescription("Delete Script");
deleteAction.setEnabled(false);
plugin.getTool().addLocalAction(provider, deleteAction);
renameAction = new DockingAction("Rename", plugin.getName()) {
@Override
public void actionPerformed(ActionContext context) {
provider.renameScript();
}
@Override
public boolean isEnabledForContext(ActionContext context) {
Object contextObject = context.getContextObject();
return contextObject instanceof ResourceFile;
}
};
renameAction.setPopupMenuData(new MenuData(new String[] { "Rename" },
ResourceManager.loadImage("images/textfield_rename.png"), null));
renameAction.setToolBarData(
new ToolBarData(ResourceManager.loadImage("images/textfield_rename.png"), null));
renameAction.setDescription("Rename Script");
renameAction.setEnabled(false);
plugin.getTool().addLocalAction(provider, renameAction);
newAction = new DockingAction("New", plugin.getName()) {
@Override
public void actionPerformed(ActionContext context) {
provider.newScript();
}
@Override
public boolean isAddToPopup(ActionContext context) {
Object contextObject = context.getContextObject();
return (contextObject instanceof GTable) || (contextObject instanceof ResourceFile);
}
};
newAction.setPopupMenuData(new MenuData(new String[] { "New" },
ResourceManager.loadImage("images/script_add.png"), null));
newAction.setToolBarData(
new ToolBarData(ResourceManager.loadImage("images/script_add.png"), null));
newAction.setDescription("Create New Script");
newAction.setEnabled(true);
plugin.getTool().addLocalAction(provider, newAction);
refreshAction = new DockingAction("Refresh", plugin.getName()) {
@Override
public void actionPerformed(ActionContext context) {
provider.refresh();
}
@Override
public boolean isAddToPopup(ActionContext context) {
Object contextObject = context.getContextObject();
return (contextObject instanceof GTable) || (contextObject instanceof ResourceFile);
}
};
refreshAction.setPopupMenuData(
new MenuData(new String[] { "Refresh" }, Icons.REFRESH_ICON, null));
refreshAction.setToolBarData(new ToolBarData(Icons.REFRESH_ICON, null));
refreshAction.setDescription("Refresh Script List");
refreshAction.setEnabled(true);
plugin.getTool().addLocalAction(provider, refreshAction);
bundleStatusAction = new DockingAction("Script Directories", plugin.getName()) {
@Override
public void actionPerformed(ActionContext context) {
provider.showBundleStatusComponent();
}
@Override
public boolean isAddToPopup(ActionContext context) {
Object contextObject = context.getContextObject();
return (contextObject instanceof GTable) || (contextObject instanceof ResourceFile);
}
};
bundleStatusAction.setPopupMenuData(new MenuData(new String[] { "Bundle Status" },
ResourceManager.loadImage("images/text_list_bullets.png"), null));
bundleStatusAction.setToolBarData(
new ToolBarData(ResourceManager.loadImage("images/text_list_bullets.png"), null));
bundleStatusAction.setDescription("Bundle Status");
bundleStatusAction.setEnabled(true);
plugin.getTool().addLocalAction(provider, bundleStatusAction);
helpAction = new DockingAction("Ghidra API Help", plugin.getName()) { helpAction = new DockingAction("Ghidra API Help", plugin.getName()) {
@Override @Override
@ -441,7 +327,7 @@ class GhidraScriptActionManager {
} }
HelpLocation getPathHelpLocation() { HelpLocation getPathHelpLocation() {
return new HelpLocation(plugin.getName(), bundleStatusAction.getName()); return new HelpLocation(plugin.getName(), showBundleStatusAction.getName());
} }
HelpLocation getKeyBindingHelpLocation() { HelpLocation getKeyBindingHelpLocation() {

View file

@ -20,6 +20,7 @@ import java.awt.Rectangle;
import java.awt.event.*; import java.awt.event.*;
import java.io.*; import java.io.*;
import java.util.*; import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.swing.*; import javax.swing.*;
@ -57,13 +58,12 @@ import util.CollectionUtils;
import utilities.util.FileUtilities; import utilities.util.FileUtilities;
public class GhidraScriptComponentProvider extends ComponentProviderAdapter { public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
static final String WINDOW_GROUP = "Script Group";
private static final double TOP_PREFERRED_RESIZE_WEIGHT = .80; private static final double TOP_PREFERRED_RESIZE_WEIGHT = .80;
private static final String DESCRIPTION_DIVIDER_LOCATION = "DESCRIPTION_DIVIDER_LOCATION"; private static final String DESCRIPTION_DIVIDER_LOCATION = "DESCRIPTION_DIVIDER_LOCATION";
private static final String FILTER_TEXT = "FILTER_TEXT"; private static final String FILTER_TEXT = "FILTER_TEXT";
static final String WINDOW_GROUP = "Script Group";
private Map<ResourceFile, GhidraScriptEditorComponentProvider> editorMap = new HashMap<>(); private Map<ResourceFile, GhidraScriptEditorComponentProvider> editorMap = new HashMap<>();
private final GhidraScriptMgrPlugin plugin; private final GhidraScriptMgrPlugin plugin;
private JPanel component; private JPanel component;
@ -126,7 +126,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
updateTitle(); updateTitle();
} }
private void build() { private void buildCategoryTree() {
scriptRoot = new RootNode(); scriptRoot = new RootNode();
scriptCategoryTree = new GTree(scriptRoot); scriptCategoryTree = new GTree(scriptRoot);
@ -159,6 +159,10 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
scriptCategoryTree.getSelectionModel() scriptCategoryTree.getSelectionModel()
.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); .setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
}
private void build() {
buildCategoryTree();
tableModel = new GhidraScriptTableModel(this, infoManager); tableModel = new GhidraScriptTableModel(this, infoManager);
@ -231,10 +235,11 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
component.add(dataDescriptionSplit, BorderLayout.CENTER); component.add(dataDescriptionSplit, BorderLayout.CENTER);
} }
//================================================================================================== /**
// Inner Classes * Restore state for bundles, user actions, and filter.
//================================================================================================== *
* @param saveState the state object
*/
public void readConfigState(SaveState saveState) { public void readConfigState(SaveState saveState) {
bundleHost.restoreManagedBundleState(saveState, getTool()); bundleHost.restoreManagedBundleState(saveState, getTool());
@ -259,6 +264,12 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
tableFilterPanel.setFilterText(filterText); tableFilterPanel.setFilterText(filterText);
} }
/**
* Save state for bundles, user actions, and filter.
*
* @param saveState the state object
*/
public void writeConfigState(SaveState saveState) { public void writeConfigState(SaveState saveState) {
bundleHost.saveManagedBundleState(saveState); bundleHost.saveManagedBundleState(saveState);
@ -283,6 +294,9 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
bundleStatusComponentProvider.dispose(); bundleStatusComponentProvider.dispose();
} }
/**
* @return the bundle host used for scripting, ultimately from {@link GhidraScriptUtil#getBundleHost()}
*/
public BundleHost getBundleHost() { public BundleHost getBundleHost() {
return bundleHost; return bundleHost;
} }
@ -299,10 +313,6 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
return editorMap; return editorMap;
} }
void showBundleStatusComponent() {
bundleStatusComponentProvider.setVisible(true);
}
void assignKeyBinding() { void assignKeyBinding() {
ResourceFile script = getSelectedScript(); ResourceFile script = getSelectedScript();
ScriptAction action = actionManager.createAction(script); ScriptAction action = actionManager.createAction(script);
@ -360,82 +370,72 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
renameScriptByCopying(script, provider, renameFile); renameScriptByCopying(script, provider, renameFile);
} }
/**
* Copy a script, renaming references to the class name.
*
* @param sourceScript source script
* @param destinationScript destination script
* @throws IOException if we fail to create temp, write contents, copy, or delete temp
*/
private void copyScript(ResourceFile sourceScript, ResourceFile destinationScript)
throws IOException {
String oldClassName = GhidraScriptUtil.getBaseName(sourceScript);
String newClassName = GhidraScriptUtil.getBaseName(destinationScript);
ResourceFile parentFile = sourceScript.getParentFile();
ResourceFile temp = new ResourceFile(parentFile, "ghidraScript.tmp");
try (PrintWriter writer = new PrintWriter(temp.getOutputStream())) {
try (BufferedReader reader =
new BufferedReader(new InputStreamReader(sourceScript.getInputStream()))) {
while (true) {
String line = reader.readLine();
if (line == null) {
break;
}
writer.println(line.replaceAll(oldClassName, newClassName));
}
}
}
FileUtilities.copyFile(temp, destinationScript, TaskMonitor.DUMMY);
temp.delete();
}
private void renameScriptByCopying(ResourceFile script, GhidraScriptProvider provider, private void renameScriptByCopying(ResourceFile script, GhidraScriptProvider provider,
ResourceFile renameFile) { ResourceFile renameFile) {
String oldClassName = GhidraScriptUtil.getBaseName(script);
String newClassName = GhidraScriptUtil.getBaseName(renameFile);
ResourceFile temp = null;
PrintWriter writer = null;
BufferedReader reader = null;
try { try {
copyScript(script, renameFile);
ResourceFile parentFile = script.getParentFile();
temp = new ResourceFile(parentFile, "ghidraScript.tmp");
writer = new PrintWriter(temp.getOutputStream());
reader = new BufferedReader(new InputStreamReader(script.getInputStream()));
while (true) {
String line = reader.readLine();
if (line == null) {
break;
}
writer.println(line.replaceAll(oldClassName, newClassName));
}
reader.close();
writer.close();
FileUtilities.copyFile(temp, renameFile, TaskMonitor.DUMMY);
if (!renameFile.exists()) {
Msg.showWarn(getClass(), getComponent(), "Unable to rename script",
"The rename operation failed.\nPlease check file permissions.");
return;
}
if (!provider.deleteScript(script)) {
Msg.showWarn(getClass(), getComponent(), "Unable to rename script",
"Unable to remove original file.\nPlease check file permissions.");
renameFile.delete();
return;
}
infoManager.removeMetadata(script);
if (actionManager.hasScriptAction(script)) {
KeyStroke ks = actionManager.getKeyBinding(script);
actionManager.removeAction(script);
ScriptAction action = actionManager.createAction(renameFile);
action.setKeyBindingData(new KeyBindingData(ks));
}
assert !infoManager
.containsMetadata(renameFile) : "renamed script already has metadata";
infoManager.getScriptInfo(renameFile);
tableModel.switchScript(script, renameFile);
setSelectedScript(renameFile);
} }
catch (IOException e) { catch (IOException e) {
Msg.showError(getClass(), getComponent(), "Unable to rename script", e.getMessage()); Msg.showError(getClass(), getComponent(), "Unable to rename script", e.getMessage());
return; return;
} }
finally {
if (reader != null) {
try {
reader.close();
}
catch (IOException e) {
// we tried
}
}
if (writer != null) { if (!renameFile.exists()) {
writer.close(); Msg.showWarn(getClass(), getComponent(), "Unable to rename script",
} "The rename operation failed.\nPlease check file permissions.");
return;
if (temp != null) {
temp.delete();
}
} }
if (!provider.deleteScript(script)) {
Msg.showWarn(getClass(), getComponent(), "Unable to rename script",
"Unable to remove original file.\nPlease check file permissions.");
renameFile.delete();
return;
}
infoManager.removeMetadata(script);
if (actionManager.hasScriptAction(script)) {
KeyStroke ks = actionManager.getKeyBinding(script);
actionManager.removeAction(script);
ScriptAction action = actionManager.createAction(renameFile);
action.setKeyBindingData(new KeyBindingData(ks));
}
assert !infoManager.containsMetadata(renameFile) : "renamed script already has metadata";
infoManager.getScriptInfo(renameFile);
tableModel.switchScript(script, renameFile);
setSelectedScript(renameFile);
} }
JTable getTable() { JTable getTable() {
@ -450,19 +450,27 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
return tableModel.getScriptAt(tableFilterPanel.getModelRow(viewRowIndex)); return tableModel.getScriptAt(tableFilterPanel.getModelRow(viewRowIndex));
} }
/**
* @return enabled bundle paths from the scripting bundle host
*/
public List<ResourceFile> getScriptDirectories() { public List<ResourceFile> getScriptDirectories() {
return bundleHost.getGhidraBundles() return bundleHost.getGhidraBundles()
.stream() .stream()
.filter(bundle -> bundle.isEnabled() && bundle instanceof GhidraSourceBundle) .filter(GhidraSourceBundle.class::isInstance)
.filter(GhidraBundle::isEnabled)
.map(GhidraBundle::getPath) .map(GhidraBundle::getPath)
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
/**
* @return non-system bundle paths from the scripting bundle host
*/
public List<ResourceFile> getWritableScriptDirectories() { public List<ResourceFile> getWritableScriptDirectories() {
return bundleHost.getGhidraBundles() return bundleHost.getGhidraBundles()
.stream() .stream()
.filter(GhidraSourceBundle.class::isInstance) .filter(GhidraSourceBundle.class::isInstance)
.filter(bundle -> !bundle.isSystemBundle()) .filter(Predicate.not(GhidraBundle::isSystemBundle))
.filter(GhidraBundle::isEnabled)
.map(GhidraBundle::getPath) .map(GhidraBundle::getPath)
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@ -496,7 +504,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
} }
else { else {
Msg.showInfo(getClass(), getComponent(), getName(), Msg.showInfo(getClass(), getComponent(), getName(),
"Unable to delete script '" + script.getName() + "'" + "\n" + "Unable to delete script '" + script.getName() + "'\n" +
"Please verify the file permissions."); "Please verify the file permissions.");
} }
} }
@ -521,7 +529,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
} }
} }
public void enableScriptDirectory(ResourceFile scriptDir) { void enableScriptDirectory(ResourceFile scriptDir) {
bundleHost.enablePath(scriptDir); bundleHost.enablePath(scriptDir);
Msg.showInfo(this, getComponent(), "Script Path Added/Enabled", Msg.showInfo(this, getComponent(), "Script Path Added/Enabled",
"The directory has been automatically enabled for use:\n" + "The directory has been automatically enabled for use:\n" +
@ -699,6 +707,10 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
return categoryPath; return categoryPath;
} }
void showBundleStatusComponent() {
bundleStatusComponentProvider.setVisible(true);
}
class RefreshingBundleHostListener implements BundleHostListener { class RefreshingBundleHostListener implements BundleHostListener {
@Override @Override

View file

@ -41,9 +41,8 @@ import resources.Icons;
import resources.ResourceManager; import resources.ResourceManager;
public class GhidraScriptEditorComponentProvider extends ComponentProvider { public class GhidraScriptEditorComponentProvider extends ComponentProvider {
static final String EDITOR_COMPONENT_NAME="EDITOR";
private static final int MAX_UNDO_REDO_SIZE = 50;
static final String CHANGE_DESTINATION_TITLE = "Where Would You Like to Store Your Changes?"; static final String CHANGE_DESTINATION_TITLE = "Where Would You Like to Store Your Changes?";
static final String FILE_ON_DISK_CHANGED_TITLE = "File Changed on Disk"; static final String FILE_ON_DISK_CHANGED_TITLE = "File Changed on Disk";
static final String FILE_ON_DISK_MISSING_TITLE = "File on Disk is Missing"; static final String FILE_ON_DISK_MISSING_TITLE = "File on Disk is Missing";
@ -53,7 +52,9 @@ public class GhidraScriptEditorComponentProvider extends ComponentProvider {
static final String KEEP_CHANGES_TEXT = "Keep Changes"; static final String KEEP_CHANGES_TEXT = "Keep Changes";
static final String DISCARD_CHANGES_TEXT = "Discard Changes"; static final String DISCARD_CHANGES_TEXT = "Discard Changes";
static Font defaultFont = new Font("monospaced", Font.PLAIN, 12); private static final int MAX_UNDO_REDO_SIZE = 50;
private static Font defaultFont = new Font("monospaced", Font.PLAIN, 12);
static void restoreState(SaveState saveState) { static void restoreState(SaveState saveState) {
String name = saveState.getString("DEFAULT_FONT_NAME", "Monospaced"); String name = saveState.getString("DEFAULT_FONT_NAME", "Monospaced");
@ -68,6 +69,7 @@ public class GhidraScriptEditorComponentProvider extends ComponentProvider {
saveState.putInt("DEFAULT_FONT_SIZE", defaultFont.getSize()); saveState.putInt("DEFAULT_FONT_SIZE", defaultFont.getSize());
} }
private GhidraScriptMgrPlugin plugin; private GhidraScriptMgrPlugin plugin;
private GhidraScriptComponentProvider provider; private GhidraScriptComponentProvider provider;
private String title; private String title;
@ -675,7 +677,7 @@ public class GhidraScriptEditorComponentProvider extends ComponentProvider {
super(text); super(text);
setFont(defaultFont); setFont(defaultFont);
setName("EDITOR"); setName(EDITOR_COMPONENT_NAME);
setWrapStyleWord(false); setWrapStyleWord(false);
Document document = getDocument(); Document document = getDocument();
document.addUndoableEditListener(e -> { document.addUndoableEditListener(e -> {

View file

@ -26,8 +26,7 @@ import ghidra.app.plugin.ProgramPlugin;
import ghidra.app.plugin.core.eclipse.EclipseConnection; import ghidra.app.plugin.core.eclipse.EclipseConnection;
import ghidra.app.plugin.core.eclipse.EclipseIntegrationOptionsPlugin; import ghidra.app.plugin.core.eclipse.EclipseIntegrationOptionsPlugin;
import ghidra.app.plugin.core.osgi.BundleHost; import ghidra.app.plugin.core.osgi.BundleHost;
import ghidra.app.script.GhidraScriptUtil; import ghidra.app.script.*;
import ghidra.app.script.GhidraState;
import ghidra.app.services.*; import ghidra.app.services.*;
import ghidra.framework.options.SaveState; import ghidra.framework.options.SaveState;
import ghidra.framework.options.ToolOptions; import ghidra.framework.options.ToolOptions;
@ -49,12 +48,17 @@ import ghidra.util.task.TaskListener;
) )
//@formatter:on //@formatter:on
public class GhidraScriptMgrPlugin extends ProgramPlugin implements GhidraScriptService { public class GhidraScriptMgrPlugin extends ProgramPlugin implements GhidraScriptService {
private static int loaded = 0;
private final GhidraScriptComponentProvider provider; private final GhidraScriptComponentProvider provider;
private static int loaded = 0;
private final BundleHost bundleHost; private final BundleHost bundleHost;
/**
* {@link GhidraScriptMgrPlugin} is the entry point for all {@link GhidraScript} capabilities.
*
* @param tool the tool this plugin is added to
*/
public GhidraScriptMgrPlugin(PluginTool tool) { public GhidraScriptMgrPlugin(PluginTool tool) {
super(tool, true, true, true); super(tool, true, true, true);
if (loaded == 0) { if (loaded == 0) {
@ -111,6 +115,11 @@ public class GhidraScriptMgrPlugin extends ProgramPlugin implements GhidraScript
provider.runScript(scriptName, listener); provider.runScript(scriptName, listener);
} }
/**
* Attempts to run a script in a {@link RunScriptTask}.
*
* @param scriptFile the script's source file
*/
public void runScript(ResourceFile scriptFile) { public void runScript(ResourceFile scriptFile) {
provider.runScript(scriptFile); provider.runScript(scriptFile);
} }

View file

@ -35,13 +35,12 @@ import ghidra.util.table.column.GColumnRenderer;
import resources.Icons; import resources.Icons;
class GhidraScriptTableModel extends GDynamicColumnTableModel<ResourceFile, Object> { class GhidraScriptTableModel extends GDynamicColumnTableModel<ResourceFile, Object> {
static final String SCRIPT_ACTION_COLUMN_NAME = "In Tool";
static final String SCRIPT_STATUS_COLUMN_NAME = "Status";
private static final String EMPTY_STRING = ""; private static final String EMPTY_STRING = "";
private static final ImageIcon ERROR_IMG = Icons.ERROR_ICON; private static final ImageIcon ERROR_IMG = Icons.ERROR_ICON;
static final String SCRIPT_ACTION_COLUMN_NAME = "In Tool";
static final String SCRIPT_STATUS_COLUMN_NAME = "Status";
private GhidraScriptComponentProvider provider; private GhidraScriptComponentProvider provider;
private List<ResourceFile> scriptList = new ArrayList<>(); private List<ResourceFile> scriptList = new ArrayList<>();
private final GhidraScriptInfoManager infoManager; private final GhidraScriptInfoManager infoManager;
@ -276,7 +275,7 @@ class GhidraScriptTableModel extends GDynamicColumnTableModel<ResourceFile, Obje
@Override @Override
public String getColumnName() { public String getColumnName() {
return "Status"; return SCRIPT_STATUS_COLUMN_NAME;
} }
@Override @Override

View file

@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,5 +16,5 @@
package ghidra.app.plugin.core.script; package ghidra.app.plugin.core.script;
public interface Ingredient { public interface Ingredient {
public IngredientDescription [] getIngredientDescriptions(); IngredientDescription [] getIngredientDescriptions();
} }

View file

@ -30,13 +30,13 @@ import ghidra.app.script.GhidraScriptUtil;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
public class PickProviderDialog extends DialogComponentProvider { public class PickProviderDialog extends DialogComponentProvider {
private static String lastSelectedProviderDescription;
private List<GhidraScriptProvider> providers; private List<GhidraScriptProvider> providers;
private ListPanel listPanel; private ListPanel listPanel;
private JComponent parent; private JComponent parent;
private boolean wasCancelled; private boolean wasCancelled;
private static String lastSelectedProviderDescription;
PickProviderDialog(JComponent parent, HelpLocation help) { PickProviderDialog(JComponent parent, HelpLocation help) {
super("New Script: Type"); super("New Script: Type");
this.parent = parent; this.parent = parent;
@ -55,6 +55,12 @@ public class PickProviderDialog extends DialogComponentProvider {
setHelpLocation(help); setHelpLocation(help);
} }
/**
* Constructor used in testing only!
*
* @param testItems values to populate model with
* @param defaultItem the default selection
*/
public PickProviderDialog(List<String> testItems, String defaultItem) { public PickProviderDialog(List<String> testItems, String defaultItem) {
super("New Script: Type"); super("New Script: Type");
@ -76,6 +82,8 @@ public class PickProviderDialog extends DialogComponentProvider {
/** /**
* For testing... * For testing...
*
* @param provider the provider selection
*/ */
void setSelectedProvider(GhidraScriptProvider provider) { void setSelectedProvider(GhidraScriptProvider provider) {
listPanel.setSelectedValue(provider); listPanel.setSelectedValue(provider);
@ -95,6 +103,9 @@ public class PickProviderDialog extends DialogComponentProvider {
return (GhidraScriptProvider) listPanel.getSelectedValue(); return (GhidraScriptProvider) listPanel.getSelectedValue();
} }
/**
* close any open dialog
*/
public void dispose() { public void dispose() {
close(); close();
} }

View file

@ -37,6 +37,8 @@ import ghidra.util.HelpLocation;
public class SaveDialog extends DialogComponentProvider implements ListSelectionListener { public class SaveDialog extends DialogComponentProvider implements ListSelectionListener {
protected GhidraScriptComponentProvider componentProvider; protected GhidraScriptComponentProvider componentProvider;
protected ResourceFile scriptFile;
private GhidraScriptProvider provider; private GhidraScriptProvider provider;
private List<ResourceFile> paths; private List<ResourceFile> paths;
@ -44,15 +46,22 @@ public class SaveDialog extends DialogComponentProvider implements ListSelection
private JTextField nameField; private JTextField nameField;
private boolean cancelled; private boolean cancelled;
protected ResourceFile scriptFile; SaveDialog(Component parent, String title, GhidraScriptComponentProvider componentProvider,
ResourceFile scriptFile, HelpLocation help) {
public SaveDialog(Component parent, String title,
GhidraScriptComponentProvider componentProvider, ResourceFile scriptFile,
HelpLocation help) {
this(parent, title, componentProvider, componentProvider.getWritableScriptDirectories(), this(parent, title, componentProvider, componentProvider.getWritableScriptDirectories(),
scriptFile, help); scriptFile, help);
} }
/**
* Only called directly from testing!
*
* @param parent parent component
* @param title dialog title
* @param componentProvider the provider
* @param scriptDirs list of directories to give as options when saving
* @param scriptFile the default save location
* @param help contextual help, e.g. for rename or save
*/
public SaveDialog(Component parent, String title, public SaveDialog(Component parent, String title,
GhidraScriptComponentProvider componentProvider, List<ResourceFile> scriptDirs, GhidraScriptComponentProvider componentProvider, List<ResourceFile> scriptDirs,
ResourceFile scriptFile, HelpLocation help) { ResourceFile scriptFile, HelpLocation help) {

View file

@ -27,7 +27,7 @@ public class ScriptCategoryNode extends GTreeNode {
private final String name; private final String name;
public ScriptCategoryNode(String name) { ScriptCategoryNode(String name) {
this.name = name; this.name = name;
} }

View file

@ -129,6 +129,9 @@ import ghidra.util.task.TaskMonitor;
* @see ghidra.program.model.listing.Program * @see ghidra.program.model.listing.Program
*/ */
public abstract class GhidraScript extends FlatProgramAPI { public abstract class GhidraScript extends FlatProgramAPI {
// Stores last-selected value for askXxx() methods, used to pre-populate askXxx()
// GUI dialogs if they are run more than once
private static Map<String, Map<Class<?>, Object>> askMap = new HashMap<>();
protected ResourceFile sourceFile; protected ResourceFile sourceFile;
protected GhidraState state; protected GhidraState state;
@ -174,16 +177,19 @@ public abstract class GhidraScript extends FlatProgramAPI {
SUSPENDED SUSPENDED
} }
// Stores last-selected value for askXxx() methods, used to pre-populate askXxx()
// GUI dialogs if they are run more than once
private static Map<String, Map<Class<?>, Object>> askMap = new HashMap<>();
/** /**
* The run method is where the script specific code is placed. * The run method is where the script specific code is placed.
* @throws Exception if any exception occurs. * @throws Exception if any exception occurs.
*/ */
protected abstract void run() throws Exception; protected abstract void run() throws Exception;
/**
* Set the context for this script.
*
* @param state state object
* @param monitor the monitor to use during run
* @param writer the target of script "print" statements
*/
public final void set(GhidraState state, TaskMonitor monitor, PrintWriter writer) { public final void set(GhidraState state, TaskMonitor monitor, PrintWriter writer) {
this.state = state; this.state = state;
this.monitor = monitor; this.monitor = monitor;
@ -191,6 +197,14 @@ public abstract class GhidraScript extends FlatProgramAPI {
loadVariablesFromState(); loadVariablesFromState();
} }
/**
* Execute/run script and {@link #doCleanup} afterwards.
*
* @param runState state object
* @param runMonitor the monitor to use during run
* @param runWriter the target of script "print" statements
* @throws Exception if the script excepts
*/
public final void execute(GhidraState runState, TaskMonitor runMonitor, PrintWriter runWriter) public final void execute(GhidraState runState, TaskMonitor runMonitor, PrintWriter runWriter)
throws Exception { throws Exception {
boolean success = false; boolean success = false;
@ -203,8 +217,8 @@ public abstract class GhidraScript extends FlatProgramAPI {
} }
} }
private final void doExecute(GhidraState runState, TaskMonitor runMonitor, private void doExecute(GhidraState runState, TaskMonitor runMonitor, PrintWriter runWriter)
PrintWriter runWriter) throws Exception { throws Exception {
this.state = runState; this.state = runState;
this.monitor = runMonitor; this.monitor = runMonitor;
this.writer = runWriter; this.writer = runWriter;
@ -478,6 +492,11 @@ public abstract class GhidraScript extends FlatProgramAPI {
return state; return state;
} }
/**
* Set the script {@link #currentAddress}, {@link #currentLocation}, and update state object.
*
* @param address the new address
*/
public final void setCurrentLocation(Address address) { public final void setCurrentLocation(Address address) {
state.setCurrentAddress(address); state.setCurrentAddress(address);
this.currentAddress = address; this.currentAddress = address;
@ -545,8 +564,8 @@ public abstract class GhidraScript extends FlatProgramAPI {
if (isRunningHeadless()) { if (isRunningHeadless()) {
// only change client authenticator in headless mode // only change client authenticator in headless mode
try { try {
HeadlessClientAuthenticator.installHeadlessClientAuthenticator( HeadlessClientAuthenticator
ClientUtil.getUserName(), null, false); .installHeadlessClientAuthenticator(ClientUtil.getUserName(), null, false);
} }
catch (IOException e) { catch (IOException e) {
throw new RuntimeException("Unexpected Exception", e); throw new RuntimeException("Unexpected Exception", e);
@ -1408,7 +1427,7 @@ public abstract class GhidraScript extends FlatProgramAPI {
// Tests if text actually equals "true" or "false // Tests if text actually equals "true" or "false
String tempBool = analysisOptionValue.toLowerCase(); String tempBool = analysisOptionValue.toLowerCase();
if (tempBool.equals("true") || tempBool.equals("false")) { if ("true".equals(tempBool) || "false".equals(tempBool)) {
options.setBoolean(analysisOption, Boolean.valueOf(tempBool)); options.setBoolean(analysisOption, Boolean.valueOf(tempBool));
} }
@ -1785,13 +1804,13 @@ public abstract class GhidraScript extends FlatProgramAPI {
} }
else { else {
try { try {
SwingUtilities.invokeAndWait( SwingUtilities
() -> Msg.showInfo(getClass(), null, name, message)); .invokeAndWait(() -> Msg.showInfo(getClass(), null, name, message));
} }
catch (InterruptedException e1) { catch (InterruptedException e) {
// shouldn't happen // shouldn't happen
} }
catch (InvocationTargetException e1) { catch (InvocationTargetException e) {
// shouldn't happen // shouldn't happen
} }
} }
@ -1970,7 +1989,7 @@ public abstract class GhidraScript extends FlatProgramAPI {
} }
private interface CancellableFunction<T, R> { private interface CancellableFunction<T, R> {
public R apply(T t) throws CancelledException; R apply(T t) throws CancelledException;
} }
/** /**
@ -2778,10 +2797,10 @@ public abstract class GhidraScript extends FlatProgramAPI {
* @throws IllegalArgumentException if the parsed value is not a valid double. * @throws IllegalArgumentException if the parsed value is not a valid double.
*/ */
public double parseDouble(String val) { public double parseDouble(String val) {
if (val.equalsIgnoreCase("pi")) { if ("pi".equalsIgnoreCase(val)) {
return Math.PI; return Math.PI;
} }
if (val.equalsIgnoreCase("e")) { if ("e".equalsIgnoreCase(val)) {
return Math.E; return Math.E;
} }
try { try {
@ -3254,7 +3273,7 @@ public abstract class GhidraScript extends FlatProgramAPI {
* @throws IllegalArgumentException if the parsed value is not a valid boolean. * @throws IllegalArgumentException if the parsed value is not a valid boolean.
*/ */
public Boolean parseBoolean(String val) { public Boolean parseBoolean(String val) {
if (val.equalsIgnoreCase("true") || val.equalsIgnoreCase("false")) { if ("true".equalsIgnoreCase(val) || "false".equalsIgnoreCase(val)) {
return Boolean.parseBoolean(val); return Boolean.parseBoolean(val);
} }
throw new IllegalArgumentException("Invalid boolean: " + val); throw new IllegalArgumentException("Invalid boolean: " + val);

View file

@ -28,6 +28,9 @@ public class GhidraScriptInfoManager {
private Map<ResourceFile, ScriptInfo> scriptFileToInfoMap = new HashMap<>(); private Map<ResourceFile, ScriptInfo> scriptFileToInfoMap = new HashMap<>();
private Map<String, List<ResourceFile>> scriptNameToFilesMap = new HashMap<>(); private Map<String, List<ResourceFile>> scriptNameToFilesMap = new HashMap<>();
/**
* clear stored metadata
*/
public void dispose() { public void dispose() {
clearMetadata(); clearMetadata();
} }
@ -110,6 +113,12 @@ public class GhidraScriptInfoManager {
return scriptFileToInfoMap.containsKey(scriptFile); return scriptFileToInfoMap.containsKey(scriptFile);
} }
/**
* Get {@link ScriptInfo} for {@code script} under the assumption that it's already managed.
*
* @param script the script
* @return info or null if the assumption was wrong. If null is returned, an error dialog is shown
*/
public ScriptInfo getExistingScriptInfo(ResourceFile script) { public ScriptInfo getExistingScriptInfo(ResourceFile script) {
ScriptInfo info = scriptFileToInfoMap.get(script); ScriptInfo info = scriptFileToInfoMap.get(script);
if (info == null) { if (info == null) {

View file

@ -33,7 +33,7 @@ public class GhidraScriptProperties {
private HashMap<String, String> propertiesMap; private HashMap<String, String> propertiesMap;
private String baseName; private String baseName;
public GhidraScriptProperties() { GhidraScriptProperties() {
propertiesMap = new HashMap<>(); propertiesMap = new HashMap<>();
} }
@ -73,6 +73,9 @@ public class GhidraScriptProperties {
} }
} }
/**
* @return the properties file name
*/
public String getFilename() { public String getFilename() {
return baseName + ".properties"; return baseName + ".properties";
} }
@ -138,6 +141,10 @@ public class GhidraScriptProperties {
return propertiesMap.put(key.trim(), value); return propertiesMap.put(key.trim(), value);
} }
/**
* @param keyString the property name
* @return the value of the key in the properties file, or an empty string if no property exists
*/
public String getValue(String keyString) { public String getValue(String keyString) {
if (propertiesMap.size() == 0) { if (propertiesMap.size() == 0) {
@ -151,10 +158,19 @@ public class GhidraScriptProperties {
return ""; return "";
} }
/**
* @return true if there are no properties
*/
public boolean isEmpty() { public boolean isEmpty() {
return (propertiesMap.size() == 0); return (propertiesMap.size() == 0);
} }
/**
* Remove the named property
*
* @param keyString the property name
* @return the previous value or null
*/
protected String remove(String keyString) { protected String remove(String keyString) {
return propertiesMap.remove(keyString); return propertiesMap.remove(keyString);
} }
@ -163,14 +179,25 @@ public class GhidraScriptProperties {
propertiesMap.clear(); propertiesMap.clear();
} }
/**
* @param keyString a property name
* @return true if the key exists in the property file
*/
public boolean containsKey(String keyString) { public boolean containsKey(String keyString) {
return propertiesMap.containsKey(keyString); return propertiesMap.containsKey(keyString);
} }
/**
* @param valueString a value string
* @return true if any property has the given value
*/
public boolean containsValue(String valueString) { public boolean containsValue(String valueString) {
return propertiesMap.containsValue(valueString); return propertiesMap.containsValue(valueString);
} }
/**
* @return the property names for all properties
*/
public Set<String> keySet() { public Set<String> keySet() {
return propertiesMap.keySet(); return propertiesMap.keySet();
} }

View file

@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -20,7 +19,7 @@ import generic.jar.ResourceFile;
class GhidraScriptUnsupportedClassVersionError extends RuntimeException { class GhidraScriptUnsupportedClassVersionError extends RuntimeException {
private ResourceFile classFile; private final ResourceFile classFile;
GhidraScriptUnsupportedClassVersionError(UnsupportedClassVersionError cause, GhidraScriptUnsupportedClassVersionError(UnsupportedClassVersionError cause,
ResourceFile classFile) { ResourceFile classFile) {

View file

@ -15,7 +15,8 @@
*/ */
package ghidra.app.script; package ghidra.app.script;
import java.io.*; import java.io.File;
import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -31,31 +32,38 @@ import ghidra.util.classfinder.ClassSearcher;
* A utility class for managing script directories and ScriptInfo objects. * A utility class for managing script directories and ScriptInfo objects.
*/ */
public class GhidraScriptUtil { public class GhidraScriptUtil {
private static final String SCRIPTS_SUBDIR_NAME = "ghidra_scripts";
private static final String DEV_SCRIPTS_SUBDIR_NAME = "developer_scripts";
private static List<GhidraScriptProvider> providers = null;
/** /**
* User's home scripts directory * User's home scripts directory
*/ */
public static String USER_SCRIPTS_DIR = buildUserScriptsDirectory(); public static String USER_SCRIPTS_DIR = buildUserScriptsDirectory();
static BundleHost _bundleHost; private static BundleHost bundleHost;
private static final String SCRIPTS_SUBDIR_NAME = "ghidra_scripts";
private static final String DEV_SCRIPTS_SUBDIR_NAME = "developer_scripts";
private static List<GhidraScriptProvider> providers;
/**
* @return the bundle host used for scripting
*/
public static BundleHost getBundleHost() { public static BundleHost getBundleHost() {
return _bundleHost; return bundleHost;
} }
private static void setBundleHost(BundleHost bundleHost) { /**
if (_bundleHost != null) { * set the bundle host and start the framework
*
* @param aBundleHost the bundle host
*/
private static void setBundleHost(BundleHost aBundleHost) {
if (bundleHost != null) {
throw new RuntimeException("GhidraScriptUtil initialized multiple times!"); throw new RuntimeException("GhidraScriptUtil initialized multiple times!");
} }
try { try {
_bundleHost = bundleHost; bundleHost = aBundleHost;
_bundleHost.startFramework(); bundleHost.startFramework();
} }
catch (OSGiException | IOException e) { catch (OSGiException | IOException e) {
e.printStackTrace(); e.printStackTrace();
@ -66,26 +74,29 @@ public class GhidraScriptUtil {
/** /**
* initialize state of GhidraScriptUtil with user, system paths, and optional extra system paths. * initialize state of GhidraScriptUtil with user, system paths, and optional extra system paths.
* *
* @param bundleHost the host to use * @param aBundleHost the host to use
* @param extraSystemPaths additional system paths for this run, can be null * @param extraSystemPaths additional system paths for this run, can be null
* *
*/ */
public static void initialize(BundleHost bundleHost, List<String> extraSystemPaths) { public static void initialize(BundleHost aBundleHost, List<String> extraSystemPaths) {
setBundleHost(bundleHost); setBundleHost(aBundleHost);
if (extraSystemPaths != null) { if (extraSystemPaths != null) {
for (String path : extraSystemPaths) { for (String path : extraSystemPaths) {
bundleHost.add(new ResourceFile(path), true, true); bundleHost.add(new ResourceFile(path), true, true);
} }
} }
bundleHost.add(GhidraScriptUtil.getUserScriptDirectory(), true, false); bundleHost.add(getUserScriptDirectory(), true, false);
bundleHost.add(GhidraScriptUtil.getSystemScriptPaths(), true, true); bundleHost.add(getSystemScriptPaths(), true, true);
} }
/**
* dispose of the bundle host and providers list
*/
public static void dispose() { public static void dispose() {
if (_bundleHost != null) { if (bundleHost != null) {
_bundleHost.dispose(); bundleHost.dispose();
_bundleHost = null; bundleHost = null;
} }
providers = null; providers = null;
} }
@ -95,8 +106,10 @@ public class GhidraScriptUtil {
* @return a list of the current script directories * @return a list of the current script directories
*/ */
public static List<ResourceFile> getScriptSourceDirectories() { public static List<ResourceFile> getScriptSourceDirectories() {
return _bundleHost.getBundlePaths().stream().filter(ResourceFile::isDirectory).collect( return bundleHost.getBundlePaths()
Collectors.toList()); .stream()
.filter(ResourceFile::isDirectory)
.collect(Collectors.toList());
} }
public static ResourceFile getSourceDirectoryContaining(ResourceFile sourceFile) { public static ResourceFile getSourceDirectoryContaining(ResourceFile sourceFile) {
@ -199,8 +212,10 @@ public class GhidraScriptUtil {
@Deprecated @Deprecated
public static List<ResourceFile> getExplodedCompiledSourceBundlePaths() { public static List<ResourceFile> getExplodedCompiledSourceBundlePaths() {
try { try {
return Files.list(BundleHost.getOsgiDir()).filter(Files::isDirectory).map( return Files.list(BundleHost.getOsgiDir())
x -> new ResourceFile(x.toFile())).collect(Collectors.toList()); .filter(Files::isDirectory)
.map(x -> new ResourceFile(x.toFile()))
.collect(Collectors.toList());
} }
catch (IOException e) { catch (IOException e) {
Msg.showError(GhidraScriptUtil.class, null, "error", Msg.showError(GhidraScriptUtil.class, null, "error",
@ -231,7 +246,7 @@ public class GhidraScriptUtil {
* @return a list of all Ghidra script providers * @return a list of all Ghidra script providers
*/ */
// Note: this method is synchronized so that two threads do not try to create the list when null // Note: this method is synchronized so that two threads do not try to create the list when null
public synchronized static List<GhidraScriptProvider> getProviders() { public static synchronized List<GhidraScriptProvider> getProviders() {
if (providers == null) { if (providers == null) {
List<GhidraScriptProvider> newProviders = List<GhidraScriptProvider> newProviders =
new ArrayList<>(ClassSearcher.getInstances(GhidraScriptProvider.class)); new ArrayList<>(ClassSearcher.getInstances(GhidraScriptProvider.class));
@ -338,8 +353,7 @@ public class GhidraScriptUtil {
return path + ".java"; return path + ".java";
} }
static ResourceFile findScriptFileInPaths( static ResourceFile findScriptFileInPaths(Collection<ResourceFile> scriptDirectories,
Collection<ResourceFile> scriptDirectories,
String filename) { String filename) {
String validatedName = fixupName(filename); String validatedName = fixupName(filename);

View file

@ -41,7 +41,7 @@ public class GhidraState {
private ProgramSelection currentSelection; private ProgramSelection currentSelection;
private ProgramSelection currentHighlight; private ProgramSelection currentHighlight;
private HashMap<String, Object> envmap = new HashMap<>(); private HashMap<String, Object> envmap = new HashMap<>();
private GatherParamPanel gatherParamPanel = null; private GatherParamPanel gatherParamPanel;
private Project project; private Project project;
private final boolean isGlobalState; private final boolean isGlobalState;
@ -105,6 +105,7 @@ public class GhidraState {
/** /**
* Sets the current program. * Sets the current program.
* @param program the new program object
*/ */
public void setCurrentProgram(Program program) { public void setCurrentProgram(Program program) {
if (program == currentProgram) { if (program == currentProgram) {
@ -117,11 +118,19 @@ public class GhidraState {
gatherParamPanel.currentProgramChanged(); gatherParamPanel.currentProgramChanged();
} }
/**
* @return the address of the current location
*/
public Address getCurrentAddress() { public Address getCurrentAddress() {
return currentLocation != null ? currentLocation.getAddress() : null; return currentLocation != null ? currentLocation.getAddress() : null;
} }
/**
* If it differs, set the current location to the given address and fire a {@link ProgramLocationPluginEvent}.
*
* @param address the address
*/
public void setCurrentAddress(Address address) { public void setCurrentAddress(Address address) {
if (SystemUtilities.isEqual(address, getCurrentAddress())) { if (SystemUtilities.isEqual(address, getCurrentAddress())) {
return; return;
@ -129,10 +138,18 @@ public class GhidraState {
setCurrentLocation(new ProgramLocation(currentProgram, address)); setCurrentLocation(new ProgramLocation(currentProgram, address));
} }
/**
* @return the current location
*/
public ProgramLocation getCurrentLocation() { public ProgramLocation getCurrentLocation() {
return currentLocation; return currentLocation;
} }
/**
* If it differs, set the current location and fire a {@link ProgramLocationPluginEvent}.
*
* @param location
*/
public void setCurrentLocation(ProgramLocation location) { public void setCurrentLocation(ProgramLocation location) {
if (SystemUtilities.isEqual(currentLocation, location)) { if (SystemUtilities.isEqual(currentLocation, location)) {
return; return;
@ -144,10 +161,18 @@ public class GhidraState {
} }
} }
/**
* @return the currently highlighted selection
*/
public ProgramSelection getCurrentHighlight() { public ProgramSelection getCurrentHighlight() {
return currentHighlight; return currentHighlight;
} }
/**
* Set the currently highlighted selection and fire a {@link ProgramHighlightPluginEvent}.
*
* @param highlight the selection
*/
public void setCurrentHighlight(ProgramSelection highlight) { public void setCurrentHighlight(ProgramSelection highlight) {
if (SystemUtilities.isEqual(currentHighlight, highlight)) { if (SystemUtilities.isEqual(currentHighlight, highlight)) {
return; return;
@ -162,10 +187,18 @@ public class GhidraState {
} }
} }
/**
* @return the current selection
*/
public ProgramSelection getCurrentSelection() { public ProgramSelection getCurrentSelection() {
return currentSelection; return currentSelection;
} }
/**
* Set the current selection and fire a {@link kProgramSelectionPluginEvent}.
*
* @param selection the selection
*/
public void setCurrentSelection(ProgramSelection selection) { public void setCurrentSelection(ProgramSelection selection) {
if (SystemUtilities.isEqual(currentSelection, selection)) { if (SystemUtilities.isEqual(currentSelection, selection)) {
return; return;
@ -181,27 +214,27 @@ public class GhidraState {
} }
public void addEnvironmentVar(String name, byte value) { public void addEnvironmentVar(String name, byte value) {
envmap.put(name, new Byte(value)); envmap.put(name, Byte.valueOf(value));
} }
public void addEnvironmentVar(String name, short value) { public void addEnvironmentVar(String name, short value) {
envmap.put(name, new Short(value)); envmap.put(name, Short.valueOf(value));
} }
public void addEnvironmentVar(String name, int value) { public void addEnvironmentVar(String name, int value) {
envmap.put(name, new Integer(value)); envmap.put(name, Integer.valueOf(value));
} }
public void addEnvironmentVar(String name, long value) { public void addEnvironmentVar(String name, long value) {
envmap.put(name, new Long(value)); envmap.put(name, Long.valueOf(value));
} }
public void addEnvironmentVar(String name, float value) { public void addEnvironmentVar(String name, float value) {
envmap.put(name, new Float(value)); envmap.put(name, Float.valueOf(value));
} }
public void addEnvironmentVar(String name, double value) { public void addEnvironmentVar(String name, double value) {
envmap.put(name, new Double(value)); envmap.put(name, Double.valueOf(value));
} }
public void addEnvironmentVar(String name, Object value) { public void addEnvironmentVar(String name, Object value) {

View file

@ -24,18 +24,27 @@ import ghidra.app.plugin.core.osgi.*;
import ghidra.util.Msg; import ghidra.util.Msg;
public class JavaScriptProvider extends GhidraScriptProvider { public class JavaScriptProvider extends GhidraScriptProvider {
final private BundleHost _bundleHost; private final BundleHost bundleHost;
/**
* Create a new {@link JavaScriptProvider} associated with the current bundle host used by scripting.
*/
public JavaScriptProvider() { public JavaScriptProvider() {
_bundleHost = GhidraScriptUtil.getBundleHost(); bundleHost = GhidraScriptUtil.getBundleHost();
} }
/**
* Get the {@link GhidraSourceBundle} containing the given source file, assuming it already exists.
*
* @param sourceFile the source file
* @return the bundle
*/
public GhidraSourceBundle getBundleForSource(ResourceFile sourceFile) { public GhidraSourceBundle getBundleForSource(ResourceFile sourceFile) {
ResourceFile sourceDir = GhidraScriptUtil.getSourceDirectoryContaining(sourceFile); ResourceFile sourceDir = GhidraScriptUtil.getSourceDirectoryContaining(sourceFile);
if (sourceDir == null) { if (sourceDir == null) {
return null; return null;
} }
return (GhidraSourceBundle) _bundleHost.getExistingGhidraBundle(sourceDir); return (GhidraSourceBundle) bundleHost.getExistingGhidraBundle(sourceDir);
} }
@Override @Override
@ -53,7 +62,7 @@ public class JavaScriptProvider extends GhidraScriptProvider {
try { try {
Bundle osgiBundle = getBundleForSource(sourceFile).getOSGiBundle(); Bundle osgiBundle = getBundleForSource(sourceFile).getOSGiBundle();
if (osgiBundle != null) { if (osgiBundle != null) {
_bundleHost.deactivateSynchronously(osgiBundle); bundleHost.deactivateSynchronously(osgiBundle);
} }
} }
catch (GhidraBundleException | InterruptedException e) { catch (GhidraBundleException | InterruptedException e) {
@ -92,13 +101,22 @@ public class JavaScriptProvider extends GhidraScriptProvider {
} }
} }
/**
* Activate and build the {@link GhidraSourceBundle} containing {@code sourceFile}
* then load the script's class from its class loader.
*
* @param sourceFile the source file
* @param writer the target for build messages
* @return the loaded {@link Class} object
* @throws Exception if build, activation, or class loading fail
*/
public Class<?> loadClass(ResourceFile sourceFile, PrintWriter writer) throws Exception { public Class<?> loadClass(ResourceFile sourceFile, PrintWriter writer) throws Exception {
GhidraSourceBundle gb = getBundleForSource(sourceFile); GhidraSourceBundle gb = getBundleForSource(sourceFile);
gb.build(writer); gb.build(writer);
Bundle b = _bundleHost.install(gb);
_bundleHost.activateSynchronously(b); Bundle b = bundleHost.install(gb);
bundleHost.activateSynchronously(b);
String classname = gb.classNameForScript(sourceFile); String classname = gb.classNameForScript(sourceFile);
Class<?> clazz = b.loadClass(classname); // throws ClassNotFoundException Class<?> clazz = b.loadClass(classname); // throws ClassNotFoundException

View file

@ -34,11 +34,18 @@ public class ResourceFileJavaFileManager implements JavaFileManager {
private StandardJavaFileManager fileManager; private StandardJavaFileManager fileManager;
private List<ResourceFile> sourceDirs; private List<ResourceFile> sourceDirs;
private Set<ResourceFile> avoid; private Set<ResourceFile> filesToAvoid;
public ResourceFileJavaFileManager(List<ResourceFile> sourceDirs, Set<ResourceFile> avoid) { /**
* Create a {@link JavaFileManager} for use by the {@link JavaCompiler}.
*
* @param sourceDirs the directories containing source
* @param filesToAvoid known "bad" files to hide from the compiler
*/
public ResourceFileJavaFileManager(List<ResourceFile> sourceDirs,
Set<ResourceFile> filesToAvoid) {
this.sourceDirs = sourceDirs; this.sourceDirs = sourceDirs;
this.avoid = avoid; this.filesToAvoid = filesToAvoid;
JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler(); JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
if (javaCompiler == null) { if (javaCompiler == null) {
throw new AssertException("Can't find java compiler"); throw new AssertException("Can't find java compiler");
@ -79,7 +86,7 @@ public class ResourceFileJavaFileManager implements JavaFileManager {
private void gatherFiles(ResourceFile root, ResourceFile file, List<JavaFileObject> accumulator, private void gatherFiles(ResourceFile root, ResourceFile file, List<JavaFileObject> accumulator,
Set<Kind> kinds, boolean recurse) { Set<Kind> kinds, boolean recurse) {
List<ResourceFile> listFiles = new ArrayList<>(Arrays.asList(file.listFiles())); List<ResourceFile> listFiles = new ArrayList<>(Arrays.asList(file.listFiles()));
listFiles.removeAll(avoid); listFiles.removeAll(filesToAvoid);
for (ResourceFile resourceFile : listFiles) { for (ResourceFile resourceFile : listFiles) {
if (resourceFile.isDirectory()) { if (resourceFile.isDirectory()) {
if (recurse) { if (recurse) {
@ -154,7 +161,7 @@ public class ResourceFileJavaFileManager implements JavaFileManager {
@Override @Override
public JavaFileObject getJavaFileForInput(Location location, String className, Kind kind) public JavaFileObject getJavaFileForInput(Location location, String className, Kind kind)
throws IOException { throws IOException {
if (!location.equals(StandardLocation.SOURCE_PATH) || className.equals("module-info")) { if (!location.equals(StandardLocation.SOURCE_PATH) || "module-info".equals(className)) {
// Our Ghidra scripts will not use Java 9's module definition file (module-info.java). // Our Ghidra scripts will not use Java 9's module definition file (module-info.java).
return fileManager.getJavaFileForInput(location, className, kind); return fileManager.getJavaFileForInput(location, className, kind);
} }

View file

@ -20,6 +20,7 @@ import java.net.URI;
import javax.lang.model.element.Modifier; import javax.lang.model.element.Modifier;
import javax.lang.model.element.NestingKind; import javax.lang.model.element.NestingKind;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject; import javax.tools.JavaFileObject;
import generic.jar.ResourceFile; import generic.jar.ResourceFile;
@ -35,6 +36,13 @@ public class ResourceFileJavaFileObject implements JavaFileObject {
private String pathName; private String pathName;
private Kind kind; private Kind kind;
/**
* Represents a {@link ResourceFile} for a {@link JavaCompiler} via a {@link ResourceFileJavaFileManager}
*
* @param sourceRoot the root source directory
* @param file the file
* @param kind the kind
*/
public ResourceFileJavaFileObject(ResourceFile sourceRoot, ResourceFile file, Kind kind) { public ResourceFileJavaFileObject(ResourceFile sourceRoot, ResourceFile file, Kind kind) {
this.file = file; this.file = file;
this.kind = kind; this.kind = kind;
@ -43,6 +51,9 @@ public class ResourceFileJavaFileObject implements JavaFileObject {
pathName = file.getAbsolutePath().substring(sourceRootPath.length() + 1); pathName = file.getAbsolutePath().substring(sourceRootPath.length() + 1);
} }
/**
* @return the {@link ResourceFile} this object represents
*/
public ResourceFile getFile() { public ResourceFile getFile() {
return file; return file;
} }

View file

@ -36,12 +36,8 @@ import resources.ResourceManager;
* This class parses the meta-data about a script. * This class parses the meta-data about a script.
*/ */
public class ScriptInfo { public class ScriptInfo {
private static final Pattern DOCUMENTATION_START = Pattern.compile("/\\*");
private static final Pattern DOCUMENTATION_END = Pattern.compile("\\*/");
/** /**
* The delimiter used to categories and menu paths. * The delimiter used in categories and menu paths.
*/ */
public static final String DELIMITTER = "."; public static final String DELIMITTER = ".";
@ -51,6 +47,9 @@ public class ScriptInfo {
static final String AT_MENUPATH = "@menupath"; static final String AT_MENUPATH = "@menupath";
static final String AT_TOOLBAR = "@toolbar"; static final String AT_TOOLBAR = "@toolbar";
private static final Pattern DOCUMENTATION_START = Pattern.compile("/\\*");
private static final Pattern DOCUMENTATION_END = Pattern.compile("\\*/");
// omit from METADATA to avoid pre-populating in new scripts // omit from METADATA to avoid pre-populating in new scripts
private static final String AT_IMPORTPACKAGE = "@importpackage"; private static final String AT_IMPORTPACKAGE = "@importpackage";
@ -231,7 +230,7 @@ public class ScriptInfo {
description = buffer.toString(); description = buffer.toString();
modified = sourceFile.lastModified(); modified = sourceFile.lastModified();
} }
catch (Exception e) { catch (IOException e) {
Msg.debug(this, "Unexpected exception reading script: " + sourceFile, e); Msg.debug(this, "Unexpected exception reading script: " + sourceFile, e);
} }
finally { finally {
@ -403,6 +402,9 @@ public class ScriptInfo {
return keyBinding; return keyBinding;
} }
/**
* @return an error resulting from parsing keybinding metadata
*/
public String getKeyBindingErrorMessage() { public String getKeyBindingErrorMessage() {
return keybindingErrorMessage; return keybindingErrorMessage;
} }
@ -460,7 +462,6 @@ public class ScriptInfo {
String space = HTML_SPACE; String space = HTML_SPACE;
String htmlAuthor = bold("Author:") + space + escapeHTML(toString(author)); String htmlAuthor = bold("Author:") + space + escapeHTML(toString(author));
String htmlCategory = bold("Category:") + space + escapeHTML(toString(category)); String htmlCategory = bold("Category:") + space + escapeHTML(toString(category));
String htmlKeyBinding = bold("Key Binding:") + space + getKeybindingToolTip(); String htmlKeyBinding = bold("Key Binding:") + space + getKeybindingToolTip();
String htmlMenuPath = bold("Menu Path:") + space + escapeHTML(toString(menupath)); String htmlMenuPath = bold("Menu Path:") + space + escapeHTML(toString(menupath));
@ -505,10 +506,16 @@ public class ScriptInfo {
return StringUtils.defaultString(joined); return StringUtils.defaultString(joined);
} }
/**
* @return true if the script either has compiler errors, or is a duplicate
*/
public boolean hasErrors() { public boolean hasErrors() {
return isCompileErrors() || isDuplicate(); return isCompileErrors() || isDuplicate();
} }
/**
* @return a generic error message
*/
public String getErrorMessage() { public String getErrorMessage() {
if (isCompileErrors()) { if (isCompileErrors()) {
return "Script contains compiler errors"; return "Script contains compiler errors";

View file

@ -16,6 +16,5 @@
package ghidra.app.script; package ghidra.app.script;
public interface StringTransformer<T> { public interface StringTransformer<T> {
T apply(String s);
public T apply(String s);
} }

View file

@ -174,11 +174,11 @@ public abstract class AbstractGhidraScriptMgrPluginTest
env.dispose(); env.dispose();
} }
static protected void wipe(ResourceFile path) throws IOException { protected static void wipe(ResourceFile path) throws IOException {
wipe(Paths.get(path.getAbsolutePath())); wipe(Paths.get(path.getAbsolutePath()));
} }
static protected void wipe(Path path) throws IOException { protected static void wipe(Path path) throws IOException {
if (Files.exists(path)) { if (Files.exists(path)) {
for (Path p : (Iterable<Path>) Files.walk(path) for (Path p : (Iterable<Path>) Files.walk(path)
.sorted(Comparator.reverseOrder())::iterator) { .sorted(Comparator.reverseOrder())::iterator) {
@ -392,7 +392,7 @@ public abstract class AbstractGhidraScriptMgrPluginTest
assertNotNull(editor); assertNotNull(editor);
editorTextArea = (JTextArea) findComponentByName(editor.getComponent(), "EDITOR"); editorTextArea = (JTextArea) findComponentByName(editor.getComponent(), GhidraScriptEditorComponentProvider.EDITOR_COMPONENT_NAME);
assertNotNull(editorTextArea); assertNotNull(editorTextArea);
buffer = new StringBuffer(editorTextArea.getText()); buffer = new StringBuffer(editorTextArea.getText());
@ -411,7 +411,7 @@ public abstract class AbstractGhidraScriptMgrPluginTest
// initialize our editor variable to the newly opened editor // initialize our editor variable to the newly opened editor
editor = waitForComponentProvider(GhidraScriptEditorComponentProvider.class); editor = waitForComponentProvider(GhidraScriptEditorComponentProvider.class);
editorTextArea = (JTextArea) findComponentByName(editor.getComponent(), "EDITOR"); editorTextArea = (JTextArea) findComponentByName(editor.getComponent(), GhidraScriptEditorComponentProvider.EDITOR_COMPONENT_NAME);
waitForSwing(); waitForSwing();
@ -850,7 +850,7 @@ public abstract class AbstractGhidraScriptMgrPluginTest
Map<ResourceFile, GhidraScriptEditorComponentProvider> map = provider.getEditorMap(); Map<ResourceFile, GhidraScriptEditorComponentProvider> map = provider.getEditorMap();
GhidraScriptEditorComponentProvider fileEditor = map.get(file); GhidraScriptEditorComponentProvider fileEditor = map.get(file);
final JTextArea textArea = final JTextArea textArea =
(JTextArea) findComponentByName(fileEditor.getComponent(), "EDITOR"); (JTextArea) findComponentByName(fileEditor.getComponent(), GhidraScriptEditorComponentProvider.EDITOR_COMPONENT_NAME);
assertNotNull(textArea); assertNotNull(textArea);
final String[] box = new String[1]; final String[] box = new String[1];
@ -1164,7 +1164,7 @@ public abstract class AbstractGhidraScriptMgrPluginTest
return info.getSourceFile(); return info.getSourceFile();
} }
static protected String CANCELLABLE_SCRIPT_NAME = TestChangeProgramScript.class.getName(); protected static String CANCELLABLE_SCRIPT_NAME = TestChangeProgramScript.class.getName();
protected void cancel() throws Exception { protected void cancel() throws Exception {
Window window = waitForWindowByTitleContaining(CANCELLABLE_SCRIPT_NAME); Window window = waitForWindowByTitleContaining(CANCELLABLE_SCRIPT_NAME);
@ -1494,7 +1494,7 @@ public abstract class AbstractGhidraScriptMgrPluginTest
protected JTextComponent grabScriptEditorTextArea() { protected JTextComponent grabScriptEditorTextArea() {
GhidraScriptEditorComponentProvider scriptEditor = grabScriptEditor(); GhidraScriptEditorComponentProvider scriptEditor = grabScriptEditor();
JTextArea textArea = (JTextArea) findComponentByName(scriptEditor.getComponent(), "EDITOR"); JTextArea textArea = (JTextArea) findComponentByName(scriptEditor.getComponent(), GhidraScriptEditorComponentProvider.EDITOR_COMPONENT_NAME);
assertNotNull(textArea); assertNotNull(textArea);
return textArea; return textArea;
} }
@ -1596,9 +1596,9 @@ public abstract class AbstractGhidraScriptMgrPluginTest
} }
} }
} }
catch (CancelledException ce) { catch (CancelledException e) {
doneLatch.countDown(); doneLatch.countDown();
throw ce; throw e;
} }
doneLatch.countDown(); doneLatch.countDown();

View file

@ -119,7 +119,7 @@ public class GhidraScriptMgrPluginScreenShots extends GhidraScreenShotGenerator
BundleStatusComponentProvider bundleStatusComponentProvider = BundleStatusComponentProvider bundleStatusComponentProvider =
showProvider(BundleStatusComponentProvider.class); showProvider(BundleStatusComponentProvider.class);
bundleStatusComponentProvider.getModel().setPathsForTesting(paths); bundleStatusComponentProvider.setPathsForTesting(paths);
waitForComponentProvider(BundleStatusComponentProvider.class); waitForComponentProvider(BundleStatusComponentProvider.class);
captureComponent(bundleStatusComponentProvider.getComponent()); captureComponent(bundleStatusComponentProvider.getComponent());