params = new HashMap<>();
- for (String pair : bodyStr.split("&")) {
- String[] kv = pair.split("=");
- if (kv.length == 2) {
- // URL decode parameter values
- try {
- String key = URLDecoder.decode(kv[0], StandardCharsets.UTF_8);
- String value = URLDecoder.decode(kv[1], StandardCharsets.UTF_8);
- params.put(key, value);
- } catch (Exception e) {
- Msg.error(this, "Error decoding URL parameter", e);
- }
- }
- }
- return params;
- }
-
- /**
- * Applies pagination to a list of strings and returns a formatted result.
- *
- * This utility method implements standard pagination behavior by:
- *
- * - Validating and constraining the offset to valid bounds
- * - Calculating the end index based on offset + limit
- * - Extracting the requested subset of items
- * - Joining the results with newline characters
- *
- *
- * Pagination Logic:
- *
- * - If offset >= list size, returns empty string
- * - If offset + limit exceeds list size, returns items from offset to
- * end
- * - Negative offsets are treated as 0
- *
- *
- * Example:
- *
- * {@code
- * List functions = Arrays.asList("main", "printf", "malloc", "free", "exit");
- * String page1 = paginateList(functions, 0, 2); // Returns "main\nprintf"
- * String page2 = paginateList(functions, 2, 2); // Returns "malloc\nfree"
- * String page3 = paginateList(functions, 4, 2); // Returns "exit"
- * }
- *
- * @param items The complete list of string items to paginate
- * @param offset Starting index for pagination (0-based, negative values treated
- * as 0)
- * @param limit Maximum number of items to return
- * @return Newline-separated string of the requested items, or empty string if
- * no items in range
- * @see List#subList(int, int)
- */
- private String paginateList(List items, int offset, int limit) {
- int start = Math.max(0, offset);
- int end = Math.min(items.size(), offset + limit);
-
- if (start >= items.size()) {
- return ""; // no items in range
- }
- List sub = items.subList(start, end);
- return String.join("\n", sub);
- }
-
- /**
- * Parse an integer from a string, or return defaultValue if null/invalid.
- */
- private int parseIntOrDefault(String val, int defaultValue) {
- if (val == null)
- return defaultValue;
- try {
- return Integer.parseInt(val);
- } catch (NumberFormatException e) {
- return defaultValue;
- }
- }
-
- /**
- * Escape non-ASCII chars to avoid potential decode issues.
- */
- private String escapeNonAscii(String input) {
- if (input == null)
- return "";
- StringBuilder sb = new StringBuilder();
- for (char c : input.toCharArray()) {
- if (c >= 32 && c < 127) {
- sb.append(c);
- } else {
- sb.append("\\x");
- sb.append(Integer.toHexString(c & 0xFF));
- }
- }
- return sb.toString();
- }
-
- /**
- * Retrieves the currently active program from Ghidra's program manager.
- *
- * This method provides safe access to the current program being analyzed in
- * the CodeBrowser. It handles cases where no program is loaded or the
- * ProgramManager service is unavailable.
- *
- * Usage Pattern: This method is called by virtually every API endpoint
- * to ensure operations are performed on the correct program context.
- *
- * @return The currently loaded Program instance, or {@code null} if:
- *
- * - No program is currently loaded in CodeBrowser
- * - ProgramManager service is not available
- * - Plugin is not properly initialized
- *
- * @see ProgramManager#getCurrentProgram()
- * @see PluginTool#getService(Class)
- */
- public Program getCurrentProgram() {
- ProgramManager pm = tool.getService(ProgramManager.class);
- return pm != null ? pm.getCurrentProgram() : null;
- }
-
- private void sendResponse(HttpExchange exchange, String response) throws IOException {
- byte[] bytes = response.getBytes(StandardCharsets.UTF_8);
- exchange.getResponseHeaders().set("Content-Type", "text/plain; charset=utf-8");
- exchange.sendResponseHeaders(200, bytes.length);
- try (OutputStream os = exchange.getResponseBody()) {
- os.write(bytes);
- }
- }
-
/**
* Cleanly shuts down the HTTP server and releases plugin resources.
*
diff --git a/src/main/java/com/lauriewired/handlers/Handler.java b/src/main/java/com/lauriewired/handlers/Handler.java
new file mode 100644
index 0000000..1ed4b64
--- /dev/null
+++ b/src/main/java/com/lauriewired/handlers/Handler.java
@@ -0,0 +1,48 @@
+package com.lauriewired.handlers;
+
+import com.sun.net.httpserver.HttpExchange;
+import ghidra.framework.plugintool.PluginTool;
+
+/**
+ * Abstract class representing a handler for HTTP requests in a Ghidra
+ * PluginTool.
+ * Subclasses must implement the handle method to define how requests are
+ * processed.
+ */
+public abstract class Handler {
+ /** The PluginTool instance this handler is associated with. */
+ protected final PluginTool tool;
+
+ /** The path this handler will respond to. */
+ protected final String path;
+
+ /**
+ * Constructs a new Handler with the specified PluginTool and path.
+ *
+ * @param tool the PluginTool instance this handler is associated with
+ * @param path the path this handler will respond to
+ */
+ protected Handler(PluginTool tool, String path) {
+ this.tool = tool;
+ this.path = path;
+ }
+
+ /**
+ * Gets the path this handler will respond to.
+ *
+ * @return the path
+ */
+ public String getPath() {
+ return path;
+ }
+
+ /**
+ * Handles an HTTP request.
+ * Subclasses must implement this method to define how requests are
+ * processed.
+ *
+ * @param exchange the HttpExchange object representing the HTTP request
+ * @throws Exception if an error occurs while handling the request
+ */
+ public abstract void handle(HttpExchange exchange) throws Exception;
+}
diff --git a/src/main/java/com/lauriewired/handlers/act/AddStructMembers.java b/src/main/java/com/lauriewired/handlers/act/AddStructMembers.java
new file mode 100644
index 0000000..5f1e97b
--- /dev/null
+++ b/src/main/java/com/lauriewired/handlers/act/AddStructMembers.java
@@ -0,0 +1,104 @@
+package com.lauriewired.handlers.act;
+
+import com.lauriewired.handlers.Handler;
+import com.sun.net.httpserver.HttpExchange;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.program.model.data.DataType;
+import ghidra.program.model.data.DataTypeManager;
+import ghidra.program.model.data.Structure;
+import ghidra.program.model.listing.Program;
+
+import com.google.gson.Gson;
+
+import javax.swing.SwingUtilities;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static com.lauriewired.util.GhidraUtils.*;
+import static com.lauriewired.util.ParseUtils.*;
+import static com.lauriewired.util.StructUtils.StructMember;
+import ghidra.program.model.data.CategoryPath;
+import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram;
+
+public final class AddStructMembers extends Handler {
+ public AddStructMembers(PluginTool tool) {
+ super(tool, "/add_struct_members");
+ }
+
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+ Map params = parsePostParams(exchange);
+ String structName = params.get("struct_name");
+ String category = params.get("category");
+ String membersJson = params.get("members");
+
+ if (structName == null || membersJson == null) {
+ sendResponse(exchange, "struct_name and members are required");
+ return;
+ }
+ sendResponse(exchange, addStructMembers(structName, category, membersJson));
+ }
+
+ private String addStructMembers(String structName, String category, String membersJson) {
+ Program program = getCurrentProgram(tool);
+ if (program == null)
+ return "No program loaded";
+
+ final AtomicReference result = new AtomicReference<>();
+ try {
+ SwingUtilities.invokeAndWait(() -> {
+ int txId = program.startTransaction("Add Struct Member");
+ boolean success = false;
+ try {
+ DataTypeManager dtm = program.getDataTypeManager();
+ CategoryPath path = new CategoryPath(category == null ? "/" : category);
+ DataType dt = dtm.getDataType(path, structName);
+
+ if (dt == null || !(dt instanceof Structure)) {
+ result.set("Error: Struct " + structName + " not found in category " + path);
+ return;
+ }
+ Structure struct = (Structure) dt;
+
+ StringBuilder responseBuilder = new StringBuilder();
+
+ if (membersJson != null && !membersJson.isEmpty()) {
+ Gson gson = new Gson();
+ StructMember[] members = gson.fromJson(membersJson, StructMember[].class);
+
+ int membersAdded = 0;
+ for (StructMember member : members) {
+ DataType memberDt = resolveDataType(tool, dtm, member.type);
+ if (memberDt == null) {
+ responseBuilder.append("\nError: Could not resolve data type '").append(member.type)
+ .append("' for member '").append(member.name)
+ .append("'. Aborting further member creation.");
+ break;
+ }
+
+ if (member.offset != -1) {
+ struct.insertAtOffset((int) member.offset, memberDt, -1, member.name, member.comment);
+ } else {
+ struct.add(memberDt, member.name, member.comment);
+ }
+ membersAdded++;
+ }
+ responseBuilder.append("\nAdded ").append(membersAdded).append(" members.");
+ result.set(responseBuilder.toString());
+ success = membersAdded > 0;
+ }
+
+ } catch (Exception e) {
+ result.set("Error: Failed to add member to struct: " + e.getMessage());
+ } finally {
+ program.endTransaction(txId, success);
+ }
+ });
+ } catch (InterruptedException | InvocationTargetException e) {
+ return "Error: Failed to execute add struct member on Swing thread: " + e.getMessage();
+ }
+ return result.get();
+ }
+}
diff --git a/src/main/java/com/lauriewired/handlers/act/CreateStruct.java b/src/main/java/com/lauriewired/handlers/act/CreateStruct.java
new file mode 100644
index 0000000..a54e784
--- /dev/null
+++ b/src/main/java/com/lauriewired/handlers/act/CreateStruct.java
@@ -0,0 +1,106 @@
+package com.lauriewired.handlers.act;
+
+import com.lauriewired.handlers.Handler;
+import com.sun.net.httpserver.HttpExchange;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.program.model.data.DataType;
+import ghidra.program.model.data.DataTypeConflictHandler;
+import ghidra.program.model.data.DataTypeManager;
+import ghidra.program.model.data.StructureDataType;
+import ghidra.program.model.listing.Program;
+
+import com.google.gson.Gson;
+
+import javax.swing.SwingUtilities;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static com.lauriewired.util.GhidraUtils.*;
+import static com.lauriewired.util.ParseUtils.*;
+import static com.lauriewired.util.StructUtils.StructMember;
+import ghidra.program.model.data.CategoryPath;
+import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram;
+
+public final class CreateStruct extends Handler {
+ public CreateStruct(PluginTool tool) {
+ super(tool, "/create_struct");
+ }
+
+ public void handle(HttpExchange exchange) throws IOException {
+ Map params = parsePostParams(exchange);
+ String name = params.get("name");
+ String category = params.get("category");
+ long size = parseIntOrDefault(params.get("size"), 0);
+ String membersJson = params.get("members"); // Optional
+
+ if (name == null || name.isEmpty()) {
+ sendResponse(exchange, "Struct name is required");
+ return;
+ }
+ sendResponse(exchange, createStruct(name, category, (int) size, membersJson));
+ }
+
+ private String createStruct(String name, String category, int size, String membersJson) {
+ Program program = getCurrentProgram(tool);
+ if (program == null)
+ return "No program loaded";
+
+ final AtomicReference result = new AtomicReference<>();
+ try {
+ SwingUtilities.invokeAndWait(() -> {
+ int txId = program.startTransaction("Create Struct");
+ boolean success = false;
+ try {
+ DataTypeManager dtm = program.getDataTypeManager();
+ CategoryPath path = new CategoryPath(category == null ? "/" : category);
+
+ if (dtm.getDataType(path, name) != null) {
+ result.set("Error: Struct " + name + " already exists in category " + path);
+ return;
+ }
+ StructureDataType newStruct = new StructureDataType(path, name, size, dtm);
+
+ StringBuilder responseBuilder = new StringBuilder(
+ "Struct " + name + " created successfully in category " + path);
+
+ if (membersJson != null && !membersJson.isEmpty()) {
+ Gson gson = new Gson();
+ StructMember[] members = gson.fromJson(membersJson, StructMember[].class);
+
+ int membersAdded = 0;
+ for (StructMember member : members) {
+ DataType memberDt = resolveDataType(tool, dtm, member.type);
+ if (memberDt == null) {
+ responseBuilder.append("\nError: Could not resolve data type '").append(member.type)
+ .append("' for member '").append(member.name)
+ .append("'. Aborting further member creation.");
+ break;
+ }
+
+ if (member.offset != -1) {
+ newStruct.insertAtOffset((int) member.offset, memberDt, -1, member.name,
+ member.comment);
+ } else {
+ newStruct.add(memberDt, member.name, member.comment);
+ }
+ membersAdded++;
+ }
+ responseBuilder.append("\nAdded ").append(membersAdded).append(" members.");
+ }
+ dtm.addDataType(newStruct, DataTypeConflictHandler.DEFAULT_HANDLER);
+ result.set(responseBuilder.toString());
+ success = true;
+ } catch (Exception e) {
+ result.set("Error: Failed to create struct: " + e.getMessage());
+ } finally {
+ program.endTransaction(txId, success);
+ }
+ });
+ } catch (InterruptedException | InvocationTargetException e) {
+ return "Error: Failed to execute create struct on Swing thread: " + e.getMessage();
+ }
+ return result.get();
+ }
+}
diff --git a/src/main/java/com/lauriewired/handlers/act/DecompileFunctionByAddress.java b/src/main/java/com/lauriewired/handlers/act/DecompileFunctionByAddress.java
new file mode 100644
index 0000000..68d2b34
--- /dev/null
+++ b/src/main/java/com/lauriewired/handlers/act/DecompileFunctionByAddress.java
@@ -0,0 +1,79 @@
+package com.lauriewired.handlers.act;
+
+import com.lauriewired.handlers.Handler;
+import com.sun.net.httpserver.HttpExchange;
+import ghidra.app.decompiler.DecompInterface;
+import ghidra.app.decompiler.DecompileResults;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.listing.Function;
+import ghidra.program.model.listing.Program;
+import ghidra.util.task.ConsoleTaskMonitor;
+
+import java.io.IOException;
+import java.util.Map;
+
+import static com.lauriewired.util.ParseUtils.parseQueryParams;
+import static com.lauriewired.util.ParseUtils.sendResponse;
+import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram;
+
+/**
+ * Handler to decompile a function by its address in the current program.
+ * This handler responds to HTTP requests with the decompiled C code of the function
+ * at the specified address.
+ */
+public final class DecompileFunctionByAddress extends Handler {
+ /**
+ * Constructor for the DecompileFunctionByAddress handler
+ *
+ * @param tool the PluginTool instance to interact with Ghidra
+ */
+ public DecompileFunctionByAddress(PluginTool tool) {
+ super(tool, "/decompile_function");
+ }
+
+ /**
+ * Handles HTTP requests to decompile a function by its address.
+ * Expects a query parameter "address" with the function's address.
+ *
+ * @param exchange the HttpExchange object representing the HTTP request
+ * @throws IOException if an I/O error occurs during handling
+ */
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+ Map qparams = parseQueryParams(exchange);
+ String address = qparams.get("address");
+ sendResponse(exchange, decompileFunctionByAddress(address));
+ }
+
+ /**
+ * Decompiles the function at the specified address in the current program.
+ *
+ * @param addressStr the address of the function to decompile
+ * @return the decompiled C code or an error message
+ */
+ private String decompileFunctionByAddress(String addressStr) {
+ Program program = getCurrentProgram(tool);
+ if (program == null)
+ return "No program loaded";
+ if (addressStr == null || addressStr.isEmpty())
+ return "Address is required";
+
+ try {
+ Address addr = program.getAddressFactory().getAddress(addressStr);
+ Function func = program.getListing().getFunctionContaining(addr);
+ if (func == null)
+ return "No function found at or containing address " + addressStr;
+
+ DecompInterface decomp = new DecompInterface();
+ decomp.openProgram(program);
+ DecompileResults result = decomp.decompileFunction(func, 30, new ConsoleTaskMonitor());
+
+ return (result != null && result.decompileCompleted())
+ ? result.getDecompiledFunction().getC()
+ : "Decompilation failed";
+ } catch (Exception e) {
+ return "Error decompiling function: " + e.getMessage();
+ }
+ }
+}
diff --git a/src/main/java/com/lauriewired/handlers/act/DecompileFunctionByName.java b/src/main/java/com/lauriewired/handlers/act/DecompileFunctionByName.java
new file mode 100644
index 0000000..9dd7d30
--- /dev/null
+++ b/src/main/java/com/lauriewired/handlers/act/DecompileFunctionByName.java
@@ -0,0 +1,76 @@
+package com.lauriewired.handlers.act;
+
+import com.lauriewired.handlers.Handler;
+import com.sun.net.httpserver.HttpExchange;
+import ghidra.app.decompiler.DecompInterface;
+import ghidra.app.decompiler.DecompileOptions;
+import ghidra.app.decompiler.DecompileResults;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.program.model.listing.Function;
+import ghidra.program.model.listing.Program;
+import ghidra.util.task.ConsoleTaskMonitor;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import static com.lauriewired.util.ParseUtils.sendResponse;
+import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram;
+
+/**
+ * Handler to decompile a function by its name.
+ * Expects the function name in the request body.
+ */
+public final class DecompileFunctionByName extends Handler {
+ /**
+ * Constructs a new DecompileFunctionByName handler.
+ *
+ * @param tool The Ghidra plugin tool instance.
+ */
+ public DecompileFunctionByName(PluginTool tool) {
+ super(tool, "/decompile");
+ }
+
+ /**
+ * Handles the HTTP request to decompile a function by its name.
+ * Reads the function name from the request body and returns the decompiled C
+ * pseudocode.
+ *
+ * @param exchange The HTTP exchange containing the request and response.
+ * @throws IOException If an I/O error occurs during handling.
+ */
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+ String name = new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8);
+ sendResponse(exchange, generateResponse(name));
+ }
+
+ /**
+ * Generates the decompiled C pseudocode for the function with the specified
+ * name.
+ *
+ * @param name The name of the function to decompile.
+ * @return The decompiled C pseudocode or an error message if the function is
+ * not found.
+ */
+ private String generateResponse(String name) {
+ Program program = getCurrentProgram(tool);
+ if (program == null)
+ return "No program loaded";
+ DecompInterface decomp = new DecompInterface();
+ DecompileOptions options = new DecompileOptions();
+ options.setRespectReadOnly(true);
+ decomp.setOptions(options);
+ decomp.openProgram(program);
+ for (Function func : program.getFunctionManager().getFunctions(true)) {
+ if (func.getName().equals(name)) {
+ DecompileResults result = decomp.decompileFunction(func, 30, new ConsoleTaskMonitor());
+ if (result != null && result.decompileCompleted()) {
+ return result.getDecompiledFunction().getC();
+ } else {
+ return "Decompilation failed";
+ }
+ }
+ }
+ return "Function not found";
+ }
+}
diff --git a/src/main/java/com/lauriewired/handlers/act/DisassembleFunction.java b/src/main/java/com/lauriewired/handlers/act/DisassembleFunction.java
new file mode 100644
index 0000000..c60c8f1
--- /dev/null
+++ b/src/main/java/com/lauriewired/handlers/act/DisassembleFunction.java
@@ -0,0 +1,90 @@
+package com.lauriewired.handlers.act;
+
+import com.lauriewired.handlers.Handler;
+import com.sun.net.httpserver.HttpExchange;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.listing.*;
+
+import java.io.IOException;
+import java.util.Map;
+
+import static com.lauriewired.util.ParseUtils.parseQueryParams;
+import static com.lauriewired.util.ParseUtils.sendResponse;
+import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram;
+
+/**
+ * Handler for disassembling a function at a given address in Ghidra
+ *
+ * This handler responds to HTTP requests to disassemble a function
+ * and returns the assembly code as a string.
+ */
+public final class DisassembleFunction extends Handler {
+ /**
+ * Constructor for the DisassembleFunction handler
+ *
+ * @param tool the Ghidra plugin tool instance
+ */
+ public DisassembleFunction(PluginTool tool) {
+ super(tool, "/disassemble_function");
+ }
+
+ /**
+ * Handles HTTP requests to disassemble a function at a specified address
+ *
+ * @param exchange the HTTP exchange containing the request
+ * @throws IOException if an I/O error occurs during handling
+ */
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+ Map qparams = parseQueryParams(exchange);
+ String address = qparams.get("address");
+ sendResponse(exchange, disassembleFunction(address));
+ }
+
+ /**
+ * Disassembles the function at the specified address and returns the assembly
+ * code
+ *
+ * @param addressStr the address of the function to disassemble
+ * @return a string containing the disassembled function code
+ */
+ private String disassembleFunction(String addressStr) {
+ Program program = getCurrentProgram(tool);
+ if (program == null)
+ return "No program loaded";
+ if (addressStr == null || addressStr.isEmpty())
+ return "Address is required";
+
+ try {
+ Address addr = program.getAddressFactory().getAddress(addressStr);
+ Function func = program.getListing().getFunctionContaining(addr);
+ if (func == null)
+ return "No function found at or containing address " + addressStr;
+
+ StringBuilder result = new StringBuilder();
+ Listing listing = program.getListing();
+ Address start = func.getEntryPoint();
+ Address end = func.getBody().getMaxAddress();
+
+ InstructionIterator instructions = listing.getInstructions(start, true);
+ while (instructions.hasNext()) {
+ Instruction instr = instructions.next();
+ if (instr.getAddress().compareTo(end) > 0) {
+ break; // Stop if we've gone past the end of the function
+ }
+ String comment = listing.getComment(CodeUnit.EOL_COMMENT, instr.getAddress());
+ comment = (comment != null) ? "; " + comment : "";
+
+ result.append(String.format("%s: %s %s\n",
+ instr.getAddress(),
+ instr.toString(),
+ comment));
+ }
+
+ return result.toString();
+ } catch (Exception e) {
+ return "Error disassembling function: " + e.getMessage();
+ }
+ }
+}
diff --git a/src/main/java/com/lauriewired/handlers/comment/SetDecompilerComment.java b/src/main/java/com/lauriewired/handlers/comment/SetDecompilerComment.java
new file mode 100644
index 0000000..0832d34
--- /dev/null
+++ b/src/main/java/com/lauriewired/handlers/comment/SetDecompilerComment.java
@@ -0,0 +1,53 @@
+package com.lauriewired.handlers.comment;
+
+import com.lauriewired.handlers.Handler;
+import com.sun.net.httpserver.HttpExchange;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.program.model.listing.CodeUnit;
+
+import java.util.Map;
+
+import static com.lauriewired.util.GhidraUtils.setCommentAtAddress;
+import static com.lauriewired.util.ParseUtils.parsePostParams;
+import static com.lauriewired.util.ParseUtils.sendResponse;
+
+/**
+ * Handler for setting a decompiler comment in Ghidra
+ * This handler processes HTTP requests to set comments on decompiled code
+ */
+public final class SetDecompilerComment extends Handler {
+ /**
+ * Constructor for the SetDecompilerComment handler
+ *
+ * @param tool The Ghidra PluginTool instance
+ */
+ public SetDecompilerComment(PluginTool tool) {
+ super(tool, "/set_decompiler_comment");
+ }
+
+ /**
+ * Handles HTTP POST requests to set a decompiler comment
+ *
+ * @param exchange The HTTP exchange containing the request and response
+ * @throws Exception If an error occurs while processing the request
+ */
+ @Override
+ public void handle(HttpExchange exchange) throws Exception {
+ Map params = parsePostParams(exchange);
+ String address = params.get("address");
+ String comment = params.get("comment");
+ boolean success = setDecompilerComment(address, comment);
+ sendResponse(exchange, success ? "Comment set successfully" : "Failed to set comment");
+ }
+
+ /**
+ * Sets a decompiler comment at the specified address
+ *
+ * @param addressStr The address as a string where the comment should be set
+ * @param comment The comment to set
+ * @return true if the comment was set successfully, false otherwise
+ */
+ private boolean setDecompilerComment(String addressStr, String comment) {
+ return setCommentAtAddress(tool, addressStr, comment, CodeUnit.PRE_COMMENT, "Set decompiler comment");
+ }
+}
diff --git a/src/main/java/com/lauriewired/handlers/comment/SetDisassemblyComment.java b/src/main/java/com/lauriewired/handlers/comment/SetDisassemblyComment.java
new file mode 100644
index 0000000..f6c59f4
--- /dev/null
+++ b/src/main/java/com/lauriewired/handlers/comment/SetDisassemblyComment.java
@@ -0,0 +1,54 @@
+package com.lauriewired.handlers.comment;
+
+import com.lauriewired.handlers.Handler;
+import com.sun.net.httpserver.HttpExchange;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.program.model.listing.CodeUnit;
+
+import java.util.Map;
+
+import static com.lauriewired.util.GhidraUtils.setCommentAtAddress;
+import static com.lauriewired.util.ParseUtils.parsePostParams;
+import static com.lauriewired.util.ParseUtils.sendResponse;
+
+/**
+ * Handler for setting a comment in the disassembly at a specific address.
+ * Expects POST request with parameters: address and comment.
+ */
+public final class SetDisassemblyComment extends Handler {
+ /**
+ * Constructor for the SetDisassemblyComment handler.
+ *
+ * @param tool the Ghidra PluginTool instance
+ */
+ public SetDisassemblyComment(PluginTool tool) {
+ super(tool, "/set_disassembly_comment");
+ }
+
+ /**
+ * Handles the HTTP request to set a disassembly comment.
+ * Expects a POST request with parameters: address and comment.
+ *
+ * @param exchange the HTTP exchange containing the request
+ * @throws Exception if an error occurs while handling the request
+ */
+ @Override
+ public void handle(HttpExchange exchange) throws Exception {
+ Map params = parsePostParams(exchange);
+ String address = params.get("address");
+ String comment = params.get("comment");
+ boolean success = setDisassemblyComment(address, comment);
+ sendResponse(exchange, success ? "Comment set successfully" : "Failed to set comment");
+ }
+
+ /**
+ * Sets a disassembly comment at the specified address.
+ *
+ * @param addressStr the address as a string
+ * @param comment the comment to set
+ * @return true if the comment was set successfully, false otherwise
+ */
+ private boolean setDisassemblyComment(String addressStr, String comment) {
+ return setCommentAtAddress(tool, addressStr, comment, CodeUnit.EOL_COMMENT, "Set disassembly comment");
+ }
+}
diff --git a/src/main/java/com/lauriewired/handlers/get/GetAllClassNames.java b/src/main/java/com/lauriewired/handlers/get/GetAllClassNames.java
new file mode 100644
index 0000000..9367163
--- /dev/null
+++ b/src/main/java/com/lauriewired/handlers/get/GetAllClassNames.java
@@ -0,0 +1,71 @@
+package com.lauriewired.handlers.get;
+
+import com.lauriewired.handlers.Handler;
+import com.sun.net.httpserver.HttpExchange;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.program.model.listing.Program;
+import ghidra.program.model.symbol.Namespace;
+import ghidra.program.model.symbol.Symbol;
+
+import java.io.IOException;
+import java.util.*;
+
+import static com.lauriewired.util.ParseUtils.*;
+import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram;
+
+/**
+ * Handler to get all class names in the current program.
+ * Supports pagination via 'offset' and 'limit' query parameters.
+ */
+public final class GetAllClassNames extends Handler {
+ /**
+ * Constructor for the GetAllClassNames handler.
+ *
+ * @param tool The PluginTool instance to use for accessing the current program.
+ */
+ public GetAllClassNames(PluginTool tool) {
+ super(tool, "/classes");
+ }
+
+ /**
+ * Parses the query parameters from the HTTP request and returns a response
+ * containing
+ * all class names in the current program, with optional pagination.
+ *
+ * @param exchange The HttpExchange object representing the HTTP request.
+ * @throws IOException If an I/O error occurs while handling the request.
+ */
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+ Map qparams = parseQueryParams(exchange);
+ int offset = parseIntOrDefault(qparams.get("offset"), 0);
+ int limit = parseIntOrDefault(qparams.get("limit"), 100);
+ sendResponse(exchange, generateResponse(offset, limit));
+ }
+
+ /**
+ * Generates a response containing all class names in the current program,
+ * with optional pagination.
+ *
+ * @param offset The starting index for pagination.
+ * @param limit The maximum number of class names to return.
+ * @return A string containing the paginated list of class names.
+ */
+ private String generateResponse(int offset, int limit) {
+ Program program = getCurrentProgram(tool);
+ if (program == null)
+ return "No program loaded";
+
+ Set classNames = new HashSet<>();
+ for (Symbol symbol : program.getSymbolTable().getAllSymbols(true)) {
+ Namespace ns = symbol.getParentNamespace();
+ if (ns != null && !ns.isGlobal()) {
+ classNames.add(ns.getName());
+ }
+ }
+ // Convert set to list for pagination
+ List sorted = new ArrayList<>(classNames);
+ Collections.sort(sorted);
+ return paginateList(sorted, offset, limit);
+ }
+}
diff --git a/src/main/java/com/lauriewired/handlers/get/GetAllFunctionNames.java b/src/main/java/com/lauriewired/handlers/get/GetAllFunctionNames.java
new file mode 100644
index 0000000..3a6ba82
--- /dev/null
+++ b/src/main/java/com/lauriewired/handlers/get/GetAllFunctionNames.java
@@ -0,0 +1,66 @@
+package com.lauriewired.handlers.get;
+
+import com.lauriewired.handlers.Handler;
+import com.sun.net.httpserver.HttpExchange;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.program.model.listing.Function;
+import ghidra.program.model.listing.Program;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static com.lauriewired.util.ParseUtils.*;
+import static com.lauriewired.util.ParseUtils.parseIntOrDefault;
+import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram;
+
+/**
+ * Handler to get all function names in the current program.
+ *
+ * Example usage: GET /methods?offset=0&limit=100
+ */
+public final class GetAllFunctionNames extends Handler {
+ /**
+ * Constructor for the GetAllFunctionNames handler.
+ *
+ * @param tool the PluginTool instance
+ */
+ public GetAllFunctionNames(PluginTool tool) {
+ super(tool, "/methods");
+ }
+
+ /**
+ * Handles the HTTP request to get all function names.
+ *
+ * @param exchange the HttpExchange instance containing the request
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+ Map qparams = parseQueryParams(exchange);
+ int offset = parseIntOrDefault(qparams.get("offset"), 0);
+ int limit = parseIntOrDefault(qparams.get("limit"), 100);
+ sendResponse(exchange, generateResponse(offset, limit));
+ }
+
+ /**
+ * Generates a paginated response containing all function names in the current
+ * program.
+ *
+ * @param offset the starting index for pagination
+ * @param limit the maximum number of function names to return
+ * @return a string containing the paginated list of function names
+ */
+ private String generateResponse(int offset, int limit) {
+ Program program = getCurrentProgram(tool);
+ if (program == null)
+ return "No program loaded";
+
+ List names = new ArrayList<>();
+ for (Function f : program.getFunctionManager().getFunctions(true)) {
+ names.add(f.getName());
+ }
+ return paginateList(names, offset, limit);
+ }
+}
diff --git a/src/main/java/com/lauriewired/handlers/get/GetBytes.java b/src/main/java/com/lauriewired/handlers/get/GetBytes.java
new file mode 100644
index 0000000..dc76557
--- /dev/null
+++ b/src/main/java/com/lauriewired/handlers/get/GetBytes.java
@@ -0,0 +1,68 @@
+package com.lauriewired.handlers.get;
+
+import com.lauriewired.handlers.Handler;
+import com.sun.net.httpserver.HttpExchange;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.listing.Program;
+
+import java.io.IOException;
+import java.util.*;
+
+import static com.lauriewired.util.ParseUtils.*;
+import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram;
+
+/**
+ * Handler to get bytes from a specified address in the current program.
+ * Expects query parameters: address= and size=.
+ */
+public final class GetBytes extends Handler {
+ /**
+ * Constructor for the GetBytes handler.
+ *
+ * @param tool The PluginTool instance to use.
+ */
+ public GetBytes(PluginTool tool) {
+ super(tool, "/get_bytes");
+ }
+
+ /**
+ * Parses the query parameters from the HTTP exchange.
+ *
+ * @param exchange The HTTP exchange containing the request.
+ * @return A map of query parameters.
+ */
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+ Map qparams = parseQueryParams(exchange);
+ String addrStr = qparams.get("address");
+ int size = parseIntOrDefault(qparams.get("size"), 1);
+ sendResponse(exchange, getBytes(addrStr, size));
+ }
+
+ /**
+ * Gets the bytes from the specified address in the current program.
+ *
+ * @param addressStr The address to read from.
+ * @param size The number of bytes to read.
+ * @return A string representation of the bytes in hex format.
+ */
+ private String getBytes(String addressStr, int size) {
+ Program program = getCurrentProgram(tool);
+ if (program == null)
+ return "No program loaded";
+ if (addressStr == null || addressStr.isEmpty())
+ return "Address is required";
+ if (size <= 0)
+ return "Size must be > 0";
+
+ try {
+ Address addr = program.getAddressFactory().getAddress(addressStr);
+ byte[] buf = new byte[size];
+ int read = program.getMemory().getBytes(addr, buf);
+ return hexdump(addr, buf, read);
+ } catch (Exception e) {
+ return "Error reading memory: " + e.getMessage();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/lauriewired/handlers/get/GetCurrentAddress.java b/src/main/java/com/lauriewired/handlers/get/GetCurrentAddress.java
new file mode 100644
index 0000000..9370a52
--- /dev/null
+++ b/src/main/java/com/lauriewired/handlers/get/GetCurrentAddress.java
@@ -0,0 +1,50 @@
+package com.lauriewired.handlers.get;
+
+import com.lauriewired.handlers.Handler;
+import com.sun.net.httpserver.HttpExchange;
+import ghidra.app.services.CodeViewerService;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.program.util.ProgramLocation;
+
+import java.io.IOException;
+
+import static com.lauriewired.util.ParseUtils.sendResponse;
+
+/**
+ * Handler to get the current address from the CodeViewerService
+ */
+public final class GetCurrentAddress extends Handler {
+ /**
+ * Constructor for GetCurrentAddress handler
+ *
+ * @param tool PluginTool instance to access Ghidra services
+ */
+ public GetCurrentAddress(PluginTool tool) {
+ super(tool, "/get_current_address");
+ }
+
+ /**
+ * Handle HTTP request to get current address
+ *
+ * @param exchange HttpExchange instance containing request and response
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+ sendResponse(exchange, getCurrentAddress());
+ }
+
+ /**
+ * Retrieves the current address from the CodeViewerService
+ *
+ * @return String representation of the current address or an error message
+ */
+ private String getCurrentAddress() {
+ CodeViewerService service = tool.getService(CodeViewerService.class);
+ if (service == null)
+ return "Code viewer service not available";
+
+ ProgramLocation location = service.getCurrentLocation();
+ return (location != null) ? location.getAddress().toString() : "No current location";
+ }
+}
diff --git a/src/main/java/com/lauriewired/handlers/get/GetCurrentFunction.java b/src/main/java/com/lauriewired/handlers/get/GetCurrentFunction.java
new file mode 100644
index 0000000..2fa62fc
--- /dev/null
+++ b/src/main/java/com/lauriewired/handlers/get/GetCurrentFunction.java
@@ -0,0 +1,69 @@
+package com.lauriewired.handlers.get;
+
+import com.lauriewired.handlers.Handler;
+import com.sun.net.httpserver.HttpExchange;
+import ghidra.app.services.CodeViewerService;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.program.model.listing.Function;
+import ghidra.program.model.listing.Program;
+import ghidra.program.util.ProgramLocation;
+
+import java.io.IOException;
+
+import static com.lauriewired.util.ParseUtils.sendResponse;
+import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram;
+
+/**
+ * Handler to get the current function in Ghidra GUI.
+ * Responds with the function name, entry point, and signature.
+ */
+public final class GetCurrentFunction extends Handler {
+ /**
+ * Constructor for the GetCurrentFunction handler.
+ *
+ * @param tool The Ghidra PluginTool instance.
+ */
+ public GetCurrentFunction(PluginTool tool) {
+ super(tool, "/get_current_function");
+ }
+
+ /**
+ * Handles the HTTP request to get the current function.
+ *
+ * @param exchange The HTTP exchange containing the request and response.
+ * @throws IOException If an I/O error occurs during handling.
+ */
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+ sendResponse(exchange, getCurrentFunction());
+ }
+
+ /**
+ * Retrieves the current function at the current location in the Ghidra GUI.
+ *
+ * @return A string containing the function name, entry point, and signature,
+ * or an error message if no function is found or if there are issues.
+ */
+ private String getCurrentFunction() {
+ CodeViewerService service = tool.getService(CodeViewerService.class);
+ if (service == null)
+ return "Code viewer service not available";
+
+ ProgramLocation location = service.getCurrentLocation();
+ if (location == null)
+ return "No current location";
+
+ Program program = getCurrentProgram(tool);
+ if (program == null)
+ return "No program loaded";
+
+ Function func = program.getFunctionManager().getFunctionContaining(location.getAddress());
+ if (func == null)
+ return "No function at current location: " + location.getAddress();
+
+ return String.format("Function: %s at %s\nSignature: %s",
+ func.getName(),
+ func.getEntryPoint(),
+ func.getSignature());
+ }
+}
diff --git a/src/main/java/com/lauriewired/handlers/get/GetDataByLabel.java b/src/main/java/com/lauriewired/handlers/get/GetDataByLabel.java
new file mode 100644
index 0000000..e9ecdb4
--- /dev/null
+++ b/src/main/java/com/lauriewired/handlers/get/GetDataByLabel.java
@@ -0,0 +1,76 @@
+package com.lauriewired.handlers.get;
+
+import com.lauriewired.handlers.Handler;
+import com.sun.net.httpserver.HttpExchange;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.listing.*;
+import ghidra.program.model.symbol.*;
+
+import java.io.IOException;
+import java.util.*;
+
+import static com.lauriewired.util.ParseUtils.*;
+import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram;
+
+/**
+ * Handler to retrieve data associated with a specific label in the current
+ * program.
+ * It responds with the address and value of the data defined at that label.
+ */
+public final class GetDataByLabel extends Handler {
+ /**
+ * Constructor for the GetDataByLabel handler.
+ *
+ * @param tool The PluginTool instance to use for accessing the current program.
+ */
+ public GetDataByLabel(PluginTool tool) {
+ super(tool, "/get_data_by_label");
+ }
+
+ /**
+ * Handles the HTTP request to retrieve data by label.
+ *
+ * @param exchange The HttpExchange object containing the request and response.
+ * @throws IOException If an I/O error occurs during handling.
+ */
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+ Map qparams = parseQueryParams(exchange);
+ String label = qparams.get("label");
+ sendResponse(exchange, getDataByLabel(label));
+ }
+
+ /**
+ * Retrieves data associated with the specified label in the current program.
+ *
+ * @param label The label to search for in the current program.
+ * @return A string containing the address and value of the data defined at that
+ * label,
+ * or an error message if the label is not found or no program is
+ * loaded.
+ */
+ private String getDataByLabel(String label) {
+ Program program = getCurrentProgram(tool);
+ if (program == null)
+ return "No program loaded";
+ if (label == null || label.isEmpty())
+ return "Label is required";
+
+ SymbolTable st = program.getSymbolTable();
+ SymbolIterator it = st.getSymbols(label);
+ if (!it.hasNext())
+ return "Label not found: " + label;
+
+ StringBuilder sb = new StringBuilder();
+ while (it.hasNext()) {
+ Symbol s = it.next();
+ Address a = s.getAddress();
+ Data d = program.getListing().getDefinedDataAt(a);
+ String v = (d != null) ? escapeNonAscii(String.valueOf(d.getDefaultValueRepresentation()))
+ : "(no defined data)";
+ sb.append(String.format("%s -> %s : %s%n", label, a, v));
+ }
+ return sb.toString();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/lauriewired/handlers/get/GetFunctionByAddress.java b/src/main/java/com/lauriewired/handlers/get/GetFunctionByAddress.java
new file mode 100644
index 0000000..40a5e30
--- /dev/null
+++ b/src/main/java/com/lauriewired/handlers/get/GetFunctionByAddress.java
@@ -0,0 +1,69 @@
+package com.lauriewired.handlers.get;
+
+import com.lauriewired.handlers.Handler;
+import com.sun.net.httpserver.HttpExchange;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.listing.Function;
+import ghidra.program.model.listing.Program;
+
+import java.io.IOException;
+import java.util.Map;
+
+import static com.lauriewired.util.ParseUtils.parseQueryParams;
+import static com.lauriewired.util.ParseUtils.sendResponse;
+import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram;
+
+/**
+ * Handler to get function details by address
+ */
+public final class GetFunctionByAddress extends Handler {
+ public GetFunctionByAddress(PluginTool tool) {
+ super(tool, "/get_function_by_address");
+ }
+
+ /**
+ * Handle HTTP GET request to retrieve function details by address
+ *
+ * @param exchange the HTTP exchange
+ * @throws IOException if an I/O error occurs
+ */
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+ Map qparams = parseQueryParams(exchange);
+ String address = qparams.get("address");
+ sendResponse(exchange, getFunctionByAddress(address));
+ }
+
+ /**
+ * Retrieves function details by address
+ *
+ * @param addressStr the address as a string
+ * @return a string containing function details or an error message
+ */
+ private String getFunctionByAddress(String addressStr) {
+ Program program = getCurrentProgram(tool);
+ if (program == null)
+ return "No program loaded";
+ if (addressStr == null || addressStr.isEmpty())
+ return "Address is required";
+
+ try {
+ Address addr = program.getAddressFactory().getAddress(addressStr);
+ Function func = program.getFunctionManager().getFunctionAt(addr);
+
+ if (func == null)
+ return "No function found at address " + addressStr;
+
+ return String.format("Function: %s at %s\nSignature: %s\nEntry: %s\nBody: %s - %s",
+ func.getName(),
+ func.getEntryPoint(),
+ func.getSignature(),
+ func.getEntryPoint(),
+ func.getBody().getMinAddress(),
+ func.getBody().getMaxAddress());
+ } catch (Exception e) {
+ return "Error getting function: " + e.getMessage();
+ }
+ }
+}
diff --git a/src/main/java/com/lauriewired/handlers/get/GetFunctionXrefs.java b/src/main/java/com/lauriewired/handlers/get/GetFunctionXrefs.java
new file mode 100644
index 0000000..4856d26
--- /dev/null
+++ b/src/main/java/com/lauriewired/handlers/get/GetFunctionXrefs.java
@@ -0,0 +1,91 @@
+package com.lauriewired.handlers.get;
+
+import com.lauriewired.handlers.Handler;
+import com.sun.net.httpserver.HttpExchange;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.listing.Function;
+import ghidra.program.model.listing.FunctionManager;
+import ghidra.program.model.listing.Program;
+import ghidra.program.model.symbol.RefType;
+import ghidra.program.model.symbol.Reference;
+import ghidra.program.model.symbol.ReferenceIterator;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static com.lauriewired.util.ParseUtils.*;
+import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram;
+
+/**
+ * Handler to get all references to a specific function by name.
+ * Expects query parameters: name, offset, limit
+ */
+public final class GetFunctionXrefs extends Handler {
+ public GetFunctionXrefs(PluginTool tool) {
+ super(tool, "/function_xrefs");
+ }
+
+ /**
+ * Handles the HTTP request to get function cross-references.
+ * Expects query parameters: name, offset, limit
+ *
+ * @param exchange the HTTP exchange containing the request
+ * @throws Exception if an error occurs while processing the request
+ */
+ @Override
+ public void handle(HttpExchange exchange) throws Exception {
+ Map qparams = parseQueryParams(exchange);
+ String name = qparams.get("name");
+ int offset = parseIntOrDefault(qparams.get("offset"), 0);
+ int limit = parseIntOrDefault(qparams.get("limit"), 100);
+ sendResponse(exchange, getFunctionXrefs(name, offset, limit));
+ }
+
+ /**
+ * Retrieves cross-references to a function by its name.
+ *
+ * @param functionName the name of the function to find references for
+ * @param offset the starting index for pagination
+ * @param limit the maximum number of results to return
+ * @return a string containing the references or an error message
+ */
+ private String getFunctionXrefs(String functionName, int offset, int limit) {
+ Program program = getCurrentProgram(tool);
+ if (program == null)
+ return "No program loaded";
+ if (functionName == null || functionName.isEmpty())
+ return "Function name is required";
+
+ try {
+ List refs = new ArrayList<>();
+ FunctionManager funcManager = program.getFunctionManager();
+ for (Function function : funcManager.getFunctions(true)) {
+ if (function.getName().equals(functionName)) {
+ Address entryPoint = function.getEntryPoint();
+ ReferenceIterator refIter = program.getReferenceManager().getReferencesTo(entryPoint);
+
+ while (refIter.hasNext()) {
+ Reference ref = refIter.next();
+ Address fromAddr = ref.getFromAddress();
+ RefType refType = ref.getReferenceType();
+
+ Function fromFunc = funcManager.getFunctionContaining(fromAddr);
+ String funcInfo = (fromFunc != null) ? " in " + fromFunc.getName() : "";
+
+ refs.add(String.format("From %s%s [%s]", fromAddr, funcInfo, refType.getName()));
+ }
+ }
+ }
+
+ if (refs.isEmpty()) {
+ return "No references found to function: " + functionName;
+ }
+
+ return paginateList(refs, offset, limit);
+ } catch (Exception e) {
+ return "Error getting function references: " + e.getMessage();
+ }
+ }
+}
diff --git a/src/main/java/com/lauriewired/handlers/get/GetStruct.java b/src/main/java/com/lauriewired/handlers/get/GetStruct.java
new file mode 100644
index 0000000..b4e5530
--- /dev/null
+++ b/src/main/java/com/lauriewired/handlers/get/GetStruct.java
@@ -0,0 +1,98 @@
+package com.lauriewired.handlers.get;
+
+import com.lauriewired.handlers.Handler;
+import com.sun.net.httpserver.HttpExchange;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.program.model.data.DataType;
+import ghidra.program.model.data.DataTypeComponent;
+import ghidra.program.model.data.DataTypeManager;
+import ghidra.program.model.data.Structure;
+import ghidra.program.model.listing.Program;
+
+import com.google.gson.Gson;
+
+import java.io.IOException;
+import java.util.*;
+
+import static com.lauriewired.util.ParseUtils.*;
+import ghidra.program.model.data.CategoryPath;
+import static ghidra.program.util.GhidraProgramUtilities.getCurrentProgram;
+
+/**
+ * Handler for retrieving details of a structure by its name and category.
+ * Expects query parameters: name (required), category (optional).
+ */
+public final class GetStruct extends Handler {
+ /**
+ * Constructor for the GetStruct handler.
+ *
+ * @param tool the PluginTool instance to use for accessing the current program.
+ */
+ public GetStruct(PluginTool tool) {
+ super(tool, "/get_struct");
+ }
+
+ /**
+ * Handles the HTTP request to retrieve structure details.
+ *
+ * @param exchange the HttpExchange object containing the request and response.
+ * @throws IOException if an I/O error occurs during handling.
+ */
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+ Map qparams = parseQueryParams(exchange);
+ String structName = qparams.get("name");
+ String category = qparams.get("category");
+ if (structName == null) {
+ sendResponse(exchange, "name is required");
+ return;
+ }
+ sendResponse(exchange, getStruct(structName, category));
+ }
+
+ /**
+ * Retrieves the structure details as a JSON string.
+ *
+ * @param structName the name of the structure to retrieve.
+ * @param category the category path where the structure is located
+ * (optional).
+ * @return a JSON representation of the structure or an error message if not
+ * found.
+ */
+ private String getStruct(String structName, String category) {
+ Program program = getCurrentProgram(tool);
+ if (program == null)
+ return "No program loaded";
+
+ DataTypeManager dtm = program.getDataTypeManager();
+ CategoryPath path = new CategoryPath(category == null ? "/" : category);
+ DataType dt = dtm.getDataType(path, structName);
+
+ if (dt == null || !(dt instanceof Structure)) {
+ return "Error: Struct " + structName + " not found in category " + path;
+ }
+
+ Structure struct = (Structure) dt;
+
+ Map structRepr = new HashMap<>();
+ structRepr.put("name", struct.getName());
+ structRepr.put("category", struct.getCategoryPath().getPath());
+ structRepr.put("size", struct.getLength());
+ structRepr.put("isNotYetDefined", struct.isNotYetDefined());
+
+ List