GP-3034 GZF/GDT Import/Export improvements

This commit is contained in:
ghidra1 2023-02-06 09:48:54 -05:00
parent b1cf7d1b61
commit af989e0ff6
18 changed files with 588 additions and 239 deletions

View File

@ -36,7 +36,9 @@ import docking.widgets.label.GLabel;
import ghidra.app.plugin.core.help.AboutDomainObjectUtils;
import ghidra.app.util.*;
import ghidra.app.util.exporter.Exporter;
import ghidra.app.util.exporter.GzfExporter;
import ghidra.app.util.importer.MessageLog;
import ghidra.framework.main.FrontEndService;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainObject;
import ghidra.framework.plugintool.PluginTool;
@ -62,17 +64,19 @@ import ghidra.util.task.*;
public class ExporterDialog extends DialogComponentProvider implements AddressFactoryService {
private static final String XML_WARNING =
" Warning: XML is lossy and intended only for transfering data to external tools. GZF is the recommended format for saving and sharing program data.";
" Warning: XML is lossy and intended only for transfering data to external tools. " +
"GZF is the recommended format for saving and sharing program data.";
private static String lastUsedExporterName = "Ghidra Zip File"; // default to GZF first time
private static String lastUsedExporterName = GzfExporter.NAME; // default to GZF first time
private JButton optionsButton;
private ProgramSelection currentSelection;
private JCheckBox selectionCheckBox;
private JCheckBox selectionCheckBox; // null for FrontEnd Tool use
private JTextField filePathTextField;
private JButton fileChooserButton;
private GhidraComboBox<Exporter> comboBox;
private final DomainFile domainFile;
private boolean domainObjectWasSupplied;
private DomainObject domainObject;
private List<Option> options;
private PluginTool tool;
@ -94,9 +98,9 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
* selected region.
*
* @param tool the tool that launched this dialog.
* @param domainFile the program file to export.
* @param domainFile the program file to export. (may be proxy)
* @param domainObject the program to export if already open, otherwise null.
* @param selection the current program selection.
* @param selection the current program selection (ignored for FrontEnd Tool).
*/
public ExporterDialog(PluginTool tool, DomainFile domainFile, DomainObject domainObject,
ProgramSelection selection) {
@ -106,8 +110,12 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
this.domainObject = domainObject;
this.currentSelection = selection;
if (domainObject != null) {
domainObjectWasSupplied = true;
domainObject.addConsumer(this);
}
else {
domainObject = getDomainObjectIfNeeded(TaskMonitor.DUMMY);
}
addWorkPanel(buildWorkPanel());
addOKButton();
@ -131,6 +139,10 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
}
}
private boolean isFrontEndPlugin() {
return tool.getService(FrontEndService.class) != null;
}
private JComponent buildWorkPanel() {
JPanel panel = new JPanel(new VerticalLayout(5));
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
@ -168,7 +180,8 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
return "Unexpected exception validating options: " + e.getMessage();
}
};
OptionsDialog optionsDialog = new OptionsDialog(options, validator, this);
AddressFactoryService svc = (domainObject instanceof Program) ? null : this;
OptionsDialog optionsDialog = new OptionsDialog(options, validator, svc);
optionsDialog
.setHelpLocation(new HelpLocation("ExporterPlugin", getAnchorForSelectedFormat()));
tool.showDialog(optionsDialog);
@ -197,17 +210,15 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
private Component buildSelectionCheckboxPanel() {
JPanel panel = new JPanel(new PairLayout(5, 5));
selectionOnlyLabel = new GLabel("Selection Only:");
panel.add(selectionOnlyLabel);
panel.add(buildSelectionCheckbox());
if (!isFrontEndPlugin()) {
selectionCheckBox = new GCheckBox("");
updateSelectionCheckbox();
panel.add(selectionOnlyLabel);
panel.add(selectionCheckBox);
}
return panel;
}
private Component buildSelectionCheckbox() {
selectionCheckBox = new GCheckBox("");
updateSelectionCheckbox();
return selectionCheckBox;
}
private Component buildFilePanel() {
filePathTextField = new JTextField();
filePathTextField.setName("OUTPUT_FILE_TEXTFIELD");
@ -284,7 +295,7 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
private Component buildFormatChooser() {
List<Exporter> exporters = getApplicableExporters();
List<Exporter> exporters = getApplicableExporters(false);
comboBox = new GhidraComboBox<>(exporters);
Exporter defaultExporter = getDefaultExporter(exporters);
@ -295,17 +306,30 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
return comboBox;
}
private List<Exporter> getApplicableExporters() {
/**
* This list generation will be based upon the open domainObject if available, otherwise
* the domainFile's content class will be used.
* @return list of exporters able to handle content
*/
private List<Exporter> getApplicableExporters(boolean preliminaryCheck) {
List<Exporter> list = new ArrayList<>(ClassSearcher.getInstances(Exporter.class));
Class<?> domainObjectClass = domainFile.getDomainObjectClass();
DomainObject domainObj = getDomainObject(TaskMonitor.DUMMY);
if (DomainObject.class.isAssignableFrom(domainObjectClass)) {
list.removeIf(exporter -> !exporter.canExportDomainObject(domainObj));
Collections.sort(list, (o1, o2) -> o1.toString().compareTo(o2.toString()));
}
list.removeIf(exporter -> !canExport(exporter, preliminaryCheck));
Collections.sort(list, (o1, o2) -> o1.toString().compareTo(o2.toString()));
return list;
}
private boolean canExport(Exporter exporter, boolean preliminaryCheck) {
if (exporter.canExportDomainFile(domainFile)) {
return true;
}
if (domainObject == null) {
return preliminaryCheck
? exporter.canExportDomainObject(domainFile.getDomainObjectClass())
: false;
}
return exporter.canExportDomainObject(domainObject);
}
private Exporter getDefaultExporter(List<Exporter> list) {
// first try the last one used
@ -321,17 +345,19 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
private void selectedFormatChanged() {
Exporter selectedExporter = getSelectedExporter();
if (selectedExporter != null) {
options = selectedExporter.getOptions(() -> getDomainObject(TaskMonitor.DUMMY));
options = selectedExporter.getOptions(() -> domainObject);
}
validate();
updateSelectionCheckbox();
}
private void updateSelectionCheckbox() {
boolean shouldEnableCheckbox = shouldEnableCheckbox();
selectionCheckBox.setSelected(shouldEnableCheckbox);
selectionCheckBox.setEnabled(shouldEnableCheckbox);
selectionOnlyLabel.setEnabled(shouldEnableCheckbox);
if (selectionCheckBox != null) {
boolean shouldEnableCheckbox = shouldEnableCheckbox();
selectionCheckBox.setSelected(shouldEnableCheckbox);
selectionCheckBox.setEnabled(shouldEnableCheckbox);
selectionOnlyLabel.setEnabled(shouldEnableCheckbox);
}
}
private boolean shouldEnableCheckbox() {
@ -339,7 +365,7 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
return false;
}
Exporter selectedExporter = getSelectedExporter();
return selectedExporter != null && selectedExporter.supportsPartialExport();
return selectedExporter != null && selectedExporter.supportsAddressRestrictedExport();
}
private void validate() {
@ -410,16 +436,32 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
}
}
private DomainObject getDomainObject(TaskMonitor taskMonitor) {
if (domainObject == null) {
if (SystemUtilities.isEventDispatchThread()) {
TaskLauncher.launchModal("Opening File: " + domainFile.getName(),
monitor -> doOpenFile(monitor));
}
else {
doOpenFile(taskMonitor);
private DomainObject getDomainObjectIfNeeded(TaskMonitor taskMonitor) {
if (domainObject != null) {
return domainObject;
}
// Only open if there is an exporter that can handle content class but can't handle
// direct domain file export. This avoids potential upgrade issues and preserves
// database in its current state for those exporters.
boolean doOpen = false;
for (Exporter exporter : getApplicableExporters(true)) {
if (!exporter.canExportDomainFile(domainFile)) {
doOpen = true;
break;
}
}
if (!doOpen) {
return null;
}
if (SystemUtilities.isEventDispatchThread()) {
TaskLauncher.launchModal("Opening File: " + domainFile.getName(),
monitor -> doOpenFile(monitor));
}
else {
doOpenFile(taskMonitor);
}
return domainObject;
}
@ -436,10 +478,27 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
domainFile.getImmutableDomainObject(this, DomainFile.DEFAULT_VERSION, monitor);
}
}
catch (VersionException | CancelledException | IOException e) {
Msg.showError(this, getComponent(), "Error Opening File",
"Could not open file: " + domainFile.getName() +
"\nThis file may need to be upgraded! Try opening it in a tool first.");
catch (VersionException e) {
String msg = "Could not open file: " + domainFile.getName() +
"\n\nAvailable export options will be limited.";
if (e.isUpgradable()) {
msg +=
"\n\nA data upgrade is required. You may open file" +
"\nin a tool first then Export if a different exporter" +
"\nis required.";
}
else {
msg += "\nFile was created with a newer version of Ghidra";
}
Msg.showError(this, getComponent(), "Error Opening File", msg);
}
catch (IOException e) {
String msg = "Could not open file: " + domainFile.getName() +
"\n\nAvailable export options will be limited.";
Msg.showError(this, getComponent(), "Error Opening File", msg, e);
}
catch (CancelledException e) {
// ignore
}
}
@ -448,9 +507,8 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
*/
@Override
public AddressFactory getAddressFactory() {
DomainObject dobj = getDomainObject(TaskMonitor.DUMMY);
if (dobj instanceof Program) {
return ((Program) domainObject).getAddressFactory();
if (domainObject instanceof Program p) {
return p.getAddressFactory();
}
return null;
}
@ -468,7 +526,6 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
private boolean success;
private boolean showResults;
private Exporter exporter;
private DomainObject exportedDomainObject;
public ExportTask() {
super("Export " + domainFile.getName(), true, true, true, false);
@ -480,10 +537,14 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
exporter = getSelectedExporter();
exporter.setExporterServiceProvider(tool);
exportedDomainObject = getDomainObject(monitor);
if (exportedDomainObject == null) {
boolean exportDomainFile =
!domainObjectWasSupplied && exporter.canExportDomainFile(domainFile);
if (!exportDomainFile && domainFile == null) {
return;
}
// Program selection only relavent if isFrontEndPlugin() is false
ProgramSelection selection = getApplicableProgramSeletion();
File outputFile = getSelectedOutputFile();
@ -497,7 +558,13 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
if (options != null) {
exporter.setOptions(options);
}
success = exporter.export(outputFile, exportedDomainObject, selection, monitor);
if (!domainObjectWasSupplied && exporter.canExportDomainFile(domainFile)) {
success = exporter.export(outputFile, domainFile, monitor);
}
else {
success = exporter.export(outputFile, domainObject, selection, monitor);
}
showResults = true;
}
catch (Exception e) {
@ -509,7 +576,7 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
void showResults() {
if (showResults) {
displaySummaryResults(exporter, exportedDomainObject);
displaySummaryResults(exporter);
}
}
@ -518,47 +585,14 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
}
}
private boolean tryExport(TaskMonitor monitor) {
Exporter exporter = getSelectedExporter();
exporter.setExporterServiceProvider(tool);
DomainObject dobj = getDomainObject(monitor);
if (dobj == null) {
return false;
}
ProgramSelection selection = getApplicableProgramSeletion();
File outputFile = getSelectedOutputFile();
try {
if (outputFile.exists() &&
OptionDialog.showOptionDialog(getComponent(), "Overwrite Existing File?",
"The file " + outputFile + " already exists.\nDo you want to overwrite it?",
"Overwrite", OptionDialog.QUESTION_MESSAGE) != OptionDialog.OPTION_ONE) {
return false;
}
if (options != null) {
exporter.setOptions(options);
}
boolean success = exporter.export(outputFile, dobj, selection, monitor);
displaySummaryResults(exporter, dobj);
return success;
}
catch (Exception e) {
Msg.error(this, "Exception exporting", e);
SystemUtilities.runSwingLater(() -> setStatusText(
"Exception exporting: " + e.getMessage() + ". If null, see log for details."));
}
return false;
}
private ProgramSelection getApplicableProgramSeletion() {
if (selectionCheckBox.isSelected()) {
if (selectionCheckBox != null && selectionCheckBox.isSelected()) {
return currentSelection;
}
return null;
}
private void displaySummaryResults(Exporter exporter, DomainObject obj) {
private void displaySummaryResults(Exporter exporter) {
File outputFile = getSelectedOutputFile();
StringBuffer resultsBuffer = new StringBuffer();
@ -572,15 +606,21 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
HelpLocation helpLocation = new HelpLocation(GenericHelpTopics.ABOUT, "About_Program");
Object tmpConsumer = new Object();
obj.addConsumer(tmpConsumer);
if (domainObject != null) {
domainObject.addConsumer(tmpConsumer);
}
Swing.runLater(() -> {
try {
AboutDomainObjectUtils.displayInformation(tool, obj.getDomainFile(),
obj.getMetadata(), "Export Results Summary", resultsBuffer.toString(),
Map<String, String> metadata =
domainObject != null ? domainObject.getMetadata() : domainFile.getMetadata();
AboutDomainObjectUtils.displayInformation(tool, domainFile,
metadata, "Export Results Summary", resultsBuffer.toString(),
helpLocation);
}
finally {
obj.release(tmpConsumer);
if (domainObject != null) {
domainObject.release(tmpConsumer);
}
}
});
@ -590,6 +630,10 @@ public class ExporterDialog extends DialogComponentProvider implements AddressFa
// Methods for Testing
//==================================================================================================
/**
* Get "Selection Only" checkbox.
* @return checkbox or null if not available (e.g., FrontEnd Tool use)
*/
JCheckBox getSelectionCheckBox() {
return selectionCheckBox;
}

View File

@ -25,8 +25,9 @@ import ghidra.app.context.NavigatableContextAction;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.services.CodeViewerService;
import ghidra.framework.main.ApplicationLevelPlugin;
import ghidra.framework.main.datatable.ProjectDataContext;
import ghidra.framework.main.FrontEndService;
import ghidra.framework.main.datatable.FrontendProjectTreeAction;
import ghidra.framework.main.datatable.ProjectDataContext;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainFolder;
import ghidra.framework.plugintool.*;
@ -46,13 +47,23 @@ import ghidra.util.HelpLocation;
//@formatter:on
public class ExporterPlugin extends Plugin implements ApplicationLevelPlugin {
private FrontEndService frontEndService;
public ExporterPlugin(PluginTool tool) {
super(tool);
frontEndService = tool.getService(FrontEndService.class);
createFrontEndAction();
createToolAction();
}
private void createToolAction() {
if (frontEndService != null) {
return; // do not add File menu Export Program action to front-end
}
DockingAction action = new NavigatableContextAction("Export Program", getName()) {
@Override
@ -83,6 +94,11 @@ public class ExporterPlugin extends Plugin implements ApplicationLevelPlugin {
}
private void createFrontEndAction() {
if (frontEndService == null) {
return; // only add project tree actions to front-end
}
DockingAction action = new FrontendProjectTreeAction("Export", getName()) {
@Override

View File

@ -31,6 +31,7 @@ public interface FileImporterService {
/**
* Imports the given file into the specified Ghidra project folder.
* @param folder the Ghidra project folder to store the imported file.
* If null, the active project's root folder will be assumed.
* @param file the file to import.
*/
public void importFile(DomainFolder folder, File file);
@ -38,6 +39,7 @@ public interface FileImporterService {
/**
* Imports the given files into the specified Ghidra project folder.
* @param folder the Ghidra project folder to store the imported files.
* If null, the active project's root folder will be assumed.
* @param files the files to import.
*/
public void importFiles(DomainFolder folder, List<File> files);

View File

@ -22,5 +22,10 @@ import ghidra.framework.model.DomainObject;
* a domainObject until it is needed.
*/
public interface DomainObjectService {
/**
* Get the domain object to be exported
* @return domain object or null if export limited to domain file
*/
public DomainObject getDomainObject();
}

View File

@ -44,9 +44,7 @@ public class GhidraFileOpenDataFlavorHandlerService {
FileOpenDropHandler.addDataFlavorHandler(DataFlavor.javaFileListFlavor,
new JavaFileListHandler());
DataFlavor linuxFileUrlFlavor =
new DataFlavor("application/x-java-serialized-object;class=java.lang.String",
"String file URL");
FileOpenDropHandler.addDataFlavorHandler(linuxFileUrlFlavor, new LinuxFileUrlHandler());
FileOpenDropHandler.addDataFlavorHandler(LinuxFileUrlHandler.linuxFileUrlFlavor,
new LinuxFileUrlHandler());
}
}

View File

@ -39,7 +39,7 @@ public class OptionsDialog extends DialogComponentProvider implements OptionList
* @param addressFactoryService a service for retrieving the AddressFactory if needed. This is
* passed instead of an actual AddressFactory, because to get an AddressFactory, it might
* require that a language be loaded or a program be opened and not all options require an
* AddressFactory.
* AddressFactory. If null, address based options will not be available.
*/
public OptionsDialog(List<Option> originalOptions,
OptionValidator validator, AddressFactoryService addressFactoryService) {

View File

@ -55,7 +55,7 @@ public class OptionsEditorPanel extends JPanel {
* Construct a new OptionsEditorPanel
* @param options the list of options to be edited.
* @param addressFactoryService a service for providing an appropriate AddressFactory if needed
* for editing an options.
* for editing an options. If null, address based options will not be available.
*/
public OptionsEditorPanel(List<Option> options, AddressFactoryService addressFactoryService) {
super(new VerticalLayout(5));
@ -76,10 +76,13 @@ public class OptionsEditorPanel extends JPanel {
panel.setBorder(createBorder(group));
for (Option option : optionGroup) {
panel.add(new GLabel(option.getName(), SwingConstants.RIGHT));
Component editorComponent = getEditorComponent(option);
editorComponent.setName(option.getName()); // set the component name to the option name
panel.add(editorComponent);
if (editorComponent != null) {
// Editor not available - omit option from panel
panel.add(new GLabel(option.getName(), SwingConstants.RIGHT));
editorComponent.setName(option.getName()); // set the component name to the option name
panel.add(editorComponent);
}
}
if (needsSelectAllDeselectAllButton(optionGroup)) {
@ -178,7 +181,14 @@ public class OptionsEditorPanel extends JPanel {
}
}
public Component getEditorComponent(Option option) {
/**
* Get the editor component for the specified option.
* @param option option to be edited
* @return option editor or null if prerequisite state not available to support
* editor (e.g., Address or AddressSpace editor when {@link AddressFactoryService}
* is not available).
*/
private Component getEditorComponent(Option option) {
// Special cases for library link/load options
if (option.getName().equals(AbstractLibrarySupportLoader.LINK_SEARCH_FOLDER_OPTION_NAME) ||
@ -262,6 +272,9 @@ public class OptionsEditorPanel extends JPanel {
}
private Component getAddressSpaceEditorComponent(Option option) {
if (addressFactoryService == null) {
return null;
}
JComboBox<AddressSpace> combo = new GComboBox<>();
AddressFactory addressFactory = addressFactoryService.getAddressFactory();
AddressSpace[] spaces =
@ -329,6 +342,9 @@ public class OptionsEditorPanel extends JPanel {
}
private Component getAddressEditorComponent(Option option) {
if (addressFactoryService == null) {
return null;
}
AddressFactory addressFactory = addressFactoryService.getAddressFactory();
AddressInput addressInput = new AddressInput();
addressInput.setName(option.getName());

View File

@ -24,6 +24,7 @@ import org.apache.commons.lang3.Validate;
import ghidra.app.util.*;
import ghidra.app.util.importer.MessageLog;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainObject;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.address.AddressSetView;
@ -101,32 +102,67 @@ abstract public class Exporter implements ExtensionPoint {
}
/**
* Returns true if this exporter knows how to export the given domain object type. For example,
* some exporters know how to export programs, other exporters can export project data type
* archives.
* Returns true if this exporter is capable of exporting the given domain file/object content
* type. For example, some exporters have the ability to export programs, other exporters can
* export project data type archives.
* <p>
* NOTE: This method should only be used as a preliminary check, if neccessary, to identify
* exporter implementations that are capable of handling a specified content type/class. Prior
* to export a final check should be performed based on the export or either a
* {@link DomainFile} or {@link DomainObject}:
* <p>
* {@link DomainFile} export - the method {@link #canExportDomainFile(DomainFile)} should be
* used to verify a direct project file export is possible using the
* {@link #export(File, DomainFile, TaskMonitor)} method.
* <p>
* {@link DomainObject} export - the method {@link #canExportDomainObject(DomainObject)} should
* be used to verify an export of a specific object is possible using the
* {@link #export(File, DomainObject, AddressSetView, TaskMonitor)} method.
*
* avoid opening DomainFile when possible.
* @param domainObjectClass the class of the domain object to test for exporting.
* @return true if this exporter knows how to export the given domain object type.
* @deprecated use {@link #canExportDomainObject(DomainObject)}
*/
@Deprecated(since = "10.3", forRemoval = true)
public boolean canExportDomainObject(Class<? extends DomainObject> domainObjectClass) {
return Program.class.isAssignableFrom(domainObjectClass);
}
/**
* Returns true if this exporter knows how to export the given domain object.
* Returns true if exporter can export the specified {@link DomainFile} without instantiating
* a {@link DomainObject}. This method should be used prior to exporting using the
* {@link #export(File, DomainFile, TaskMonitor)} method. All exporter capable of a
* {@link DomainFile} export must also support a export of a {@link DomainObject} so that any
* possible data modification/upgrade is included within resulting export.
*
* @param domainFile domain file
* @return true if export can occur else false if not
*/
public boolean canExportDomainFile(DomainFile domainFile) {
return false;
}
/**
* Returns true if this exporter knows how to export the given domain object considering any
* constraints based on the specific makeup of the object. This method should be used prior to
* exporting using the {@link #export(File, DomainObject, AddressSetView, TaskMonitor)} method.
*
* @param domainObject the domain object to test for exporting.
* @return true if this exporter knows how to export the given domain object.
*/
public boolean canExportDomainObject(DomainObject domainObject) {
if (domainObject == null) {
return false;
}
return canExportDomainObject(domainObject.getClass());
}
/**
* Returns true if this exporter can export less than the entire domain file.
* Returns true if this exporter can perform a restricted export of a {@link DomainObject}
* based upon a specified {@link AddressSetView}.
*
* @return true if this exporter can export less than the entire domain file.
*/
public boolean supportsPartialExport() {
public boolean supportsAddressRestrictedExport() {
return true;
}
@ -150,26 +186,47 @@ abstract public class Exporter implements ExtensionPoint {
abstract public void setOptions(List<Option> options) throws OptionException;
/**
* Actually does the work of exporting the program.
* Actually does the work of exporting a {@link DomainObject}. Export will include all
* saved and unsaved modifications which may have been made to the object.
*
* @param file the output file to write the exported info
* @param domainObj the domain object to export
* @param addrSet the address set if only a portion of the program should be exported
* NOTE: see {@link #supportsAddressRestrictedExport()}.
* @param monitor the task monitor
*
* @return true if the program was successfully exported; otherwise, false. If the program
* was not successfully exported, the message log should be checked to find the source of
* the error.
*
* @throws ExporterException
* @throws IOException
* @throws ExporterException if export error occurs
* @throws IOException if an IO error occurs
*/
abstract public boolean export(File file, DomainObject domainObj, AddressSetView addrSet,
TaskMonitor monitor) throws ExporterException, IOException;
/**
* Actually does the work of exporting a domain file, if supported (see
* {@link #canExportDomainFile(DomainFile)}). Export is performed without instantiation of a
* {@link DomainObject}.
*
* @param file the output file to write the exported info
* @param domainFile the domain file to be exported (e.g., packed DB file)
* @param monitor the task monitor
* @return true if the file was successfully exported; otherwise, false. If the file
* was not successfully exported, the message log should be checked to find the source of
* the error.
*
* @throws ExporterException if export error occurs
* @throws IOException if an IO error occurs
*/
public boolean export(File file, DomainFile domainFile, TaskMonitor monitor)
throws ExporterException, IOException {
throw new UnsupportedOperationException("DomainFile export not supported");
}
@Override
final public String toString() {
return getName();
}
}

View File

@ -16,22 +16,43 @@
package ghidra.app.util.exporter;
import java.io.File;
import java.io.IOException;
import java.util.List;
import ghidra.app.util.DomainObjectService;
import ghidra.app.util.Option;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainObject;
import ghidra.program.database.DataTypeArchiveDB;
import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.data.FileDataTypeManager;
import ghidra.program.model.listing.DataTypeArchive;
import ghidra.util.HelpLocation;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
public class ProjectArchiveExporter extends Exporter {
public static final String NAME = "Ghidra Data Type File";
public class GdtExporter extends Exporter {
public ProjectArchiveExporter() {
super(NAME, FileDataTypeManager.EXTENSION, null);
public static final String EXTENSION = "gdt";
public static final String SUFFIX = "." + EXTENSION;
public static final String NAME = "Ghidra Data Type Archive File";
public GdtExporter() {
super(NAME, EXTENSION, new HelpLocation("ExporterPlugin", EXTENSION));
}
@Override
public boolean canExportDomainObject(Class<? extends DomainObject> domainObjectClass) {
return DataTypeArchiveDB.class.isAssignableFrom(domainObjectClass);
}
@Override
public boolean canExportDomainFile(DomainFile domainFile) {
return canExportDomainObject(domainFile.getDomainObjectClass());
}
@Override
public boolean equals(Object obj) {
return (obj instanceof GdtExporter);
}
@Override
@ -56,6 +77,25 @@ public class ProjectArchiveExporter extends Exporter {
return true;
}
@Override
public boolean export(File file, DomainFile domainFile, TaskMonitor monitor)
throws ExporterException, IOException {
if (!canExportDomainFile(domainFile)) {
throw new UnsupportedOperationException("only DataTypeArchiveDB files are supported");
}
try {
domainFile.packFile(file, monitor);
}
catch (CancelledException e) {
return false;
}
catch (Exception e) {
log.appendMsg("Unexpected exception exporting file: " + e.getMessage());
return false;
}
return true;
}
@Override
public List<Option> getOptions(DomainObjectService domainObjectService) {
return EMPTY_OPTIONS;
@ -63,11 +103,14 @@ public class ProjectArchiveExporter extends Exporter {
@Override
public void setOptions(List<Option> options) {
// this exporter doesn't support any options
// no options for this exporter
}
/**
* Returns false. GDT export only supports entire database.
*/
@Override
public boolean canExportDomainObject(Class<? extends DomainObject> domainObjectClass) {
return DataTypeArchive.class.isAssignableFrom(domainObjectClass);
public boolean supportsAddressRestrictedExport() {
return false;
}
}

View File

@ -16,11 +16,14 @@
package ghidra.app.util.exporter;
import java.io.File;
import java.io.IOException;
import java.util.List;
import ghidra.app.util.DomainObjectService;
import ghidra.app.util.Option;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainObject;
import ghidra.program.database.ProgramDB;
import ghidra.program.model.address.AddressSetView;
import ghidra.util.HelpLocation;
import ghidra.util.exception.CancelledException;
@ -37,6 +40,15 @@ public class GzfExporter extends Exporter {
super(NAME, EXTENSION, new HelpLocation("ExporterPlugin", "gzf"));
}
@Override
public boolean canExportDomainFile(DomainFile domainFile) {
return canExportDomainObject(domainFile.getDomainObjectClass());
}
public boolean canExportDomainObject(Class<? extends DomainObject> domainObjectClass) {
return ProgramDB.class.isAssignableFrom(domainObjectClass);
}
@Override
public boolean equals(Object obj) {
return (obj instanceof GzfExporter);
@ -64,6 +76,25 @@ public class GzfExporter extends Exporter {
return true;
}
@Override
public boolean export(File file, DomainFile domainFile, TaskMonitor monitor)
throws ExporterException, IOException {
if (!canExportDomainFile(domainFile)) {
throw new UnsupportedOperationException("only ProgramDB files are supported");
}
try {
domainFile.packFile(file, monitor);
}
catch (CancelledException e) {
return false;
}
catch (Exception e) {
log.appendMsg("Unexpected exception exporting file: " + e.getMessage());
return false;
}
return true;
}
@Override
public List<Option> getOptions(DomainObjectService domainObjectService) {
return EMPTY_OPTIONS;
@ -78,7 +109,7 @@ public class GzfExporter extends Exporter {
* Returns false. GZF export only supports entire database.
*/
@Override
public boolean supportsPartialExport() {
public boolean supportsAddressRestrictedExport() {
return false;
}
}

View File

@ -58,7 +58,7 @@ public class OriginalFileExporter extends Exporter {
}
@Override
public boolean supportsPartialExport() {
public boolean supportsAddressRestrictedExport() {
return false;
}

View File

@ -0,0 +1,75 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.framework.main.datatree;
import java.awt.Component;
import java.io.File;
import java.util.List;
import docking.widgets.tree.GTreeNode;
import ghidra.app.services.FileImporterService;
import ghidra.app.util.FileOpenDataFlavorHandler;
import ghidra.framework.model.DomainFolder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.Msg;
import ghidra.util.Swing;
/**
* An abstract handler to facilitate drag-n-drop for a list of Java {@link File} objects which is
* dropped onto the Project data tree (see {@link DataTreeFlavorHandler}) or a running Ghidra Tool
* (see {@link FileOpenDataFlavorHandler}).
*/
abstract class AbstractFileListFlavorHandler
implements DataTreeFlavorHandler, FileOpenDataFlavorHandler {
/**
* Do import when destination folder has been specified (e.g., data tree folder node).
* @param folder destination folder (if null root folder will be assumed)
* @param files files to be imported
* @param tool target tool (active/current project assumed)
* @param component parent component for popup messages
*/
protected void doImport(DomainFolder folder, List<File> files, PluginTool tool,
Component component) {
Swing.runLater(() -> {
FileImporterService im = tool.getService(FileImporterService.class);
if (im == null) {
Msg.showError(AbstractFileListFlavorHandler.class, component, "Could Not Import",
"Could not find importer service.");
return;
}
if (files.size() == 1 && files.get(0).isFile()) {
im.importFile(folder, files.get(0));
}
else {
im.importFiles(folder, files);
}
});
}
protected DomainFolder getDomainFolder(GTreeNode destinationNode) {
if (destinationNode instanceof DomainFolderNode) {
return ((DomainFolderNode) destinationNode).getDomainFolder();
}
else if (destinationNode instanceof DomainFileNode) {
DomainFolderNode parent = (DomainFolderNode) destinationNode.getParent();
return parent.getDomainFolder();
}
return null;
}
}

View File

@ -36,10 +36,7 @@ public class GhidraDataFlavorHandlerService {
DataTreeDragNDropHandler.addActiveDataFlavorHandler(DataFlavor.javaFileListFlavor,
new JavaFileListHandler());
DataFlavor linuxFileUrlFlavor =
new DataFlavor("application/x-java-serialized-object;class=java.lang.String",
"String file URL");
DataTreeDragNDropHandler.addActiveDataFlavorHandler(linuxFileUrlFlavor,
DataTreeDragNDropHandler.addActiveDataFlavorHandler(LinuxFileUrlHandler.linuxFileUrlFlavor,
new LinuxFileUrlHandler());
}
}

View File

@ -24,68 +24,27 @@ import java.io.File;
import java.util.List;
import docking.widgets.tree.GTreeNode;
import ghidra.app.services.FileImporterService;
import ghidra.app.util.FileOpenDataFlavorHandler;
import ghidra.framework.model.DomainFolder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.Msg;
import ghidra.util.Swing;
import util.CollectionUtils;
/**
* {@literal A drag-and-drop handler for trees that is specific to List<File>.} (see
* {@link DataFlavor#javaFileListFlavor}).
* A handler to facilitate drag-n-drop for a list of Java {@link File} objects which is dropped
* onto the Project data tree or a running Ghidra Tool (see {@link DataFlavor#javaFileListFlavor}).
*/
public final class JavaFileListHandler implements DataTreeFlavorHandler, FileOpenDataFlavorHandler {
public final class JavaFileListHandler extends AbstractFileListFlavorHandler {
@Override
// This is for the FileOpenDataFlavorHandler for handling OS files dropped on a Ghidra Tool
public void handle(PluginTool tool, Object transferData, DropTargetDropEvent e, DataFlavor f) {
FileImporterService importer = tool.getService(FileImporterService.class);
if (importer == null) {
Msg.showError(this, null, "Could Not Import", "Could not find Importer Service");
return;
}
DomainFolder folder = tool.getProject().getProjectData().getRootFolder();
doImport(importer, folder, transferData);
List<File> fileList = CollectionUtils.asList((List<?>) transferData, File.class);
doImport(null, fileList, tool, tool.getToolFrame());
}
@Override
// This is for the DataFlavorHandler interface for handling OS files dropped onto a DataTree
public void handle(PluginTool tool, DataTree dataTree, GTreeNode destinationNode,
Object transferData, int dropAction) {
FileImporterService importer = tool.getService(FileImporterService.class);
if (importer == null) {
Msg.showError(this, dataTree, "Could Not Import", "Could not find Importer Service");
return;
}
DomainFolder folder = getDomainFolder(destinationNode);
doImport(importer, folder, transferData);
}
private void doImport(FileImporterService importer, DomainFolder folder, Object files) {
List<File> fileList = CollectionUtils.asList((List<?>) files, File.class);
Swing.runLater(() -> {
if (fileList.size() == 1 && fileList.get(0).isFile()) {
importer.importFile(folder, fileList.get(0));
}
else {
importer.importFiles(folder, fileList);
}
});
}
private DomainFolder getDomainFolder(GTreeNode destinationNode) {
if (destinationNode instanceof DomainFolderNode) {
return ((DomainFolderNode) destinationNode).getDomainFolder();
}
else if (destinationNode instanceof DomainFileNode) {
DomainFolderNode parent = (DomainFolderNode) destinationNode.getParent();
return parent.getDomainFolder();
}
return null;
List<File> fileList = CollectionUtils.asList((List<?>) transferData, File.class);
doImport(getDomainFolder(destinationNode), fileList, tool, dataTree);
}
}

View File

@ -15,7 +15,6 @@
*/
package ghidra.framework.main.datatree;
import java.awt.Component;
import java.awt.datatransfer.DataFlavor;
import java.awt.dnd.DropTargetDropEvent;
import java.io.File;
@ -26,62 +25,41 @@ import java.util.List;
import java.util.function.Function;
import docking.widgets.tree.GTreeNode;
import ghidra.app.services.FileImporterService;
import ghidra.app.util.FileOpenDataFlavorHandler;
import ghidra.framework.model.DomainFolder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.util.Msg;
/**
* A special handler to deal with files dragged from Linux to Ghidra. This class does double
* duty in that it opens files for DataTrees and for Tools (signaled via the interfaces it
* implements).
* A handler to facilitate drag-n-drop for a Linux URL-based file list which is dropped
* onto the Project data tree or a running Ghidra Tool (see {@link #linuxFileUrlFlavor}).
*/
public final class LinuxFileUrlHandler implements DataTreeFlavorHandler, FileOpenDataFlavorHandler {
public final class LinuxFileUrlHandler extends AbstractFileListFlavorHandler {
/**
* Linux URL-based file list {@link DataFlavor} to be used during handler registration
* using {@link DataTreeDragNDropHandler#addActiveDataFlavorHandler}.
*/
public static final DataFlavor linuxFileUrlFlavor =
new DataFlavor("application/x-java-serialized-object;class=java.lang.String",
"String file URL");
@Override
// This is for the FileOpenDataFlavorHandler for handling file drops from Linux to a Tool
public void handle(PluginTool tool, Object transferData, DropTargetDropEvent e, DataFlavor f) {
List<File> files = toFiles(transferData);
doImport(null, files, tool, tool.getToolFrame());
}
@Override
// This is for the DataFlavorHandler interface for handling node drops in DataTrees
public void handle(PluginTool tool, DataTree dataTree, GTreeNode destinationNode,
Object transferData, int dropAction) {
DomainFolder folder = getDomainFolder(destinationNode);
doImport(dataTree, transferData, tool, folder);
}
@Override
// This is for the FileOpenDataFlavorHandler for handling file drops from Linux to a Tool
public void handle(PluginTool tool, Object transferData, DropTargetDropEvent e, DataFlavor f) {
DomainFolder folder = tool.getProject().getProjectData().getRootFolder();
doImport(tool.getToolFrame(), transferData, tool, folder);
}
private void doImport(Component component, Object transferData, ServiceProvider sp,
DomainFolder folder) {
FileImporterService im = sp.getService(FileImporterService.class);
if (im == null) {
Msg.showError(this, component, "Could Not Import", "Could not find importer service.");
return;
}
List<File> files = toFiles(transferData);
if (files.isEmpty()) {
return;
}
if (files.size() == 1 && files.get(0).isFile()) {
im.importFile(folder, files.get(0));
}
else {
im.importFiles(folder, files);
}
doImport(getDomainFolder(destinationNode), files, tool, dataTree);
}
private List<File> toFiles(Object transferData) {
return toUrls(transferData, s -> {
return toFiles(transferData, s -> {
try {
return new File(new URL(s).toURI());
}
@ -98,29 +76,17 @@ public final class LinuxFileUrlHandler implements DataTreeFlavorHandler, FileOpe
});
}
private List<File> toUrls(Object transferData, Function<String, File> converter) {
private List<File> toFiles(Object transferData, Function<String, File> urlToFileConverter) {
List<File> files = new ArrayList<>();
String string = (String) transferData;
String[] urls = string.split("\\n");
for (String url : urls) {
File file = converter.apply(url);
File file = urlToFileConverter.apply(url);
if (file != null) {
files.add(file);
}
}
return files;
}
private DomainFolder getDomainFolder(GTreeNode destinationNode) {
if (destinationNode instanceof DomainFolderNode) {
return ((DomainFolderNode) destinationNode).getDomainFolder();
}
else if (destinationNode instanceof DomainFileNode) {
DomainFolderNode parent = (DomainFolderNode) destinationNode.getParent();
return parent.getDomainFolder();
}
return null;
}
}

View File

@ -15,15 +15,16 @@
*/
package ghidra.plugin.importer;
import java.util.*;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.io.File;
import java.io.IOException;
import java.nio.CharBuffer;
import java.util.*;
import docking.ActionContext;
import docking.action.*;
import docking.tool.ToolConstants;
import docking.widgets.filechooser.GhidraFileChooser;
import docking.widgets.filechooser.GhidraFileChooserMode;
import ghidra.app.CorePluginPackage;
@ -40,22 +41,25 @@ import ghidra.formats.gfilesystem.FileCache.FileCacheEntry;
import ghidra.formats.gfilesystem.FileCache.FileCacheEntryBuilder;
import ghidra.formats.gfilesystem.FileSystemService;
import ghidra.framework.main.*;
import ghidra.framework.main.datatree.DomainFileNode;
import ghidra.framework.main.datatree.DomainFolderNode;
import ghidra.framework.main.datatree.*;
import ghidra.framework.model.*;
import ghidra.framework.options.SaveState;
import ghidra.framework.options.ToolOptions;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.framework.preferences.Preferences;
import ghidra.framework.store.local.ItemDeserializer;
import ghidra.framework.store.local.LocalFileSystem;
import ghidra.plugins.importer.batch.BatchImportDialog;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.*;
import ghidra.program.util.ProgramSelection;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.*;
import ghidra.util.exception.AssertException;
import ghidra.util.exception.CancelledException;
import ghidra.util.filechooser.GhidraFileFilter;
import ghidra.util.task.TaskLauncher;
import ghidra.util.task.*;
/**
* A {@link Plugin} that supplies menu items and tasks to import files into Ghidra.
@ -80,8 +84,11 @@ public class ImporterPlugin extends Plugin
"This plugin manages importing files, including those contained within " +
"firmware/filesystem images.";
private static final String SIMPLE_UNPACK_OPTION = "Enable simple GZF/GDT unpack";
private static final boolean SIMPLE_UNPACK_OPTION_DEFAULT = false;
private DockingAction importAction;
private DockingAction importSelectionAction;// NA in front-end
private DockingAction importSelectionAction;
private DockingAction addToProgramAction;
private GhidraFileChooser chooser;
private FrontEndService frontEndService;
@ -98,6 +105,12 @@ public class ImporterPlugin extends Plugin
frontEndService = tool.getService(FrontEndService.class);
if (frontEndService != null) {
frontEndService.addProjectListener(this);
ToolOptions options = tool.getOptions(ToolConstants.FILE_IMPORT_OPTIONS);
HelpLocation help = new HelpLocation("ImporterPlugin", "Project_Tree");
options.registerOption(SIMPLE_UNPACK_OPTION, SIMPLE_UNPACK_OPTION_DEFAULT, help,
"Perform simple unpack when any packed DB file is imported");
}
setupImportAction();
@ -156,6 +169,16 @@ public class ImporterPlugin extends Plugin
@Override
public void importFiles(DomainFolder destFolder, List<File> files) {
if (destFolder == null) {
destFolder = tool.getProject().getProjectData().getRootFolder();
}
files = handleSimpleDBUnpack(destFolder, files);
if (files.isEmpty()) {
return;
}
BatchImportDialog.showAndImport(tool, null, files2FSRLs(files), destFolder,
getTool().getService(ProgramManager.class));
}
@ -175,11 +198,115 @@ public class ImporterPlugin extends Plugin
@Override
public void importFile(DomainFolder folder, File file) {
if (folder == null) {
folder = tool.getProject().getProjectData().getRootFolder();
}
if (handleSimpleDBUnpack(folder, file)) {
return;
}
FSRL fsrl = FileSystemService.getInstance().getLocalFSRL(file);
ProgramManager manager = tool.getService(ProgramManager.class);
ImporterUtilities.showImportDialog(tool, manager, fsrl, folder, null);
}
private static String makeValidUniqueFilename(String name, DomainFolder folder) {
// Trim-off file extension if ours *.g?? (e.g., gzf, gdt, etc.)
int extIndex = name.lastIndexOf(".g");
if (extIndex > 1 && (name.length() - extIndex) == 4) {
name = name.substring(0, extIndex);
}
CharBuffer buf = CharBuffer.wrap(name.toCharArray());
for (int i = 0; i < buf.length(); i++) {
if (!LocalFileSystem.isValidNameCharacter(buf.get(i))) {
buf.put(i, '_');
}
}
String baseName = buf.toString();
name = baseName;
int count = 0;
while (folder.getFile(name) != null) {
++count;
name = baseName + "." + count;
}
return name;
}
private List<File> handleSimpleDBUnpack(DomainFolder folder, List<File> files) {
if (frontEndService == null || !isSimpleUnpackEnabled()) {
return files;
}
ArrayList<File> remainingFiles = new ArrayList<>();
Task task = new Task("", true, true, true) {
@Override
public void run(TaskMonitor monitor) throws CancelledException {
for (File f : files) {
monitor.checkCanceled();
// Test for Packed DB file using ItemDeserializer
ItemDeserializer itemDeserializer = null;
try {
itemDeserializer = new ItemDeserializer(f); // fails for non-packed file
}
catch (IOException e) {
remainingFiles.add(f);
continue; // not a Packed DB - skip file
}
finally {
if (itemDeserializer != null) {
itemDeserializer.dispose();
}
}
monitor.setMessage("Unpacking " + f.getName() + " ...");
// Perform direct unpack of Packed DB file
String filename = makeValidUniqueFilename(f.getName(), folder);
try {
DomainFile df = folder.createFile(filename, f, monitor);
Msg.info(this, "Imported " + f.getName() + " to " + df.getPathname());
}
catch (InvalidNameException e) {
throw new AssertException(e); // unexpected - valid name was used
}
catch (IOException e) {
Msg.showError(JavaFileListHandler.class, tool.getToolFrame(),
"Packed DB Import Failed",
"Failed to import " + f.getName(), e);
}
}
}
};
TaskLauncher.launchModal("Import", task);
if (task.isCancelled()) {
return List.of(); // return empty list if cancelled
}
return remainingFiles; // return files not yet imported
}
private boolean handleSimpleDBUnpack(DomainFolder folder, File file) {
List<File> files = handleSimpleDBUnpack(folder, List.of(file));
return files.isEmpty();
}
private boolean isSimpleUnpackEnabled() {
if (frontEndService == null) {
return false;
}
ToolOptions options = tool.getOptions(ToolConstants.FILE_IMPORT_OPTIONS);
return options.getBoolean(SIMPLE_UNPACK_OPTION, SIMPLE_UNPACK_OPTION_DEFAULT);
}
@Override
public void projectClosed(Project project) {
if (importAction != null) {

View File

@ -108,6 +108,11 @@ public interface ToolConstants extends DockingToolConstants {
*/
public static final String TOOL_OPTIONS = "Tool";
/**
* File Import options name
*/
public static final String FILE_IMPORT_OPTIONS = "File Import";
/**
* Graph options name
*/

View File

@ -42,6 +42,14 @@ public class SkeletonExporter extends Exporter {
super("My Exporter", "exp", null);
}
@Override
public boolean supportsAddressRestrictedExport() {
// TODO: return true if addrSet export parameter can be used to restrict export
return false;
}
@Override
public boolean export(File file, DomainObject domainObj, AddressSetView addrSet,
TaskMonitor monitor) throws ExporterException, IOException {