1412 lines
77 KiB
Java
1412 lines
77 KiB
Java
package eu.starsong.ghidra.endpoints;
|
|
|
|
import com.google.gson.JsonObject;
|
|
import com.sun.net.httpserver.HttpExchange;
|
|
import com.sun.net.httpserver.HttpServer;
|
|
import eu.starsong.ghidra.util.TransactionHelper;
|
|
import eu.starsong.ghidra.util.TransactionHelper.TransactionException;
|
|
import ghidra.program.model.address.Address;
|
|
import ghidra.framework.plugintool.PluginTool;
|
|
import ghidra.program.model.listing.Data;
|
|
import ghidra.program.model.listing.DataIterator;
|
|
import ghidra.program.model.listing.Listing;
|
|
import ghidra.program.model.listing.Program;
|
|
import ghidra.program.model.mem.MemoryBlock;
|
|
import ghidra.program.model.symbol.SourceType;
|
|
import ghidra.program.model.symbol.Symbol;
|
|
import ghidra.program.model.symbol.SymbolTable;
|
|
import ghidra.util.Msg;
|
|
|
|
import java.io.IOException;
|
|
import java.util.*;
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
import javax.swing.SwingUtilities;
|
|
import java.lang.reflect.InvocationTargetException;
|
|
|
|
public class DataEndpoints extends AbstractEndpoint {
|
|
|
|
private PluginTool tool;
|
|
|
|
// Updated constructor to accept port
|
|
public DataEndpoints(Program program, int port) {
|
|
super(program, port); // Call super constructor
|
|
}
|
|
|
|
public DataEndpoints(Program program, int port, PluginTool tool) {
|
|
super(program, port);
|
|
this.tool = tool;
|
|
}
|
|
|
|
@Override
|
|
protected PluginTool getTool() {
|
|
return tool;
|
|
}
|
|
|
|
@Override
|
|
public void registerEndpoints(HttpServer server) {
|
|
server.createContext("/data", this::handleData);
|
|
server.createContext("/data/delete", exchange -> {
|
|
try {
|
|
if ("POST".equals(exchange.getRequestMethod())) {
|
|
Map<String, String> params = parseJsonPostParams(exchange);
|
|
handleDeleteData(exchange, params);
|
|
} else {
|
|
sendErrorResponse(exchange, 405, "Method Not Allowed");
|
|
}
|
|
} catch (Exception e) {
|
|
Msg.error(this, "Error in /data/delete endpoint", e);
|
|
sendErrorResponse(exchange, 500, "Internal server error: " + e.getMessage());
|
|
}
|
|
});
|
|
server.createContext("/data/update", exchange -> {
|
|
try {
|
|
if ("POST".equals(exchange.getRequestMethod())) {
|
|
Map<String, String> params = parseJsonPostParams(exchange);
|
|
handleUpdateData(exchange, params);
|
|
} else {
|
|
sendErrorResponse(exchange, 405, "Method Not Allowed");
|
|
}
|
|
} catch (Exception e) {
|
|
Msg.error(this, "Error in /data/update endpoint", e);
|
|
sendErrorResponse(exchange, 500, "Internal server error: " + e.getMessage());
|
|
}
|
|
});
|
|
server.createContext("/data/type", exchange -> {
|
|
try {
|
|
if ("POST".equals(exchange.getRequestMethod()) || "PATCH".equals(exchange.getRequestMethod())) {
|
|
Map<String, String> params = parseJsonPostParams(exchange);
|
|
handleTypeChangeData(exchange, params);
|
|
} else {
|
|
sendErrorResponse(exchange, 405, "Method Not Allowed");
|
|
}
|
|
} catch (Exception e) {
|
|
Msg.error(this, "Error in /data/type endpoint", e);
|
|
sendErrorResponse(exchange, 500, "Internal server error: " + e.getMessage());
|
|
}
|
|
});
|
|
server.createContext("/strings", exchange -> {
|
|
try {
|
|
if ("GET".equals(exchange.getRequestMethod())) {
|
|
handleListStrings(exchange);
|
|
} else {
|
|
sendErrorResponse(exchange, 405, "Method Not Allowed");
|
|
}
|
|
} catch (Exception e) {
|
|
Msg.error(this, "Error in /strings endpoint", e);
|
|
sendErrorResponse(exchange, 500, "Internal server error: " + e.getMessage());
|
|
}
|
|
});
|
|
}
|
|
|
|
public void handleData(HttpExchange exchange) throws IOException {
|
|
try {
|
|
if ("GET".equals(exchange.getRequestMethod())) {
|
|
handleListData(exchange);
|
|
} else if ("POST".equals(exchange.getRequestMethod())) {
|
|
// Determine what kind of operation this is based on parameters
|
|
Map<String, String> params = parseJsonPostParams(exchange);
|
|
// Debug - log the params
|
|
StringBuilder debugInfo = new StringBuilder("DEBUG - Received parameters: ");
|
|
for (Map.Entry<String, String> entry : params.entrySet()) {
|
|
debugInfo.append(entry.getKey()).append("=").append(entry.getValue()).append(", ");
|
|
}
|
|
Msg.info(this, debugInfo.toString());
|
|
|
|
boolean hasNewName = params.containsKey("newName") && params.get("newName") != null && !params.get("newName").isEmpty();
|
|
boolean hasType = params.containsKey("type") && params.get("type") != null && !params.get("type").isEmpty();
|
|
boolean hasSize = params.containsKey("size") && params.get("size") != null && !params.get("size").isEmpty();
|
|
|
|
// Add more detailed debugging
|
|
Msg.info(this, "Decision logic: hasNewName=" + hasNewName + ", hasType=" + hasType + ", hasSize=" + hasSize);
|
|
Msg.info(this, "Raw newName value: " + params.get("newName"));
|
|
Msg.info(this, "Raw type value: " + params.get("type"));
|
|
Msg.info(this, "Raw address value: " + params.get("address"));
|
|
Msg.info(this, "Raw size value: " + params.get("size"));
|
|
|
|
// Check if this is a create operation (address + type without checking for existing data)
|
|
if (params.containsKey("address") && hasType) {
|
|
// Check if the data already exists at the address
|
|
try {
|
|
Program program = getCurrentProgram();
|
|
if (program != null) {
|
|
Address addr = program.getAddressFactory().getAddress(params.get("address"));
|
|
Listing listing = program.getListing();
|
|
Data data = listing.getDefinedDataAt(addr);
|
|
|
|
if (data == null) {
|
|
// No data exists at this address, so treat as a create operation
|
|
Msg.info(this, "Selected route: handleCreateData - creating new data");
|
|
handleCreateData(exchange, params);
|
|
return;
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
Msg.warn(this, "Error checking for existing data: " + e.getMessage());
|
|
// Continue with normal processing
|
|
}
|
|
}
|
|
|
|
// Proceeding with update operations if not create
|
|
if (params.containsKey("address") && hasNewName && hasType) {
|
|
Msg.info(this, "Selected route: handleUpdateData - both name and type");
|
|
handleUpdateData(exchange, params);
|
|
} else if (params.containsKey("address") && hasNewName) {
|
|
Msg.info(this, "Selected route: handleRenameData - only name");
|
|
handleRenameData(exchange, params);
|
|
} else if (params.containsKey("address") && hasType) {
|
|
Msg.info(this, "Selected route: handleTypeChangeData - only type");
|
|
handleTypeChangeData(exchange, params);
|
|
} else {
|
|
Msg.info(this, "Selected route: Error - missing parameters");
|
|
// Neither parameter was provided
|
|
sendErrorResponse(exchange, 400, "Missing required parameters: at least one of newName or type must be provided", "MISSING_PARAMETERS");
|
|
}
|
|
} else {
|
|
sendErrorResponse(exchange, 405, "Method Not Allowed");
|
|
}
|
|
} catch (Exception e) {
|
|
Msg.error(this, "Error in /data endpoint", e);
|
|
sendErrorResponse(exchange, 500, "Internal server error: " + e.getMessage());
|
|
}
|
|
}
|
|
|
|
private void handleListData(HttpExchange exchange) throws IOException {
|
|
try {
|
|
Map<String, String> qparams = parseQueryParams(exchange);
|
|
int offset = parseIntOrDefault(qparams.get("offset"), 0);
|
|
int limit = parseIntOrDefault(qparams.get("limit"), 100);
|
|
|
|
Program program = getCurrentProgram();
|
|
if (program == null) {
|
|
sendErrorResponse(exchange, 400, "No program loaded", "NO_PROGRAM_LOADED");
|
|
return;
|
|
}
|
|
|
|
List<Map<String, Object>> dataItems = new ArrayList<>();
|
|
for (MemoryBlock block : program.getMemory().getBlocks()) {
|
|
DataIterator it = program.getListing().getDefinedData(block.getStart(), true);
|
|
while (it.hasNext()) {
|
|
Data data = it.next();
|
|
if (block.contains(data.getAddress())) {
|
|
// Apply addr filter if present
|
|
String addrFilter = qparams.get("addr");
|
|
if (addrFilter != null && !data.getAddress().toString().equals(addrFilter)) {
|
|
continue; // Skip this data item if address doesn't match filter
|
|
}
|
|
|
|
Map<String, Object> item = new HashMap<>();
|
|
item.put("address", data.getAddress().toString());
|
|
item.put("label", data.getLabel() != null ? data.getLabel() : "(unnamed)");
|
|
item.put("value", data.getDefaultValueRepresentation());
|
|
item.put("dataType", data.getDataType().getName());
|
|
|
|
// Add HATEOAS links
|
|
Map<String, Object> links = new HashMap<>();
|
|
Map<String, String> selfLink = new HashMap<>();
|
|
selfLink.put("href", "/data/" + data.getAddress().toString());
|
|
links.put("self", selfLink);
|
|
item.put("_links", links);
|
|
|
|
dataItems.add(item);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Build response with HATEOAS links
|
|
eu.starsong.ghidra.api.ResponseBuilder builder = new eu.starsong.ghidra.api.ResponseBuilder(exchange, port)
|
|
.success(true);
|
|
|
|
// Apply pagination and get paginated items
|
|
List<Map<String, Object>> paginated = applyPagination(dataItems, offset, limit, builder, "/data");
|
|
|
|
// Set the paginated result
|
|
builder.result(paginated);
|
|
|
|
// Add program link
|
|
builder.addLink("program", "/program");
|
|
|
|
sendJsonResponse(exchange, builder.build(), 200);
|
|
} catch (Exception e) {
|
|
Msg.error(this, "Error listing data", e);
|
|
sendErrorResponse(exchange, 500, "Error listing data: " + e.getMessage(), "INTERNAL_ERROR");
|
|
}
|
|
}
|
|
|
|
private void handleRenameData(HttpExchange exchange, Map<String, String> params) throws IOException {
|
|
try {
|
|
// Debug - log the params again
|
|
StringBuilder debugInfo = new StringBuilder("DEBUG handleRenameData - Received parameters: ");
|
|
for (Map.Entry<String, String> entry : params.entrySet()) {
|
|
debugInfo.append(entry.getKey()).append("=").append(entry.getValue()).append(", ");
|
|
}
|
|
Msg.info(this, debugInfo.toString());
|
|
|
|
final String addressStr = params.get("address");
|
|
final String newName = params.get("newName");
|
|
final String dataTypeStr = params.get("type");
|
|
|
|
// Address is always required
|
|
if (addressStr == null || addressStr.isEmpty()) {
|
|
sendErrorResponse(exchange, 400, "Missing required parameter: address", "MISSING_PARAMETERS");
|
|
return;
|
|
}
|
|
|
|
// Either newName or type or both must be provided
|
|
if ((newName == null || newName.isEmpty()) && (dataTypeStr == null || dataTypeStr.isEmpty())) {
|
|
sendErrorResponse(exchange, 400, "At least one of newName or type must be provided", "MISSING_PARAMETERS");
|
|
return;
|
|
}
|
|
|
|
Program program = getCurrentProgram();
|
|
if (program == null) {
|
|
sendErrorResponse(exchange, 400, "No program loaded", "NO_PROGRAM_LOADED");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Create a result map to collect operation results
|
|
Map<String, Object> resultMap = new HashMap<>();
|
|
resultMap.put("address", addressStr);
|
|
|
|
TransactionHelper.executeInTransaction(program, "Update Data", () -> {
|
|
// Get the data at the address first
|
|
Address addr = program.getAddressFactory().getAddress(addressStr);
|
|
Listing listing = program.getListing();
|
|
Data data = listing.getDefinedDataAt(addr);
|
|
|
|
if (data == null) {
|
|
throw new Exception("No defined data found at address: " + addressStr);
|
|
}
|
|
|
|
// Get current data info for operations that need it
|
|
String currentName = null;
|
|
if (data.getLabel() != null) {
|
|
currentName = data.getLabel();
|
|
} else {
|
|
Symbol sym = program.getSymbolTable().getPrimarySymbol(addr);
|
|
if (sym != null) {
|
|
currentName = sym.getName();
|
|
}
|
|
}
|
|
|
|
// If we need to set a data type
|
|
if (dataTypeStr != null && !dataTypeStr.isEmpty()) {
|
|
// Find the data type
|
|
ghidra.program.model.data.DataType dataType = null;
|
|
|
|
// First try built-in types
|
|
dataType = program.getDataTypeManager().getDataType("/" + dataTypeStr);
|
|
|
|
// If not found, try to find it without path
|
|
if (dataType == null) {
|
|
dataType = program.getDataTypeManager().findDataType("/" + dataTypeStr);
|
|
}
|
|
|
|
// If still null, try using the parser
|
|
if (dataType == null) {
|
|
try {
|
|
ghidra.app.util.parser.FunctionSignatureParser parser =
|
|
new ghidra.app.util.parser.FunctionSignatureParser(program.getDataTypeManager(), null);
|
|
dataType = parser.parse(null, dataTypeStr);
|
|
} catch (Exception e) {
|
|
Msg.debug(this, "Function signature parser failed: " + e.getMessage());
|
|
}
|
|
}
|
|
|
|
if (dataType == null) {
|
|
throw new Exception("Could not find or parse data type: " + dataTypeStr);
|
|
}
|
|
|
|
// Apply the data type
|
|
try {
|
|
// Clear any existing data first
|
|
listing.clearCodeUnits(addr, addr.add(data.getLength() - 1), false);
|
|
|
|
// Create new data with the type
|
|
Data newData = listing.createData(addr, dataType);
|
|
if (newData == null) {
|
|
throw new Exception("Failed to apply data type " + dataTypeStr + " at " + addressStr);
|
|
}
|
|
|
|
// Capture info for response
|
|
resultMap.put("dataType", dataTypeStr);
|
|
resultMap.put("originalType", data.getDataType().getName());
|
|
|
|
// Update our reference to the data
|
|
data = newData;
|
|
} catch (Exception e) {
|
|
throw new Exception("Error applying data type: " + e.getMessage(), e);
|
|
}
|
|
}
|
|
|
|
// Handle renaming if needed
|
|
if (newName != null && !newName.isEmpty()) {
|
|
SymbolTable symTable = program.getSymbolTable();
|
|
Symbol symbol = symTable.getPrimarySymbol(addr);
|
|
|
|
if (symbol != null) {
|
|
symbol.setName(newName, SourceType.USER_DEFINED);
|
|
} else {
|
|
// Create a new label if no primary symbol exists
|
|
symTable.createLabel(addr, newName, SourceType.USER_DEFINED);
|
|
}
|
|
|
|
resultMap.put("name", newName);
|
|
if (currentName != null) {
|
|
resultMap.put("originalName", currentName);
|
|
}
|
|
} else if (currentName != null) {
|
|
// If we didn't rename but have a name from data type change, preserve it
|
|
SymbolTable symTable = program.getSymbolTable();
|
|
Symbol symbol = symTable.getPrimarySymbol(addr);
|
|
|
|
if (symbol == null || !symbol.getName().equals(currentName)) {
|
|
if (symbol != null) {
|
|
symbol.setName(currentName, SourceType.USER_DEFINED);
|
|
} else {
|
|
symTable.createLabel(addr, currentName, SourceType.USER_DEFINED);
|
|
}
|
|
}
|
|
|
|
resultMap.put("name", currentName);
|
|
}
|
|
|
|
return null; // Return null for void operation
|
|
});
|
|
|
|
// Add a meaningful message
|
|
String message;
|
|
if (newName != null && !newName.isEmpty() && dataTypeStr != null && !dataTypeStr.isEmpty()) {
|
|
message = "Data renamed and type changed successfully";
|
|
} else if (newName != null && !newName.isEmpty()) {
|
|
message = "Data renamed successfully";
|
|
} else {
|
|
message = "Data type changed successfully";
|
|
}
|
|
resultMap.put("message", message);
|
|
|
|
// Build HATEOAS response
|
|
eu.starsong.ghidra.api.ResponseBuilder builder = new eu.starsong.ghidra.api.ResponseBuilder(exchange, port)
|
|
.success(true)
|
|
.result(resultMap);
|
|
|
|
// Add relevant links
|
|
builder.addLink("self", "/data/" + addressStr);
|
|
builder.addLink("data", "/data");
|
|
builder.addLink("program", "/program");
|
|
|
|
sendJsonResponse(exchange, builder.build(), 200);
|
|
} catch (TransactionException e) {
|
|
Msg.error(this, "Transaction failed: Update Data", e);
|
|
sendErrorResponse(exchange, 500, "Failed to update data: " + e.getMessage(), "TRANSACTION_ERROR");
|
|
} catch (Exception e) { // Catch potential AddressFormatException or other issues
|
|
Msg.error(this, "Error during data update operation", e);
|
|
sendErrorResponse(exchange, 400, "Error updating data: " + e.getMessage(), "INVALID_PARAMETER");
|
|
}
|
|
} catch (IOException e) {
|
|
Msg.error(this, "Error parsing POST params for data update", e);
|
|
sendErrorResponse(exchange, 400, "Invalid request body: " + e.getMessage(), "INVALID_REQUEST");
|
|
} catch (Exception e) { // Catch unexpected errors
|
|
Msg.error(this, "Unexpected error updating data", e);
|
|
sendErrorResponse(exchange, 500, "Error updating data: " + e.getMessage(), "INTERNAL_ERROR");
|
|
}
|
|
}
|
|
|
|
|
|
private boolean renameDataAtAddress(Program program, String addressStr, String newName) throws Exception {
|
|
// This method now throws Exception to be caught by the transaction helper
|
|
AtomicBoolean successFlag = new AtomicBoolean(false);
|
|
try {
|
|
Address addr = program.getAddressFactory().getAddress(addressStr);
|
|
Listing listing = program.getListing();
|
|
Data data = listing.getDefinedDataAt(addr);
|
|
if (data != null) {
|
|
SymbolTable symTable = program.getSymbolTable();
|
|
Symbol symbol = symTable.getPrimarySymbol(addr);
|
|
if (symbol != null) {
|
|
symbol.setName(newName, SourceType.USER_DEFINED);
|
|
successFlag.set(true);
|
|
} else {
|
|
// Create a new label if no primary symbol exists
|
|
symTable.createLabel(addr, newName, SourceType.USER_DEFINED);
|
|
successFlag.set(true);
|
|
}
|
|
} else {
|
|
throw new Exception("No defined data found at address: " + addressStr);
|
|
}
|
|
} catch (ghidra.program.model.address.AddressFormatException afe) {
|
|
throw new Exception("Invalid address format: " + addressStr, afe);
|
|
} catch (ghidra.util.exception.InvalidInputException iie) {
|
|
throw new Exception("Invalid name: " + newName, iie);
|
|
} catch (Exception e) { // Catch other potential Ghidra exceptions
|
|
throw new Exception("Failed to rename data at " + addressStr, e);
|
|
}
|
|
return successFlag.get();
|
|
}
|
|
|
|
/**
|
|
* Handle a data type change request (without renaming)
|
|
*/
|
|
public void handleTypeChangeData(HttpExchange exchange, Map<String, String> params) throws IOException {
|
|
try {
|
|
// Debug - log all parameters received by this method
|
|
StringBuilder debugInfo = new StringBuilder("DEBUG handleTypeChangeData - Received parameters: ");
|
|
for (Map.Entry<String, String> entry : params.entrySet()) {
|
|
debugInfo.append(entry.getKey()).append("=").append(entry.getValue()).append(", ");
|
|
}
|
|
Msg.info(this, debugInfo.toString());
|
|
|
|
final String addressStr = params.get("address");
|
|
final String dataTypeStr = params.get("type");
|
|
|
|
Msg.info(this, "handleTypeChangeData - extracted parameters: address=" + addressStr +
|
|
", type=" + dataTypeStr);
|
|
|
|
if (addressStr == null || addressStr.isEmpty()) {
|
|
Msg.info(this, "handleTypeChangeData - Missing required parameter: address");
|
|
sendErrorResponse(exchange, 400, "Missing required parameter: address", "MISSING_PARAMETERS");
|
|
return;
|
|
}
|
|
|
|
if (dataTypeStr == null || dataTypeStr.isEmpty()) {
|
|
sendErrorResponse(exchange, 400, "Missing required parameter: type", "MISSING_PARAMETERS");
|
|
return;
|
|
}
|
|
|
|
Program program = getCurrentProgram();
|
|
if (program == null) {
|
|
sendErrorResponse(exchange, 400, "No program loaded", "NO_PROGRAM_LOADED");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Create a result map to collect operation results
|
|
Map<String, Object> resultMap = new HashMap<>();
|
|
resultMap.put("address", addressStr);
|
|
resultMap.put("dataType", dataTypeStr);
|
|
|
|
TransactionHelper.executeInTransaction(program, "Change Data Type", () -> {
|
|
// Get the data at the address first
|
|
Address addr = program.getAddressFactory().getAddress(addressStr);
|
|
Listing listing = program.getListing();
|
|
Data data = listing.getDefinedDataAt(addr);
|
|
|
|
if (data == null) {
|
|
throw new Exception("No defined data found at address: " + addressStr);
|
|
}
|
|
|
|
// Get current name to preserve after type change
|
|
String currentName = null;
|
|
Symbol symbol = program.getSymbolTable().getPrimarySymbol(addr);
|
|
if (symbol != null) {
|
|
currentName = symbol.getName();
|
|
resultMap.put("originalName", currentName);
|
|
}
|
|
|
|
// Remember original data type
|
|
String originalType = data.getDataType().getName();
|
|
resultMap.put("originalType", originalType);
|
|
|
|
// Find the requested data type
|
|
ghidra.program.model.data.DataType dataType = null;
|
|
|
|
// First try built-in types
|
|
dataType = program.getDataTypeManager().getDataType("/" + dataTypeStr);
|
|
|
|
// If not found, try to find it without path
|
|
if (dataType == null) {
|
|
dataType = program.getDataTypeManager().findDataType("/" + dataTypeStr);
|
|
}
|
|
|
|
// If still null, try using the parser
|
|
if (dataType == null) {
|
|
try {
|
|
ghidra.app.util.parser.FunctionSignatureParser parser =
|
|
new ghidra.app.util.parser.FunctionSignatureParser(program.getDataTypeManager(), null);
|
|
dataType = parser.parse(null, dataTypeStr);
|
|
} catch (Exception e) {
|
|
Msg.debug(this, "Function signature parser failed: " + e.getMessage());
|
|
}
|
|
}
|
|
|
|
if (dataType == null) {
|
|
throw new Exception("Could not find or parse data type: " + dataTypeStr);
|
|
}
|
|
|
|
// Clear existing data
|
|
int length = data.getLength();
|
|
listing.clearCodeUnits(addr, addr.add(length - 1), false);
|
|
|
|
// Create new data
|
|
Data newData = listing.createData(addr, dataType);
|
|
if (newData == null) {
|
|
throw new Exception("Failed to create data with type " + dataTypeStr);
|
|
}
|
|
|
|
// Preserve the original name
|
|
if (currentName != null) {
|
|
SymbolTable symTable = program.getSymbolTable();
|
|
symTable.createLabel(addr, currentName, SourceType.USER_DEFINED);
|
|
resultMap.put("name", currentName);
|
|
}
|
|
|
|
return null;
|
|
});
|
|
|
|
resultMap.put("message", "Data type changed successfully");
|
|
|
|
// Build HATEOAS response
|
|
eu.starsong.ghidra.api.ResponseBuilder builder = new eu.starsong.ghidra.api.ResponseBuilder(exchange, port)
|
|
.success(true)
|
|
.result(resultMap);
|
|
|
|
// Add relevant links
|
|
builder.addLink("self", "/data/" + addressStr);
|
|
builder.addLink("data", "/data");
|
|
builder.addLink("program", "/program");
|
|
|
|
sendJsonResponse(exchange, builder.build(), 200);
|
|
} catch (TransactionException e) {
|
|
Msg.error(this, "Transaction failed: Change Data Type", e);
|
|
sendErrorResponse(exchange, 500, "Failed to change data type: " + e.getMessage(), "TRANSACTION_ERROR");
|
|
} catch (Exception e) {
|
|
Msg.error(this, "Error changing data type", e);
|
|
sendErrorResponse(exchange, 400, "Error changing data type: " + e.getMessage(), "INVALID_PARAMETER");
|
|
}
|
|
} catch (IOException e) {
|
|
Msg.error(this, "Error parsing POST params for data type change", e);
|
|
sendErrorResponse(exchange, 400, "Invalid request body: " + e.getMessage(), "INVALID_REQUEST");
|
|
} catch (Exception e) {
|
|
Msg.error(this, "Unexpected error changing data type", e);
|
|
sendErrorResponse(exchange, 500, "Error changing data type: " + e.getMessage(), "INTERNAL_ERROR");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle a combined update request (both name and type)
|
|
*/
|
|
public void handleUpdateData(HttpExchange exchange, Map<String, String> params) throws IOException {
|
|
try {
|
|
// Debug - log all parameters received by this method
|
|
StringBuilder debugInfo = new StringBuilder("DEBUG handleUpdateData - Received parameters: ");
|
|
for (Map.Entry<String, String> entry : params.entrySet()) {
|
|
debugInfo.append(entry.getKey()).append("=").append(entry.getValue()).append(", ");
|
|
}
|
|
Msg.info(this, debugInfo.toString());
|
|
|
|
final String addressStr = params.get("address");
|
|
final String newName = params.get("newName");
|
|
final String dataTypeStr = params.get("type");
|
|
|
|
Msg.info(this, "handleUpdateData - extracted parameters: address=" + addressStr +
|
|
", newName=" + newName + ", type=" + dataTypeStr);
|
|
|
|
if (addressStr == null || addressStr.isEmpty()) {
|
|
Msg.info(this, "handleUpdateData - Missing required parameter: address");
|
|
sendErrorResponse(exchange, 400, "Missing required parameter: address", "MISSING_PARAMETERS");
|
|
return;
|
|
}
|
|
|
|
if ((newName == null || newName.isEmpty()) && (dataTypeStr == null || dataTypeStr.isEmpty())) {
|
|
sendErrorResponse(exchange, 400, "Missing required parameters: at least one of newName or type must be provided", "MISSING_PARAMETERS");
|
|
return;
|
|
}
|
|
|
|
Program program = getCurrentProgram();
|
|
if (program == null) {
|
|
sendErrorResponse(exchange, 400, "No program loaded", "NO_PROGRAM_LOADED");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Create a result map to collect operation results
|
|
Map<String, Object> resultMap = new HashMap<>();
|
|
resultMap.put("address", addressStr);
|
|
|
|
TransactionHelper.executeInTransaction(program, "Update Data", () -> {
|
|
// Get the data at the address first
|
|
Address addr = program.getAddressFactory().getAddress(addressStr);
|
|
Listing listing = program.getListing();
|
|
Data data = listing.getDefinedDataAt(addr);
|
|
|
|
if (data == null) {
|
|
throw new Exception("No defined data found at address: " + addressStr);
|
|
}
|
|
|
|
// Get current name
|
|
String currentName = null;
|
|
Symbol symbol = program.getSymbolTable().getPrimarySymbol(addr);
|
|
if (symbol != null) {
|
|
currentName = symbol.getName();
|
|
resultMap.put("originalName", currentName);
|
|
}
|
|
|
|
// Handle type change if requested
|
|
if (dataTypeStr != null && !dataTypeStr.isEmpty()) {
|
|
// Remember original type
|
|
String originalType = data.getDataType().getName();
|
|
resultMap.put("originalType", originalType);
|
|
|
|
// Find the data type
|
|
ghidra.program.model.data.DataType dataType = null;
|
|
|
|
// First try built-in types
|
|
dataType = program.getDataTypeManager().getDataType("/" + dataTypeStr);
|
|
|
|
// If not found, try to find it without path
|
|
if (dataType == null) {
|
|
dataType = program.getDataTypeManager().findDataType("/" + dataTypeStr);
|
|
}
|
|
|
|
// If still null, try using the parser
|
|
if (dataType == null) {
|
|
try {
|
|
ghidra.app.util.parser.FunctionSignatureParser parser =
|
|
new ghidra.app.util.parser.FunctionSignatureParser(program.getDataTypeManager(), null);
|
|
dataType = parser.parse(null, dataTypeStr);
|
|
} catch (Exception e) {
|
|
Msg.debug(this, "Function signature parser failed: " + e.getMessage());
|
|
}
|
|
}
|
|
|
|
if (dataType == null) {
|
|
throw new Exception("Could not find or parse data type: " + dataTypeStr);
|
|
}
|
|
|
|
// Apply the data type
|
|
try {
|
|
// Clear existing data
|
|
int length = data.getLength();
|
|
listing.clearCodeUnits(addr, addr.add(length - 1), false);
|
|
|
|
// Create new data with the type
|
|
Data newData = listing.createData(addr, dataType);
|
|
if (newData == null) {
|
|
throw new Exception("Failed to create data with type " + dataTypeStr);
|
|
}
|
|
|
|
resultMap.put("dataType", dataTypeStr);
|
|
|
|
// Update our reference to the data
|
|
data = newData;
|
|
} catch (Exception e) {
|
|
throw new Exception("Error applying data type: " + e.getMessage(), e);
|
|
}
|
|
}
|
|
|
|
// Handle rename if requested
|
|
if (newName != null && !newName.isEmpty()) {
|
|
SymbolTable symTable = program.getSymbolTable();
|
|
Symbol currentSymbol = symTable.getPrimarySymbol(addr);
|
|
|
|
if (currentSymbol != null) {
|
|
currentSymbol.setName(newName, SourceType.USER_DEFINED);
|
|
} else {
|
|
// Create a new label if no primary symbol exists
|
|
symTable.createLabel(addr, newName, SourceType.USER_DEFINED);
|
|
}
|
|
|
|
resultMap.put("name", newName);
|
|
} else if (currentName != null) {
|
|
// If we didn't rename but need to preserve name after type change
|
|
SymbolTable symTable = program.getSymbolTable();
|
|
Symbol currentSymbol = symTable.getPrimarySymbol(addr);
|
|
|
|
if (currentSymbol == null) {
|
|
symTable.createLabel(addr, currentName, SourceType.USER_DEFINED);
|
|
}
|
|
|
|
resultMap.put("name", currentName);
|
|
}
|
|
|
|
return null;
|
|
});
|
|
|
|
// Add a meaningful message
|
|
String message;
|
|
if (newName != null && !newName.isEmpty() && dataTypeStr != null && !dataTypeStr.isEmpty()) {
|
|
message = "Data renamed and type changed successfully";
|
|
} else if (newName != null && !newName.isEmpty()) {
|
|
message = "Data renamed successfully";
|
|
} else {
|
|
message = "Data type changed successfully";
|
|
}
|
|
resultMap.put("message", message);
|
|
|
|
// Build HATEOAS response
|
|
eu.starsong.ghidra.api.ResponseBuilder builder = new eu.starsong.ghidra.api.ResponseBuilder(exchange, port)
|
|
.success(true)
|
|
.result(resultMap);
|
|
|
|
// Add relevant links
|
|
builder.addLink("self", "/data/" + addressStr);
|
|
builder.addLink("data", "/data");
|
|
builder.addLink("program", "/program");
|
|
|
|
sendJsonResponse(exchange, builder.build(), 200);
|
|
} catch (TransactionException e) {
|
|
Msg.error(this, "Transaction failed: Update Data", e);
|
|
sendErrorResponse(exchange, 500, "Failed to update data: " + e.getMessage(), "TRANSACTION_ERROR");
|
|
} catch (Exception e) {
|
|
Msg.error(this, "Error during data update operation", e);
|
|
sendErrorResponse(exchange, 400, "Error updating data: " + e.getMessage(), "INVALID_PARAMETER");
|
|
}
|
|
} catch (IOException e) {
|
|
Msg.error(this, "Error parsing POST params for data update", e);
|
|
sendErrorResponse(exchange, 400, "Invalid request body: " + e.getMessage(), "INVALID_REQUEST");
|
|
} catch (Exception e) {
|
|
Msg.error(this, "Unexpected error updating data", e);
|
|
sendErrorResponse(exchange, 500, "Error updating data: " + e.getMessage(), "INTERNAL_ERROR");
|
|
}
|
|
}
|
|
|
|
// parseIntOrDefault is inherited from AbstractEndpoint
|
|
|
|
/**
|
|
* Handle a data creation request
|
|
*/
|
|
public void handleCreateData(HttpExchange exchange, Map<String, String> params) throws IOException {
|
|
try {
|
|
// Debug - log all parameters
|
|
StringBuilder debugInfo = new StringBuilder("DEBUG handleCreateData - Received parameters: ");
|
|
for (Map.Entry<String, String> entry : params.entrySet()) {
|
|
debugInfo.append(entry.getKey()).append("=").append(entry.getValue()).append(", ");
|
|
}
|
|
Msg.info(this, debugInfo.toString());
|
|
|
|
final String addressStr = params.get("address");
|
|
final String dataTypeStr = params.get("type");
|
|
final String sizeStr = params.get("size");
|
|
final String nameStr = params.get("newName"); // Optional name for the new data
|
|
|
|
// Validate required parameters
|
|
if (addressStr == null || addressStr.isEmpty()) {
|
|
sendErrorResponse(exchange, 400, "Missing required parameter: address", "MISSING_PARAMETERS");
|
|
return;
|
|
}
|
|
|
|
if (dataTypeStr == null || dataTypeStr.isEmpty()) {
|
|
sendErrorResponse(exchange, 400, "Missing required parameter: type", "MISSING_PARAMETERS");
|
|
return;
|
|
}
|
|
|
|
Program program = getCurrentProgram();
|
|
if (program == null) {
|
|
sendErrorResponse(exchange, 400, "No program loaded", "NO_PROGRAM_LOADED");
|
|
return;
|
|
}
|
|
|
|
// Parse size if provided
|
|
Integer size = null;
|
|
if (sizeStr != null && !sizeStr.isEmpty()) {
|
|
try {
|
|
size = Integer.parseInt(sizeStr);
|
|
} catch (NumberFormatException e) {
|
|
sendErrorResponse(exchange, 400, "Invalid size parameter: must be an integer", "INVALID_PARAMETER");
|
|
return;
|
|
}
|
|
}
|
|
|
|
try {
|
|
// Create a result map for the response
|
|
Map<String, Object> resultMap = new HashMap<>();
|
|
resultMap.put("address", addressStr);
|
|
resultMap.put("dataType", dataTypeStr);
|
|
if (size != null) {
|
|
resultMap.put("size", size);
|
|
}
|
|
|
|
final Integer finalSize = size; // Make a final copy for the lambda
|
|
|
|
TransactionHelper.executeInTransaction(program, "Create Data", () -> {
|
|
// Get the address
|
|
Address addr = program.getAddressFactory().getAddress(addressStr);
|
|
Listing listing = program.getListing();
|
|
|
|
// Verify no data is already defined at this address
|
|
Data existingData = listing.getDefinedDataAt(addr);
|
|
if (existingData != null) {
|
|
throw new Exception("Data already exists at address: " + addressStr);
|
|
}
|
|
|
|
// Find the requested data type
|
|
ghidra.program.model.data.DataType dataType = null;
|
|
|
|
// Map common shorthand data types to their Ghidra equivalents
|
|
String mappedType = dataTypeStr;
|
|
switch(dataTypeStr.toLowerCase()) {
|
|
case "byte":
|
|
mappedType = "byte";
|
|
break;
|
|
case "char":
|
|
mappedType = "char";
|
|
break;
|
|
case "word":
|
|
mappedType = "word";
|
|
break;
|
|
case "dword":
|
|
mappedType = "dword";
|
|
break;
|
|
case "qword":
|
|
mappedType = "qword";
|
|
break;
|
|
case "string":
|
|
// For string, we'll use StringDataType directly
|
|
dataType = new ghidra.program.model.data.StringDataType();
|
|
break;
|
|
case "float":
|
|
mappedType = "float";
|
|
break;
|
|
case "double":
|
|
mappedType = "double";
|
|
break;
|
|
case "int":
|
|
mappedType = "int";
|
|
break;
|
|
case "long":
|
|
mappedType = "long";
|
|
break;
|
|
case "pointer":
|
|
mappedType = "pointer";
|
|
break;
|
|
default:
|
|
// Keep the original type string
|
|
break;
|
|
}
|
|
|
|
// Continue with data type lookup if not directly mapped
|
|
if (dataType == null) {
|
|
// First try built-in types with path
|
|
dataType = program.getDataTypeManager().getDataType("/" + mappedType);
|
|
|
|
// If not found, try to find it without path
|
|
if (dataType == null) {
|
|
dataType = program.getDataTypeManager().findDataType("/" + mappedType);
|
|
}
|
|
|
|
// Try data type manager from program
|
|
if (dataType == null) {
|
|
try {
|
|
dataType = program.getDataTypeManager().findDataType("/" + mappedType);
|
|
} catch (Exception e) {
|
|
Msg.debug(this, "Error getting built-in type: " + e.getMessage());
|
|
}
|
|
}
|
|
|
|
// If still null, try parsing it
|
|
if (dataType == null) {
|
|
try {
|
|
ghidra.app.util.parser.FunctionSignatureParser parser =
|
|
new ghidra.app.util.parser.FunctionSignatureParser(program.getDataTypeManager(), null);
|
|
dataType = parser.parse(null, mappedType);
|
|
} catch (Exception e) {
|
|
Msg.debug(this, "Function signature parser failed: " + e.getMessage());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Try some specific data type classes if still not found
|
|
if (dataType == null) {
|
|
switch(dataTypeStr.toLowerCase()) {
|
|
case "byte":
|
|
dataType = new ghidra.program.model.data.ByteDataType();
|
|
break;
|
|
case "char":
|
|
dataType = new ghidra.program.model.data.CharDataType();
|
|
break;
|
|
case "word":
|
|
dataType = new ghidra.program.model.data.WordDataType();
|
|
break;
|
|
case "dword":
|
|
dataType = new ghidra.program.model.data.DWordDataType();
|
|
break;
|
|
case "qword":
|
|
dataType = new ghidra.program.model.data.QWordDataType();
|
|
break;
|
|
case "float":
|
|
dataType = new ghidra.program.model.data.FloatDataType();
|
|
break;
|
|
case "double":
|
|
dataType = new ghidra.program.model.data.DoubleDataType();
|
|
break;
|
|
case "int":
|
|
dataType = new ghidra.program.model.data.IntegerDataType();
|
|
break;
|
|
case "uint32_t":
|
|
dataType = new ghidra.program.model.data.UnsignedIntegerDataType();
|
|
break;
|
|
case "long":
|
|
dataType = new ghidra.program.model.data.LongDataType();
|
|
break;
|
|
case "pointer":
|
|
dataType = new ghidra.program.model.data.PointerDataType();
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (dataType == null) {
|
|
throw new Exception("Could not find or parse data type: " + dataTypeStr);
|
|
}
|
|
|
|
Msg.info(this, "Successfully mapped data type '" + dataTypeStr + "' to Ghidra type: " + dataType.getName());
|
|
|
|
// Create the data at the specified address
|
|
Data newData;
|
|
|
|
// Make final copy of dataType for use in lambda
|
|
final ghidra.program.model.data.DataType finalDataType = dataType;
|
|
|
|
// Check if there's already existing code or data at this address
|
|
int dataSize = finalSize != null ? finalSize : finalDataType.getLength();
|
|
if (dataSize <= 0) dataSize = 4; // Default size if unknown
|
|
|
|
// Check if there's data/code at the target address range
|
|
boolean hasConflict = false;
|
|
String conflictType = "";
|
|
|
|
try {
|
|
// Check for existing code units
|
|
if (listing.getInstructionAt(addr) != null) {
|
|
hasConflict = true;
|
|
conflictType = "instruction";
|
|
} else if (listing.getDefinedDataAt(addr) != null) {
|
|
hasConflict = true;
|
|
conflictType = "data";
|
|
} else {
|
|
// Check if any address in range has a code unit
|
|
for (int i = 1; i < dataSize; i++) {
|
|
Address checkAddr = addr.add(i);
|
|
if (listing.getInstructionAt(checkAddr) != null) {
|
|
hasConflict = true;
|
|
conflictType = "instruction";
|
|
break;
|
|
} else if (listing.getDefinedDataAt(checkAddr) != null) {
|
|
hasConflict = true;
|
|
conflictType = "data";
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (hasConflict) {
|
|
throw new Exception("Conflicting " + conflictType + " exists at address range " + addr +
|
|
" to " + addr.add(dataSize - 1) + ". Use update_data or delete_data first.");
|
|
}
|
|
|
|
// Also check if the address is valid (in a memory block)
|
|
if (!program.getMemory().contains(addr)) {
|
|
throw new Exception("Address " + addressStr + " is not in any memory block. Valid addresses must be within defined memory blocks.");
|
|
}
|
|
} catch (Exception e) {
|
|
if (hasConflict) {
|
|
throw e; // Rethrow the conflict error
|
|
}
|
|
// Other errors we can try to continue
|
|
Msg.warn(this, "Error checking for existing code units: " + e.getMessage());
|
|
}
|
|
|
|
// Now create the data
|
|
if (finalSize != null) {
|
|
// For variable length types like strings, need to clear space first
|
|
if (finalDataType.getLength() <= 0 || finalDataType.getLength() != finalSize) {
|
|
Msg.info(this, "Creating variable-length data with size: " + finalSize);
|
|
|
|
// For arrays and strings, may need to create custom type
|
|
if (finalDataType.getName().toLowerCase().contains("string") ||
|
|
dataTypeStr.toLowerCase().contains("string")) {
|
|
// Create a string data type with specified length
|
|
try {
|
|
ghidra.program.model.data.StringDataType stringType = new ghidra.program.model.data.StringDataType();
|
|
newData = listing.createData(addr, stringType, finalSize);
|
|
} catch (Exception e) {
|
|
Msg.warn(this, "Couldn't create string data: " + e.getMessage());
|
|
// Fallback to byte array
|
|
newData = listing.createData(addr, new ghidra.program.model.data.ByteDataType(), finalSize);
|
|
}
|
|
} else {
|
|
// For other variable length types, create clear space and then create
|
|
newData = listing.createData(addr, finalDataType);
|
|
}
|
|
} else {
|
|
// For fixed size datatypes
|
|
newData = listing.createData(addr, finalDataType);
|
|
}
|
|
} else {
|
|
// Normal data creation without size
|
|
newData = listing.createData(addr, finalDataType);
|
|
}
|
|
|
|
if (newData == null) {
|
|
throw new Exception("Failed to create data of type " + dataTypeStr + " at " + addressStr);
|
|
}
|
|
|
|
// Set name if provided
|
|
if (nameStr != null && !nameStr.isEmpty()) {
|
|
SymbolTable symTable = program.getSymbolTable();
|
|
symTable.createLabel(addr, nameStr, SourceType.USER_DEFINED);
|
|
resultMap.put("name", nameStr);
|
|
}
|
|
|
|
// Add information about the created data to the result
|
|
resultMap.put("length", newData.getLength());
|
|
resultMap.put("value", newData.getDefaultValueRepresentation());
|
|
|
|
return null;
|
|
});
|
|
|
|
resultMap.put("message", "Data created successfully");
|
|
|
|
// Build HATEOAS response
|
|
eu.starsong.ghidra.api.ResponseBuilder builder = new eu.starsong.ghidra.api.ResponseBuilder(exchange, port)
|
|
.success(true)
|
|
.result(resultMap);
|
|
|
|
// Add relevant links
|
|
builder.addLink("self", "/data/" + addressStr);
|
|
builder.addLink("data", "/data");
|
|
builder.addLink("program", "/program");
|
|
|
|
sendJsonResponse(exchange, builder.build(), 200);
|
|
} catch (TransactionException e) {
|
|
Msg.error(this, "Transaction failed: Create Data", e);
|
|
sendErrorResponse(exchange, 500, "Failed to create data: " + e.getMessage(), "TRANSACTION_ERROR");
|
|
} catch (Exception e) {
|
|
Msg.error(this, "Error during data creation", e);
|
|
sendErrorResponse(exchange, 400, "Error creating data: " + e.getMessage(), "INVALID_PARAMETER");
|
|
}
|
|
} catch (Exception e) {
|
|
Msg.error(this, "Unexpected error creating data", e);
|
|
sendErrorResponse(exchange, 500, "Error creating data: " + e.getMessage(), "INTERNAL_ERROR");
|
|
}
|
|
}
|
|
|
|
public void handleSetDataType(HttpExchange exchange) throws IOException {
|
|
try {
|
|
if ("PATCH".equals(exchange.getRequestMethod()) || "POST".equals(exchange.getRequestMethod())) {
|
|
Map<String, String> params = parseJsonPostParams(exchange);
|
|
|
|
// Debug - log all parameters received by this method
|
|
StringBuilder debugInfo = new StringBuilder("DEBUG handleSetDataType - Received parameters: ");
|
|
for (Map.Entry<String, String> entry : params.entrySet()) {
|
|
debugInfo.append(entry.getKey()).append("=").append(entry.getValue()).append(", ");
|
|
}
|
|
Msg.info(this, debugInfo.toString());
|
|
|
|
final String addressStr = params.get("address");
|
|
final String dataTypeStr = params.get("type");
|
|
|
|
if (addressStr == null || addressStr.isEmpty() || dataTypeStr == null || dataTypeStr.isEmpty()) {
|
|
sendErrorResponse(exchange, 400,
|
|
"Missing required parameters: address and type must be provided",
|
|
"MISSING_PARAMETERS");
|
|
return;
|
|
}
|
|
|
|
Program program = getCurrentProgram();
|
|
if (program == null) {
|
|
sendErrorResponse(exchange, 400, "No program loaded", "NO_PROGRAM_LOADED");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
Map<String, Object> result = new HashMap<>();
|
|
result.put("address", addressStr);
|
|
result.put("type", dataTypeStr);
|
|
|
|
TransactionHelper.executeInTransaction(program, "Set Data Type", () -> {
|
|
// Get the data at the address
|
|
Address addr = program.getAddressFactory().getAddress(addressStr);
|
|
Listing listing = program.getListing();
|
|
Data existingData = listing.getDefinedDataAt(addr);
|
|
|
|
if (existingData == null) {
|
|
throw new Exception("No defined data found at address: " + addressStr);
|
|
}
|
|
|
|
// Try to find the data type in the data type manager
|
|
ghidra.program.model.data.DataType dataType = null;
|
|
|
|
// First try built-in types with path
|
|
dataType = program.getDataTypeManager().getDataType("/" + dataTypeStr);
|
|
|
|
// Try built-in types without path
|
|
if (dataType == null) {
|
|
dataType = program.getDataTypeManager().findDataType("/" + dataTypeStr);
|
|
}
|
|
|
|
// If still not found, try to parse it as a C-style declaration
|
|
if (dataType == null) {
|
|
try {
|
|
ghidra.app.util.parser.FunctionSignatureParser parser =
|
|
new ghidra.app.util.parser.FunctionSignatureParser(program.getDataTypeManager(), null);
|
|
dataType = parser.parse(null, dataTypeStr);
|
|
} catch (Exception e) {
|
|
Msg.debug(this, "Function signature parser failed: " + e.getMessage());
|
|
}
|
|
}
|
|
|
|
// Try C parser as a last resort
|
|
if (dataType == null) {
|
|
try {
|
|
// Use the DataTypeParser to create the type
|
|
ghidra.app.util.parser.FunctionSignatureParser parser =
|
|
new ghidra.app.util.parser.FunctionSignatureParser(program.getDataTypeManager(), null);
|
|
dataType = parser.parse(null, dataTypeStr);
|
|
} catch (Exception e) {
|
|
Msg.error(this, "Error parsing data type: " + dataTypeStr, e);
|
|
}
|
|
}
|
|
|
|
if (dataType == null) {
|
|
throw new Exception("Could not find or parse data type: " + dataTypeStr);
|
|
}
|
|
|
|
// Apply the data type
|
|
try {
|
|
Data newDataItem = listing.createData(addr, dataType);
|
|
if (newDataItem == null) {
|
|
// Try clearing existing data first and then creating it
|
|
listing.clearCodeUnits(addr, addr.add(existingData.getLength() - 1), false);
|
|
newDataItem = listing.createData(addr, dataType);
|
|
|
|
if (newDataItem == null) {
|
|
throw new Exception("Failed to apply data type " + dataTypeStr + " at " + addressStr);
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
throw new Exception("Failed to apply data type " + dataTypeStr + " at " + addressStr, e);
|
|
}
|
|
|
|
// Re-get the data to return its current info
|
|
Data newData = listing.getDefinedDataAt(addr);
|
|
if (newData != null) {
|
|
result.put("currentDataType", newData.getDataType().getName());
|
|
result.put("length", newData.getLength());
|
|
result.put("value", newData.getDefaultValueRepresentation());
|
|
}
|
|
|
|
return null;
|
|
});
|
|
|
|
// Build HATEOAS response
|
|
eu.starsong.ghidra.api.ResponseBuilder builder = new eu.starsong.ghidra.api.ResponseBuilder(exchange, port)
|
|
.success(true)
|
|
.result(result);
|
|
|
|
// Add relevant links
|
|
builder.addLink("self", "/data/" + addressStr);
|
|
builder.addLink("data", "/data");
|
|
builder.addLink("program", "/program");
|
|
|
|
sendJsonResponse(exchange, builder.build(), 200);
|
|
} catch (TransactionException e) {
|
|
Msg.error(this, "Transaction failed: Set Data Type", e);
|
|
sendErrorResponse(exchange, 500, "Failed to set data type: " + e.getMessage(), "TRANSACTION_ERROR");
|
|
} catch (Exception e) {
|
|
Msg.error(this, "Error during set data type operation", e);
|
|
sendErrorResponse(exchange, 400, "Error setting data type: " + e.getMessage(), "INVALID_PARAMETER");
|
|
}
|
|
} else {
|
|
sendErrorResponse(exchange, 405, "Method Not Allowed", "METHOD_NOT_ALLOWED");
|
|
}
|
|
} catch (IOException e) {
|
|
Msg.error(this, "Error parsing request parameters for data type update", e);
|
|
sendErrorResponse(exchange, 400, "Invalid request body: " + e.getMessage(), "INVALID_REQUEST");
|
|
} catch (Exception e) {
|
|
Msg.error(this, "Unexpected error setting data type", e);
|
|
sendErrorResponse(exchange, 500, "Error setting data type: " + e.getMessage(), "INTERNAL_ERROR");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle a delete data request
|
|
*/
|
|
public void handleDeleteData(HttpExchange exchange, Map<String, String> params) throws IOException {
|
|
try {
|
|
// Debug - log all parameters
|
|
StringBuilder debugInfo = new StringBuilder("DEBUG handleDeleteData - Received parameters: ");
|
|
for (Map.Entry<String, String> entry : params.entrySet()) {
|
|
debugInfo.append(entry.getKey()).append("=").append(entry.getValue()).append(", ");
|
|
}
|
|
Msg.info(this, debugInfo.toString());
|
|
|
|
final String addressStr = params.get("address");
|
|
|
|
// Validate required parameters
|
|
if (addressStr == null || addressStr.isEmpty()) {
|
|
sendErrorResponse(exchange, 400, "Missing required parameter: address", "MISSING_PARAMETERS");
|
|
return;
|
|
}
|
|
|
|
Program program = getCurrentProgram();
|
|
if (program == null) {
|
|
sendErrorResponse(exchange, 400, "No program loaded", "NO_PROGRAM_LOADED");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Create a result map for the response
|
|
Map<String, Object> resultMap = new HashMap<>();
|
|
resultMap.put("address", addressStr);
|
|
|
|
TransactionHelper.executeInTransaction(program, "Delete Data", () -> {
|
|
// Get the address
|
|
Address addr = program.getAddressFactory().getAddress(addressStr);
|
|
Listing listing = program.getListing();
|
|
|
|
// Check if there's data at the address
|
|
Data existingData = listing.getDefinedDataAt(addr);
|
|
if (existingData == null) {
|
|
// Check if there's an instruction
|
|
if (listing.getInstructionAt(addr) != null) {
|
|
// Clear the instruction
|
|
listing.clearCodeUnits(addr, addr, true);
|
|
resultMap.put("cleared", "instruction");
|
|
} else {
|
|
// No data or instruction, but still treat as success
|
|
resultMap.put("message", "No data or instruction exists at address: " + addressStr);
|
|
resultMap.put("cleared", "none");
|
|
}
|
|
} else {
|
|
// Remember what we're deleting
|
|
resultMap.put("original_type", existingData.getDataType().getName());
|
|
resultMap.put("length", existingData.getLength());
|
|
|
|
// Get the name if any
|
|
Symbol symbol = program.getSymbolTable().getPrimarySymbol(addr);
|
|
if (symbol != null) {
|
|
resultMap.put("original_name", symbol.getName());
|
|
}
|
|
|
|
// Clear the data
|
|
listing.clearCodeUnits(addr, addr.add(existingData.getLength() - 1), true);
|
|
resultMap.put("cleared", "data");
|
|
}
|
|
|
|
return null;
|
|
});
|
|
|
|
resultMap.put("message", "Data deleted successfully");
|
|
|
|
// Build HATEOAS response
|
|
eu.starsong.ghidra.api.ResponseBuilder builder = new eu.starsong.ghidra.api.ResponseBuilder(exchange, port)
|
|
.success(true)
|
|
.result(resultMap);
|
|
|
|
// Add relevant links
|
|
builder.addLink("data", "/data");
|
|
builder.addLink("program", "/program");
|
|
|
|
sendJsonResponse(exchange, builder.build(), 200);
|
|
} catch (TransactionException e) {
|
|
Msg.error(this, "Transaction failed: Delete Data", e);
|
|
sendErrorResponse(exchange, 500, "Failed to delete data: " + e.getMessage(), "TRANSACTION_ERROR");
|
|
} catch (Exception e) {
|
|
Msg.error(this, "Error during data deletion", e);
|
|
sendErrorResponse(exchange, 400, "Error deleting data: " + e.getMessage(), "INVALID_PARAMETER");
|
|
}
|
|
} catch (Exception e) {
|
|
Msg.error(this, "Unexpected error deleting data", e);
|
|
sendErrorResponse(exchange, 500, "Error deleting data: " + e.getMessage(), "INTERNAL_ERROR");
|
|
}
|
|
}
|
|
|
|
// Note: The handleUpdateData method is already defined earlier in this file at line 477
|
|
|
|
/**
|
|
* Handle request to list strings in the binary
|
|
* @param exchange The HTTP exchange
|
|
* @throws IOException If an I/O error occurs
|
|
*/
|
|
public void handleListStrings(HttpExchange exchange) throws IOException {
|
|
try {
|
|
Map<String, String> qparams = parseQueryParams(exchange);
|
|
int offset = parseIntOrDefault(qparams.get("offset"), 0);
|
|
int limit = parseIntOrDefault(qparams.get("limit"), 2000);
|
|
String filter = qparams.get("filter");
|
|
|
|
Program program = getCurrentProgram();
|
|
if (program == null) {
|
|
sendErrorResponse(exchange, 400, "No program loaded", "NO_PROGRAM_LOADED");
|
|
return;
|
|
}
|
|
|
|
List<Map<String, Object>> strings = new ArrayList<>();
|
|
|
|
for (MemoryBlock block : program.getMemory().getBlocks()) {
|
|
if (!block.isInitialized()) continue;
|
|
|
|
DataIterator it = program.getListing().getDefinedData(block.getStart(), true);
|
|
while (it.hasNext()) {
|
|
Data data = it.next();
|
|
if (!block.contains(data.getAddress())) continue;
|
|
|
|
// Check if the data type is a string type
|
|
String dataTypeName = data.getDataType().getName().toLowerCase();
|
|
boolean isString = dataTypeName.contains("string") ||
|
|
dataTypeName.contains("unicode") ||
|
|
(dataTypeName.contains("char") && data.getLength() > 1); // Array of chars
|
|
|
|
if (isString) {
|
|
// Get the string value
|
|
String value = data.getDefaultValueRepresentation();
|
|
if (value == null) value = "";
|
|
|
|
// Skip if it doesn't match the filter
|
|
if (filter != null && !filter.isEmpty() && !value.toLowerCase().contains(filter.toLowerCase())) {
|
|
continue;
|
|
}
|
|
|
|
Map<String, Object> stringInfo = new HashMap<>();
|
|
stringInfo.put("address", data.getAddress().toString());
|
|
stringInfo.put("value", value);
|
|
stringInfo.put("length", data.getLength());
|
|
stringInfo.put("type", data.getDataType().getName());
|
|
|
|
// If the data has a label/name, include it
|
|
String name = null;
|
|
Symbol symbol = program.getSymbolTable().getPrimarySymbol(data.getAddress());
|
|
if (symbol != null) {
|
|
name = symbol.getName();
|
|
}
|
|
stringInfo.put("name", name != null ? name : "");
|
|
|
|
// Add HATEOAS links
|
|
Map<String, Object> links = new HashMap<>();
|
|
Map<String, String> selfLink = new HashMap<>();
|
|
selfLink.put("href", "/data/" + data.getAddress().toString());
|
|
links.put("self", selfLink);
|
|
|
|
Map<String, String> memoryLink = new HashMap<>();
|
|
memoryLink.put("href", "/memory?address=" + data.getAddress().toString());
|
|
links.put("memory", memoryLink);
|
|
|
|
stringInfo.put("_links", links);
|
|
|
|
strings.add(stringInfo);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Build response with HATEOAS links
|
|
eu.starsong.ghidra.api.ResponseBuilder builder = new eu.starsong.ghidra.api.ResponseBuilder(exchange, port)
|
|
.success(true);
|
|
|
|
// Apply pagination and get paginated items
|
|
List<Map<String, Object>> paginated = applyPagination(strings, offset, limit, builder, "/strings");
|
|
|
|
// Set the paginated result
|
|
builder.result(paginated);
|
|
|
|
// Add program link
|
|
builder.addLink("program", "/program");
|
|
builder.addLink("data", "/data");
|
|
|
|
sendJsonResponse(exchange, builder.build(), 200);
|
|
} catch (Exception e) {
|
|
Msg.error(this, "Error listing strings", e);
|
|
sendErrorResponse(exchange, 500, "Error listing strings: " + e.getMessage(), "INTERNAL_ERROR");
|
|
}
|
|
}
|
|
}
|