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 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 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 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 params = parseJsonPostParams(exchange); // Debug - log the params StringBuilder debugInfo = new StringBuilder("DEBUG - Received parameters: "); for (Map.Entry 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 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> 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 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 links = new HashMap<>(); Map 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> 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 params) throws IOException { try { // Debug - log the params again StringBuilder debugInfo = new StringBuilder("DEBUG handleRenameData - Received parameters: "); for (Map.Entry 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 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 params) throws IOException { try { // Debug - log all parameters received by this method StringBuilder debugInfo = new StringBuilder("DEBUG handleTypeChangeData - Received parameters: "); for (Map.Entry 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 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 params) throws IOException { try { // Debug - log all parameters received by this method StringBuilder debugInfo = new StringBuilder("DEBUG handleUpdateData - Received parameters: "); for (Map.Entry 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 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 params) throws IOException { try { // Debug - log all parameters StringBuilder debugInfo = new StringBuilder("DEBUG handleCreateData - Received parameters: "); for (Map.Entry 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 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 params = parseJsonPostParams(exchange); // Debug - log all parameters received by this method StringBuilder debugInfo = new StringBuilder("DEBUG handleSetDataType - Received parameters: "); for (Map.Entry 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 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 params) throws IOException { try { // Debug - log all parameters StringBuilder debugInfo = new StringBuilder("DEBUG handleDeleteData - Received parameters: "); for (Map.Entry 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 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 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> 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 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 links = new HashMap<>(); Map selfLink = new HashMap<>(); selfLink.put("href", "/data/" + data.getAddress().toString()); links.put("self", selfLink); Map 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> 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"); } } }