don't assume that scripts are Java, fixes #2562

This commit is contained in:
Jason P. Leasure 2021-01-04 08:50:47 -05:00
parent b9129348fc
commit e4e15cdb9d
4 changed files with 93 additions and 30 deletions

View file

@ -133,4 +133,19 @@ public abstract class GhidraScriptProvider
protected void writeBody(PrintWriter writer) { protected void writeBody(PrintWriter writer) {
writer.println(getCommentCharacter() + "TODO Add User Code Here"); writer.println(getCommentCharacter() + "TODO Add User Code Here");
} }
/**
* Fixup a script name for searching in script directories.
*
* <p>This method is part of a poorly specified behavior that is due for future amendment,
* see {@link GhidraScriptUtil#fixupName(String)}.
*
* @param scriptName the name of the script, must end with this provider's extension
* @return a (relative) file path to the corresponding script
*/
@Deprecated
protected String fixupName(String scriptName) {
return scriptName;
}
} }

View file

@ -28,6 +28,7 @@ import generic.jar.ResourceFile;
import ghidra.app.plugin.core.osgi.BundleHost; import ghidra.app.plugin.core.osgi.BundleHost;
import ghidra.app.plugin.core.osgi.OSGiException; import ghidra.app.plugin.core.osgi.OSGiException;
import ghidra.app.plugin.core.script.GhidraScriptMgrPlugin; import ghidra.app.plugin.core.script.GhidraScriptMgrPlugin;
import ghidra.app.util.headless.HeadlessAnalyzer;
import ghidra.framework.Application; import ghidra.framework.Application;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.classfinder.ClassSearcher; import ghidra.util.classfinder.ClassSearcher;
@ -139,8 +140,7 @@ public class GhidraScriptUtil {
} }
catch (IOException e) { catch (IOException e) {
Msg.error(GhidraScriptUtil.class, Msg.error(GhidraScriptUtil.class,
"Failed to find script in any script directory: " + sourceFile.toString(), "Failed to find script in any script directory: " + sourceFile.toString(), e);
e);
} }
return null; return null;
} }
@ -293,14 +293,7 @@ public class GhidraScriptUtil {
* @return the Ghidra script provider * @return the Ghidra script provider
*/ */
public static GhidraScriptProvider getProvider(ResourceFile scriptFile) { public static GhidraScriptProvider getProvider(ResourceFile scriptFile) {
String scriptFileName = scriptFile.getName().toLowerCase(); return findProvider(scriptFile.getName());
for (GhidraScriptProvider provider : getProviders()) {
if (scriptFileName.endsWith(provider.getExtension().toLowerCase())) {
return provider;
}
}
return null;
} }
/** /**
@ -310,13 +303,23 @@ public class GhidraScriptUtil {
* @return true if a provider exists that can process the specified file * @return true if a provider exists that can process the specified file
*/ */
public static boolean hasScriptProvider(ResourceFile scriptFile) { public static boolean hasScriptProvider(ResourceFile scriptFile) {
String scriptFileName = scriptFile.getName().toLowerCase(); return findProvider(scriptFile.getName()) != null;
}
/**
* Find the provider whose extension matches the given filename extension.
*
* @param fileName name of script file
* @return the first matching provider or null if no provider matches
*/
private static GhidraScriptProvider findProvider(String fileName) {
fileName = fileName.toLowerCase();
for (GhidraScriptProvider provider : getProviders()) { for (GhidraScriptProvider provider : getProviders()) {
if (scriptFileName.endsWith(provider.getExtension().toLowerCase())) { if (fileName.endsWith(provider.getExtension().toLowerCase())) {
return true; return provider;
} }
} }
return false; return null;
} }
/** /**
@ -362,31 +365,35 @@ public class GhidraScriptUtil {
} }
/** /**
* Fixup name issues, such as package parts in the name and inner class names. * Fix script name issues for searching in script directories.
* If no provider can be identified, Java is assumed.
* *
* <p>This method can handle names with or without '.java' at the end; names with * <p>This method is part of a poorly specified behavior that is due for future amendment.
* '$' (inner classes) and names with '.' characters for package separators
* *
* <p>It is used by {@link GhidraScript#runScript(String)} methods,
* {@link #createNewScript(String, String, ResourceFile, List)}, and by {@link HeadlessAnalyzer} for
* {@code preScript} and {@code postScript}. The intent was to allow some freedom in how a user specifies
* a script in two ways: 1) if the extension is omitted ".java" is assumed and 2) if a Java class name is
* given it's converted to a relative path.
*
* @param name the name of the script * @param name the name of the script
* @return the name as a '.java' file path (with '/'s and not '.'s) * @return the name as a file path
*/ */
@Deprecated
static String fixupName(String name) { static String fixupName(String name) {
if (name.endsWith(".java")) { GhidraScriptProvider provider = findProvider(name);
name = name.substring(0, name.length() - 5); // assume Java if no provider matched
if (provider == null) {
name = name + ".java";
provider = findProvider(".java");
} }
return provider.fixupName(name);
String path = name.replace('.', '/');
int innerClassIndex = path.indexOf('$');
if (innerClassIndex != -1) {
path = path.substring(0, innerClassIndex);
}
return path + ".java";
} }
static ResourceFile findScriptFileInPaths(Collection<ResourceFile> scriptDirectories, static ResourceFile findScriptFileInPaths(Collection<ResourceFile> scriptDirectories,
String filename) { String name) {
String validatedName = fixupName(filename); String validatedName = fixupName(name);
for (ResourceFile resourceFile : scriptDirectories) { for (ResourceFile resourceFile : scriptDirectories) {
if (resourceFile.isDirectory()) { if (resourceFile.isDirectory()) {

View file

@ -164,4 +164,29 @@ public class JavaScriptProvider extends GhidraScriptProvider {
return "//"; return "//";
} }
/**
*
* Fix script name for search in script directories, such as Java package parts in the name and inner class names.
*
* <p>This method can handle names with '$' (inner classes) and names with '.'
* characters for package separators
*
* <p>It is part of a poorly specified behavior that is due for future amendment,
* see {@link GhidraScriptUtil#fixupName(String)}.
*
* @param scriptName the name of the script
* @return the name as a '.java' file path (with '/'s and not '.'s)
*/
@Override
protected String fixupName(String scriptName) {
scriptName = scriptName.substring(0, scriptName.length() - 5);
String path = scriptName.replace('.', '/');
int innerClassIndex = path.indexOf('$');
if (innerClassIndex != -1) {
path = path.substring(0, innerClassIndex);
}
return path + ".java";
}
} }

View file

@ -15,14 +15,23 @@
*/ */
package ghidra.app.script; package ghidra.app.script;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import generic.test.AbstractGenericTest; import generic.test.AbstractGenericTest;
import ghidra.util.classfinder.ClassSearcher;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.ConsoleTaskMonitor;
public class GhidraScriptUtilTest extends AbstractGenericTest { public class GhidraScriptUtilTest extends AbstractGenericTest {
@Before
public void setup() throws CancelledException {
ClassSearcher.search(false, new ConsoleTaskMonitor());
}
@Test @Test
public void fixupName_WithExtension() { public void fixupName_WithExtension() {
String input = "Bob.java"; String input = "Bob.java";
@ -58,4 +67,11 @@ public class GhidraScriptUtilTest extends AbstractGenericTest {
String input = "a.b.c.Bob$InnerClass"; String input = "a.b.c.Bob$InnerClass";
assertEquals(GhidraScriptUtil.fixupName(input), "a/b/c/Bob.java"); assertEquals(GhidraScriptUtil.fixupName(input), "a/b/c/Bob.java");
} }
@Test
public void fixupName_Python() {
String input = "Bob.py";
assertEquals(GhidraScriptUtil.fixupName(input), "Bob.py");
}
} }