mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-06 03:50:02 +02:00
Merge remote-tracking branch
'origin/GP-843_dragonmacher_PR-2846_goatshriek_python-header' (Closes #2846)
This commit is contained in:
commit
76a73095df
7 changed files with 599 additions and 58 deletions
|
@ -17,6 +17,7 @@ package ghidra.app.script;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import generic.jar.ResourceFile;
|
import generic.jar.ResourceFile;
|
||||||
import ghidra.util.classfinder.ExtensionPoint;
|
import ghidra.util.classfinder.ExtensionPoint;
|
||||||
|
@ -93,6 +94,24 @@ public abstract class GhidraScriptProvider
|
||||||
public abstract void createNewScript(ResourceFile newScript, String category)
|
public abstract void createNewScript(ResourceFile newScript, String category)
|
||||||
throws IOException;
|
throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a Pattern that matches block comment openings.
|
||||||
|
* If block comments are not supported by this provider, then this returns null.
|
||||||
|
* @return the Pattern for block comment openings, null if block comments are not supported
|
||||||
|
*/
|
||||||
|
public Pattern getBlockCommentStart() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a Pattern that matches block comment closings.
|
||||||
|
* If block comments are not supported by this provider, then this returns null.
|
||||||
|
* @return the Pattern for block comment closings, null if block comments are not supported
|
||||||
|
*/
|
||||||
|
public Pattern getBlockCommentEnd() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the comment character.
|
* Returns the comment character.
|
||||||
* For example, "//" or "#".
|
* For example, "//" or "#".
|
||||||
|
|
|
@ -17,6 +17,7 @@ package ghidra.app.script;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.osgi.framework.Bundle;
|
import org.osgi.framework.Bundle;
|
||||||
|
|
||||||
|
@ -26,6 +27,9 @@ import ghidra.util.Msg;
|
||||||
import ghidra.util.task.TaskMonitor;
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
public class JavaScriptProvider extends GhidraScriptProvider {
|
public class JavaScriptProvider extends GhidraScriptProvider {
|
||||||
|
private static final Pattern BLOCK_COMMENT_START = Pattern.compile("/\\*");
|
||||||
|
private static final Pattern BLOCK_COMMENT_END = Pattern.compile("\\*/");
|
||||||
|
|
||||||
private final BundleHost bundleHost;
|
private final BundleHost bundleHost;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -159,6 +163,26 @@ public class JavaScriptProvider extends GhidraScriptProvider {
|
||||||
writer.close();
|
writer.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a Pattern that matches block comment openings.
|
||||||
|
* For Java this is "/*".
|
||||||
|
* @return the Pattern for Java block comment openings
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Pattern getBlockCommentStart() {
|
||||||
|
return BLOCK_COMMENT_START;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a Pattern that matches block comment closings.
|
||||||
|
* In Java this is an asterisk followed by a forward slash.
|
||||||
|
* @return the Pattern for Java block comment closings
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Pattern getBlockCommentEnd() {
|
||||||
|
return BLOCK_COMMENT_END;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getCommentCharacter() {
|
public String getCommentCharacter() {
|
||||||
return "//";
|
return "//";
|
||||||
|
|
|
@ -20,6 +20,8 @@ import static ghidra.util.HTMLUtilities.*;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.StringTokenizer;
|
import java.util.StringTokenizer;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import javax.swing.ImageIcon;
|
import javax.swing.ImageIcon;
|
||||||
import javax.swing.KeyStroke;
|
import javax.swing.KeyStroke;
|
||||||
|
@ -187,60 +189,39 @@ public class ScriptInfo {
|
||||||
// Note that skipping certification header presumes that the header
|
// Note that skipping certification header presumes that the header
|
||||||
// is intact with an appropriate start and end
|
// is intact with an appropriate start and end
|
||||||
String certifyHeaderStart = provider.getCertifyHeaderStart();
|
String certifyHeaderStart = provider.getCertifyHeaderStart();
|
||||||
String certifyHeaderEnd = provider.getCertifyHeaderEnd();
|
|
||||||
String certifyHeaderBodyPrefix = provider.getCertificationBodyPrefix();
|
|
||||||
boolean allowCertifyHeader = (certifyHeaderStart != null);
|
boolean allowCertifyHeader = (certifyHeaderStart != null);
|
||||||
boolean skipCertifyHeader = false;
|
|
||||||
|
|
||||||
BufferedReader reader = null;
|
try (BufferedReader reader =
|
||||||
try {
|
new BufferedReader(new InputStreamReader(sourceFile.getInputStream()))) {
|
||||||
StringBuffer buffer = new StringBuffer();
|
StringBuilder buffer = new StringBuilder();
|
||||||
boolean hitAtSign = false;
|
boolean hitAtSign = false;
|
||||||
reader = new BufferedReader(new InputStreamReader(sourceFile.getInputStream()));
|
|
||||||
while (true) {
|
while (true) {
|
||||||
String line = reader.readLine();
|
String line = reader.readLine();
|
||||||
if (line == null) {
|
if (line == null) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allowCertifyHeader) {
|
if (allowCertifyHeader && skipCertifyHeader(reader, line)) {
|
||||||
// Skip past certification header if found
|
|
||||||
if (skipCertifyHeader) {
|
|
||||||
String trimLine = line.trim();
|
|
||||||
if (trimLine.startsWith(certifyHeaderEnd)) {
|
|
||||||
allowCertifyHeader = false;
|
allowCertifyHeader = false;
|
||||||
skipCertifyHeader = false;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (certifyHeaderBodyPrefix == null ||
|
|
||||||
trimLine.startsWith(certifyHeaderBodyPrefix)) {
|
if (parseBlockComment(reader, line)) {
|
||||||
continue; // skip certification header body
|
|
||||||
}
|
|
||||||
// broken certification header - unexpected line
|
|
||||||
Msg.error(this,
|
|
||||||
"Script contains invalid certification header: " + getName());
|
|
||||||
allowCertifyHeader = false;
|
allowCertifyHeader = false;
|
||||||
skipCertifyHeader = false;
|
continue; // read block comment; move to next line
|
||||||
}
|
|
||||||
else if (line.startsWith(certifyHeaderStart)) {
|
|
||||||
skipCertifyHeader = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (line.startsWith(commentPrefix)) {
|
if (line.startsWith(commentPrefix)) {
|
||||||
allowCertifyHeader = false;
|
allowCertifyHeader = false;
|
||||||
|
|
||||||
line = line.substring(commentPrefix.length()).trim();
|
line = line.substring(commentPrefix.length()).trim();
|
||||||
|
|
||||||
if (line.startsWith("@")) {
|
if (line.startsWith("@")) {
|
||||||
hitAtSign = true;
|
hitAtSign = true;
|
||||||
parseMetaDataLine(line);
|
parseMetaDataLine(line);
|
||||||
}
|
}
|
||||||
else if (!hitAtSign) {
|
else if (!hitAtSign) {
|
||||||
buffer.append(line);
|
// only consume line comments that come before metadata
|
||||||
buffer.append(' ');
|
buffer.append(line).append(' ').append('\n');
|
||||||
buffer.append('\n');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (line.trim().isEmpty()) {
|
else if (line.trim().isEmpty()) {
|
||||||
|
@ -257,16 +238,62 @@ public class ScriptInfo {
|
||||||
catch (IOException e) {
|
catch (IOException e) {
|
||||||
Msg.debug(this, "Unexpected exception reading script: " + sourceFile, e);
|
Msg.debug(this, "Unexpected exception reading script: " + sourceFile, e);
|
||||||
}
|
}
|
||||||
finally {
|
|
||||||
if (reader != null) {
|
|
||||||
try {
|
|
||||||
reader.close();
|
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
|
||||||
// don't care; we tried
|
private boolean skipCertifyHeader(BufferedReader reader, String line) throws IOException {
|
||||||
|
|
||||||
|
// Note that skipping certification header presumes that the header
|
||||||
|
// is intact with an appropriate start and end
|
||||||
|
String certifyHeaderStart = provider.getCertifyHeaderStart();
|
||||||
|
if (certifyHeaderStart == null) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!line.startsWith(certifyHeaderStart)) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String certifyHeaderEnd = provider.getCertifyHeaderEnd();
|
||||||
|
String certifyHeaderBodyPrefix = provider.getCertificationBodyPrefix();
|
||||||
|
certifyHeaderBodyPrefix = certifyHeaderBodyPrefix == null ? "" : certifyHeaderBodyPrefix;
|
||||||
|
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
|
||||||
|
// Skip past certification header if found
|
||||||
|
String trimLine = line.trim();
|
||||||
|
if (trimLine.startsWith(certifyHeaderEnd)) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (trimLine.startsWith(certifyHeaderBodyPrefix)) {
|
||||||
|
continue; // skip certification header body
|
||||||
|
}
|
||||||
|
|
||||||
|
// broken certification header - unexpected line
|
||||||
|
Msg.error(this,
|
||||||
|
"Script contains invalid certification header: " + getName());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean parseBlockComment(BufferedReader reader, String line) throws IOException {
|
||||||
|
Pattern blockStart = provider.getBlockCommentStart();
|
||||||
|
Pattern blockEnd = provider.getBlockCommentEnd();
|
||||||
|
|
||||||
|
if (blockStart == null || blockEnd == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Matcher startMatcher = blockStart.matcher(line);
|
||||||
|
if (startMatcher.find()) {
|
||||||
|
int lastOffset = startMatcher.end();
|
||||||
|
while (line != null && !blockEnd.matcher(line).find(lastOffset)) {
|
||||||
|
line = reader.readLine();
|
||||||
|
lastOffset = 0;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void parseMetaDataLine(String line) {
|
private void parseMetaDataLine(String line) {
|
||||||
|
|
|
@ -528,6 +528,18 @@ public abstract class AbstractGhidraScriptMgrPluginTest
|
||||||
return new ResourceFile(tempFile);
|
return new ResourceFile(tempFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected ResourceFile createTempScriptFileWithLines(String... lines) throws IOException {
|
||||||
|
ResourceFile newScript = createTempScriptFile();
|
||||||
|
|
||||||
|
PrintWriter writer = new PrintWriter(newScript.getOutputStream());
|
||||||
|
for (String line : lines) {
|
||||||
|
writer.println(line);
|
||||||
|
}
|
||||||
|
writer.close();
|
||||||
|
|
||||||
|
return newScript;
|
||||||
|
}
|
||||||
|
|
||||||
protected String changeEditorContents() {
|
protected String changeEditorContents() {
|
||||||
assertNotNull("Editor not opened and initialized", editorTextArea);
|
assertNotNull("Editor not opened and initialized", editorTextArea);
|
||||||
assertNotNull("Editor not opened and initialized", buffer);
|
assertNotNull("Editor not opened and initialized", buffer);
|
||||||
|
|
|
@ -0,0 +1,214 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.app.plugin.core.script;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import javax.swing.KeyStroke;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import generic.jar.ResourceFile;
|
||||||
|
import ghidra.app.script.GhidraScriptUtil;
|
||||||
|
import ghidra.app.script.ScriptInfo;
|
||||||
|
|
||||||
|
public class JavaScriptInfoTest extends AbstractGhidraScriptMgrPluginTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDetailedJavaScript() {
|
||||||
|
String descLine1 = "This script exists to check that the info on";
|
||||||
|
String descLine2 = "a script that has extensive documentation is";
|
||||||
|
String descLine3 = "properly parsed and represented.";
|
||||||
|
String author = "Fake Name";
|
||||||
|
String categoryTop = "Test";
|
||||||
|
String categoryBottom = "ScriptInfo";
|
||||||
|
String keybinding = "ctrl shift COMMA";
|
||||||
|
String menupath = "File.Run.Detailed Script";
|
||||||
|
String importPackage = "detailStuff";
|
||||||
|
ResourceFile scriptFile = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
//@formatter:off
|
||||||
|
scriptFile = createTempScriptFileWithLines(
|
||||||
|
"/*",
|
||||||
|
" * This is a test block comment. It will be ignored.",
|
||||||
|
" * @category NotTheRealCategory",
|
||||||
|
" */",
|
||||||
|
"//" + descLine1,
|
||||||
|
"//" + descLine2,
|
||||||
|
"//" + descLine3,
|
||||||
|
"//@author " + author,
|
||||||
|
"//@category " + categoryTop + "." + categoryBottom,
|
||||||
|
"//@keybinding " + keybinding,
|
||||||
|
"//@menupath " + menupath,
|
||||||
|
"//@importpackage " + importPackage,
|
||||||
|
"class DetailedScript {",
|
||||||
|
" // for a blank class, it sure is well documented!",
|
||||||
|
"}");
|
||||||
|
//@formatter:on
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
fail("couldn't create a test script: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile);
|
||||||
|
|
||||||
|
String expectedDescription = descLine1 + " \n" + descLine2 + " \n" + descLine3 + " \n";
|
||||||
|
assertEquals(expectedDescription, info.getDescription());
|
||||||
|
|
||||||
|
assertEquals(author, info.getAuthor());
|
||||||
|
assertEquals(KeyStroke.getKeyStroke(keybinding), info.getKeyBinding());
|
||||||
|
assertEquals(menupath.replace(".", "->"), info.getMenuPathAsString());
|
||||||
|
assertEquals(importPackage, info.getImportPackage());
|
||||||
|
|
||||||
|
String[] actualCategory = info.getCategory();
|
||||||
|
assertEquals(2, actualCategory.length);
|
||||||
|
assertEquals(categoryTop, actualCategory[0]);
|
||||||
|
assertEquals(categoryBottom, actualCategory[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testJavaScriptWithBlockComment() {
|
||||||
|
String description = "Script with a block comment at the top.";
|
||||||
|
String category = "Test";
|
||||||
|
ResourceFile scriptFile = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
//@formatter:off
|
||||||
|
scriptFile = createTempScriptFileWithLines(
|
||||||
|
"/*",
|
||||||
|
" * This is a test block comment. It will be ignored.",
|
||||||
|
" * @category NotTheRealCategory",
|
||||||
|
" */",
|
||||||
|
"//" + description,
|
||||||
|
"//@category " + category,
|
||||||
|
"class BlockCommentScript {",
|
||||||
|
" // just a blank class, nothing to see here",
|
||||||
|
"}");
|
||||||
|
//@formatter:on
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
fail("couldn't create a test script: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile);
|
||||||
|
assertEquals(description + " \n", info.getDescription());
|
||||||
|
|
||||||
|
String[] actualCategory = info.getCategory();
|
||||||
|
assertEquals(1, actualCategory.length);
|
||||||
|
assertEquals(category, actualCategory[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testJavaScriptWithBlockCommentAndCertifyHeader() {
|
||||||
|
String description = "Script with a block comment at the top.";
|
||||||
|
String category = "Test";
|
||||||
|
ResourceFile scriptFile = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
//@formatter:off
|
||||||
|
scriptFile = createTempScriptFileWithLines(
|
||||||
|
"/* ###" +
|
||||||
|
" * IP: GHIDRA" +
|
||||||
|
" * " +
|
||||||
|
" * Some license text..." +
|
||||||
|
" * you may not use this file except in compliance with the License." +
|
||||||
|
" * " +
|
||||||
|
" * blah blah blah" +
|
||||||
|
" */" +
|
||||||
|
" " +
|
||||||
|
"/*",
|
||||||
|
" * This is a test block comment. It will be ignored.",
|
||||||
|
" * @category NotTheRealCategory",
|
||||||
|
" */",
|
||||||
|
"//" + description,
|
||||||
|
"//@category " + category,
|
||||||
|
"class BlockCommentScript {",
|
||||||
|
" // just a blank class, nothing to see here",
|
||||||
|
"}");
|
||||||
|
//@formatter:on
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
fail("couldn't create a test script: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile);
|
||||||
|
assertEquals(description + " \n", info.getDescription());
|
||||||
|
|
||||||
|
String[] actualCategory = info.getCategory();
|
||||||
|
assertEquals(1, actualCategory.length);
|
||||||
|
assertEquals(category, actualCategory[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testJavaScriptWithoutBlockComment() {
|
||||||
|
String description = "Script without a block comment at the top.";
|
||||||
|
String category = "Test";
|
||||||
|
ResourceFile scriptFile = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
//@formatter:off
|
||||||
|
scriptFile = createTempScriptFileWithLines(
|
||||||
|
"//" + description,
|
||||||
|
"//@category " + category,
|
||||||
|
"class NoBlockCommentScript {",
|
||||||
|
" // just a blank class, nothing to see here",
|
||||||
|
"}");
|
||||||
|
//@formatter:on
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
fail("couldn't create a test script: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile);
|
||||||
|
assertEquals(description + " \n", info.getDescription());
|
||||||
|
|
||||||
|
String[] actualCategory = info.getCategory();
|
||||||
|
assertEquals(1, actualCategory.length);
|
||||||
|
assertEquals(category, actualCategory[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testJavaScriptWithSingleLineBlockComment() {
|
||||||
|
String description = "Script with a block comment at the top.";
|
||||||
|
String category = "Test";
|
||||||
|
ResourceFile scriptFile = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
//@formatter:off
|
||||||
|
scriptFile = createTempScriptFileWithLines(
|
||||||
|
"/* This is a test block comment. It will be ignored. */",
|
||||||
|
"//" + description,
|
||||||
|
"//@category " + category,
|
||||||
|
"class SingleLineBlockCommentScript {",
|
||||||
|
" // just a blank class, nothing to see here",
|
||||||
|
"}");
|
||||||
|
//@formatter:on
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
fail("couldn't create a test script: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile);
|
||||||
|
assertEquals(description + " \n", info.getDescription());
|
||||||
|
|
||||||
|
String[] actualCategory = info.getCategory();
|
||||||
|
assertEquals(1, actualCategory.length);
|
||||||
|
assertEquals(category, actualCategory[0]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,7 @@
|
||||||
package ghidra.python;
|
package ghidra.python;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import generic.jar.ResourceFile;
|
import generic.jar.ResourceFile;
|
||||||
import ghidra.app.script.GhidraScript;
|
import ghidra.app.script.GhidraScript;
|
||||||
|
@ -23,6 +24,8 @@ import ghidra.app.script.GhidraScriptProvider;
|
||||||
|
|
||||||
public class PythonScriptProvider extends GhidraScriptProvider {
|
public class PythonScriptProvider extends GhidraScriptProvider {
|
||||||
|
|
||||||
|
private static final Pattern BLOCK_COMMENT = Pattern.compile("'''");
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void createNewScript(ResourceFile newScript, String category) throws IOException {
|
public void createNewScript(ResourceFile newScript, String category) throws IOException {
|
||||||
PrintWriter writer = new PrintWriter(new FileWriter(newScript.getFile(false)));
|
PrintWriter writer = new PrintWriter(new FileWriter(newScript.getFile(false)));
|
||||||
|
@ -33,26 +36,31 @@ public class PythonScriptProvider extends GhidraScriptProvider {
|
||||||
writer.close();
|
writer.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a Pattern that matches block comment openings.
|
||||||
|
* In Python this is a triple single quote sequence, "'''".
|
||||||
|
* @return the Pattern for Python block comment openings
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Pattern getBlockCommentStart() {
|
||||||
|
return BLOCK_COMMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a Pattern that matches block comment closings.
|
||||||
|
* In Python this is a triple single quote sequence, "'''".
|
||||||
|
* @return the Pattern for Python block comment openings
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Pattern getBlockCommentEnd() {
|
||||||
|
return BLOCK_COMMENT;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getCommentCharacter() {
|
public String getCommentCharacter() {
|
||||||
return "#";
|
return "#";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getCertifyHeaderStart() {
|
|
||||||
return "## ###";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getCertifyHeaderEnd() {
|
|
||||||
return "##";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getCertificationBodyPrefix() {
|
|
||||||
return "#";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDescription() {
|
public String getDescription() {
|
||||||
return "Python";
|
return "Python";
|
||||||
|
|
|
@ -0,0 +1,237 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.python;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import javax.swing.KeyStroke;
|
||||||
|
|
||||||
|
import org.junit.*;
|
||||||
|
|
||||||
|
import generic.jar.ResourceFile;
|
||||||
|
import ghidra.app.plugin.core.osgi.BundleHost;
|
||||||
|
import ghidra.app.script.GhidraScriptUtil;
|
||||||
|
import ghidra.app.script.ScriptInfo;
|
||||||
|
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||||
|
|
||||||
|
public class PythonScriptInfoTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
GhidraScriptUtil.initialize(new BundleHost(), null);
|
||||||
|
Path userScriptDir = java.nio.file.Paths.get(GhidraScriptUtil.USER_SCRIPTS_DIR);
|
||||||
|
if (Files.notExists(userScriptDir)) {
|
||||||
|
Files.createDirectories(userScriptDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() throws Exception {
|
||||||
|
GhidraScriptUtil.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDetailedPythonScript() {
|
||||||
|
String descLine1 = "This script exists to check that the info on";
|
||||||
|
String descLine2 = "a script that has extensive documentation is";
|
||||||
|
String descLine3 = "properly parsed and represented.";
|
||||||
|
String author = "Fake Name";
|
||||||
|
String categoryTop = "Test";
|
||||||
|
String categoryBottom = "ScriptInfo";
|
||||||
|
String keybinding = "ctrl shift COMMA";
|
||||||
|
String menupath = "File.Run.Detailed Script";
|
||||||
|
String importPackage = "detailStuff";
|
||||||
|
ResourceFile scriptFile = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
//@formatter:off
|
||||||
|
scriptFile = createTempPyScriptFileWithLines(
|
||||||
|
"'''",
|
||||||
|
"This is a test block comment. It will be ignored.",
|
||||||
|
"@category NotTheRealCategory",
|
||||||
|
"'''",
|
||||||
|
"#" + descLine1,
|
||||||
|
"#" + descLine2,
|
||||||
|
"#" + descLine3,
|
||||||
|
"#@author " + author,
|
||||||
|
"#@category " + categoryTop + "." + categoryBottom,
|
||||||
|
"#@keybinding " + keybinding,
|
||||||
|
"#@menupath " + menupath,
|
||||||
|
"#@importpackage " + importPackage,
|
||||||
|
"print('for a blank class, it sure is well documented!')");
|
||||||
|
//@formatter:on
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
fail("couldn't create a test script: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile);
|
||||||
|
|
||||||
|
String expectedDescription = descLine1 + " \n" + descLine2 + " \n" + descLine3 + " \n";
|
||||||
|
assertEquals(expectedDescription, info.getDescription());
|
||||||
|
|
||||||
|
assertEquals(author, info.getAuthor());
|
||||||
|
assertEquals(KeyStroke.getKeyStroke(keybinding), info.getKeyBinding());
|
||||||
|
assertEquals(menupath.replace(".", "->"), info.getMenuPathAsString());
|
||||||
|
assertEquals(importPackage, info.getImportPackage());
|
||||||
|
|
||||||
|
String[] actualCategory = info.getCategory();
|
||||||
|
assertEquals(2, actualCategory.length);
|
||||||
|
assertEquals(categoryTop, actualCategory[0]);
|
||||||
|
assertEquals(categoryBottom, actualCategory[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPythonScriptWithBlockComment() {
|
||||||
|
String description = "Script with a block comment at the top.";
|
||||||
|
String category = "Test";
|
||||||
|
ResourceFile scriptFile = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
//@formatter:off
|
||||||
|
scriptFile = createTempPyScriptFileWithLines(
|
||||||
|
"'''",
|
||||||
|
"This is a test block comment. It will be ignored.",
|
||||||
|
"@category NotTheRealCategory",
|
||||||
|
"'''",
|
||||||
|
"#" + description,
|
||||||
|
"#@category " + category,
|
||||||
|
"print 'hello!'");
|
||||||
|
//@formatter:on
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
fail("couldn't create a test script: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile);
|
||||||
|
assertEquals(description + " \n", info.getDescription());
|
||||||
|
|
||||||
|
String[] actualCategory = info.getCategory();
|
||||||
|
assertEquals(1, actualCategory.length);
|
||||||
|
assertEquals(category, actualCategory[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPythonScriptWithBlockCommentAndCertifyHeader() {
|
||||||
|
String description = "Script with a block comment at the top.";
|
||||||
|
String category = "Test";
|
||||||
|
ResourceFile scriptFile = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
//@formatter:off
|
||||||
|
scriptFile = createTempPyScriptFileWithLines(
|
||||||
|
"## ###" +
|
||||||
|
"# IP: GHIDRA" +
|
||||||
|
"# " +
|
||||||
|
"# Some license text..." +
|
||||||
|
"# you may not use this file except in compliance with the License." +
|
||||||
|
"# " +
|
||||||
|
"# blah blah blah" +
|
||||||
|
"##" +
|
||||||
|
"" +
|
||||||
|
"'''",
|
||||||
|
"This is a test block comment. It will be ignored.",
|
||||||
|
"@category NotTheRealCategory",
|
||||||
|
"'''",
|
||||||
|
"#" + description,
|
||||||
|
"#@category " + category,
|
||||||
|
"print 'hello!'");
|
||||||
|
//@formatter:on
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
fail("couldn't create a test script: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile);
|
||||||
|
assertEquals(description + " \n", info.getDescription());
|
||||||
|
|
||||||
|
String[] actualCategory = info.getCategory();
|
||||||
|
assertEquals(1, actualCategory.length);
|
||||||
|
assertEquals(category, actualCategory[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPythonScriptWithoutBlockComment() {
|
||||||
|
String description = "Script without a block comment at the top.";
|
||||||
|
String category = "Test";
|
||||||
|
ResourceFile scriptFile = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
//@formatter:off
|
||||||
|
scriptFile = createTempPyScriptFileWithLines(
|
||||||
|
"#" + description,
|
||||||
|
"#@category " + category,
|
||||||
|
"print 'hello!'");
|
||||||
|
//@formatter:on
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
fail("couldn't create a test script: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile);
|
||||||
|
assertEquals(description + " \n", info.getDescription());
|
||||||
|
|
||||||
|
String[] actualCategory = info.getCategory();
|
||||||
|
assertEquals(1, actualCategory.length);
|
||||||
|
assertEquals(category, actualCategory[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPythonScriptWithSingleLineBlockComment() {
|
||||||
|
String description = "Script with a block comment at the top.";
|
||||||
|
String category = "Test";
|
||||||
|
ResourceFile scriptFile = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
//@formatter:off
|
||||||
|
scriptFile = createTempPyScriptFileWithLines(
|
||||||
|
"'''This is a test block comment. It will be ignored.'''",
|
||||||
|
"#" + description,
|
||||||
|
"#@category " + category,
|
||||||
|
"print 'hello!'");
|
||||||
|
//@formatter:on
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
fail("couldn't create a test script: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
ScriptInfo info = GhidraScriptUtil.newScriptInfo(scriptFile);
|
||||||
|
assertEquals(description + " \n", info.getDescription());
|
||||||
|
|
||||||
|
String[] actualCategory = info.getCategory();
|
||||||
|
assertEquals(1, actualCategory.length);
|
||||||
|
assertEquals(category, actualCategory[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResourceFile createTempPyScriptFileWithLines(String... lines) throws IOException {
|
||||||
|
File scriptDir = new File(GhidraScriptUtil.USER_SCRIPTS_DIR);
|
||||||
|
File tempFile = File.createTempFile(testName.getMethodName(), ".py", scriptDir);
|
||||||
|
tempFile.deleteOnExit();
|
||||||
|
ResourceFile tempResourceFile = new ResourceFile(tempFile);
|
||||||
|
|
||||||
|
PrintWriter writer = new PrintWriter(tempResourceFile.getOutputStream());
|
||||||
|
for (String line : lines) {
|
||||||
|
writer.println(line);
|
||||||
|
}
|
||||||
|
writer.close();
|
||||||
|
|
||||||
|
return tempResourceFile;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue