mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-28 15:11:44 +00:00
Merge remote-tracking branch
'origin/GP-3466-dragonmacher-extenions-version-check' into patch (Closes #1193)
This commit is contained in:
commit
5e87119ef1
@ -99,9 +99,6 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
||||
}
|
||||
|
||||
ExtensionDetails extension = getSelectedExtension(rowIndex);
|
||||
if (!isValidVersion(extension)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do not allow GUI uninstallation of extensions manually installed in installation
|
||||
// directory
|
||||
|
@ -157,9 +157,14 @@ public class ExtensionTableProvider extends DialogComponentProvider {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!hasCorrectVersion(file)) {
|
||||
Msg.showError(this, null, "Installation Error", "Extension version for [" +
|
||||
file.getName() + "] is incompatible with Ghidra.");
|
||||
String extensionVersion = getExtensionVersion(file);
|
||||
if (extensionVersion == null) {
|
||||
Msg.showError(this, null, "Installation Error",
|
||||
"Unable to read extension version for [" + file + "]");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ExtensionUtils.validateExtensionVersion(extensionVersion)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -188,53 +193,41 @@ public class ExtensionTableProvider extends DialogComponentProvider {
|
||||
addAction(addAction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the extension(s) represented by the given file (or directory) have
|
||||
* a version that is compatible with the current version of Ghidra.
|
||||
*
|
||||
* @param file the file or directory to inspect
|
||||
* @return true if the extension(s) has the correct version
|
||||
*/
|
||||
private boolean hasCorrectVersion(File file) {
|
||||
|
||||
String ghidraVersion = Application.getApplicationVersion();
|
||||
|
||||
// If the given file is a zip...
|
||||
if (file.isFile()) {
|
||||
try {
|
||||
if (ExtensionUtils.isZip(file)) {
|
||||
Properties props = ExtensionUtils.getPropertiesFromArchive(file);
|
||||
if (props == null) {
|
||||
return false; // no prop file exists
|
||||
}
|
||||
ExtensionDetails extension =
|
||||
ExtensionUtils.createExtensionDetailsFromProperties(props);
|
||||
String extVersion = extension.getVersion();
|
||||
if (extVersion != null && extVersion.equals(ghidraVersion)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ExtensionException e) {
|
||||
// just fall through
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
private String getExtensionVersion(File file) {
|
||||
|
||||
// If the given file is a directory...
|
||||
List<ResourceFile> propFiles =
|
||||
ExtensionUtils.findExtensionPropertyFiles(new ResourceFile(file), true);
|
||||
for (ResourceFile propFile : propFiles) {
|
||||
ExtensionDetails extension =
|
||||
ExtensionUtils.createExtensionDetailsFromPropertyFile(propFile);
|
||||
String extVersion = extension.getVersion();
|
||||
if (extVersion != null && extVersion.equals(ghidraVersion)) {
|
||||
return true;
|
||||
if (!file.isFile()) {
|
||||
List<ResourceFile> propFiles =
|
||||
ExtensionUtils.findExtensionPropertyFiles(new ResourceFile(file), true);
|
||||
for (ResourceFile props : propFiles) {
|
||||
ExtensionDetails ext = ExtensionUtils.createExtensionDetailsFromPropertyFile(props);
|
||||
String version = ext.getVersion();
|
||||
if (version != null) {
|
||||
return version;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return false;
|
||||
// If the given file is a zip...
|
||||
try {
|
||||
if (ExtensionUtils.isZip(file)) {
|
||||
Properties props = ExtensionUtils.getPropertiesFromArchive(file);
|
||||
if (props == null) {
|
||||
return null; // no prop file exists
|
||||
}
|
||||
ExtensionDetails ext = ExtensionUtils.createExtensionDetailsFromProperties(props);
|
||||
String version = ext.getVersion();
|
||||
if (version != null) {
|
||||
return version;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ExtensionException e) {
|
||||
// just fall through
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -265,7 +258,7 @@ public class ExtensionTableProvider extends DialogComponentProvider {
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter for a {@link GhidraFileChooser} that restricts selection to those
|
||||
* Filter for a {@link GhidraFileChooser} that restricts selection to those
|
||||
* files that are Ghidra Extensions (zip files with an extension.properties
|
||||
* file) or folders.
|
||||
*/
|
||||
|
@ -27,6 +27,7 @@ import org.apache.commons.compress.utils.IOUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import docking.DockingWindowManager;
|
||||
import docking.widgets.OptionDialog;
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.framework.options.PreferenceState;
|
||||
@ -43,7 +44,7 @@ import utility.application.ApplicationLayout;
|
||||
import utility.module.ModuleUtilities;
|
||||
|
||||
/**
|
||||
* Utility class for managing Ghidra Extensions.
|
||||
* Utility class for managing Ghidra Extensions.
|
||||
* <p>
|
||||
* Extensions are defined as any archive or folder that contains an <code>extension.properties</code>
|
||||
* file. This properties file can contain the following attributes:
|
||||
@ -54,13 +55,13 @@ import utility.module.ModuleUtilities;
|
||||
* <li>createdOn (format: mm/dd/yyyy)</li>
|
||||
* </ul>
|
||||
*
|
||||
* Extensions may be installed/uninstalled by users at runtime, using the {@link ExtensionTableProvider}.
|
||||
* Installation consists of unzipping the extension archive to an installation folder, currently
|
||||
* Extensions may be installed/uninstalled by users at runtime, using the {@link ExtensionTableProvider}.
|
||||
* Installation consists of unzipping the extension archive to an installation folder, currently
|
||||
* <code>Ghidra/Extensions</code>. To uninstall, the unpacked folder is simply removed.
|
||||
*/
|
||||
public class ExtensionUtils {
|
||||
|
||||
/** Magic number that identifies the first bytes of a ZIP archive. This is used to verify
|
||||
/** Magic number that identifies the first bytes of a ZIP archive. This is used to verify
|
||||
that a file is a zip rather than just checking the extension. */
|
||||
private static final int ZIPFILE = 0x504b0304;
|
||||
|
||||
@ -77,7 +78,7 @@ public class ExtensionUtils {
|
||||
* <li>{@link ApplicationLayout#getExtensionArchiveDir}</li>
|
||||
* <li>{@link ApplicationLayout#getExtensionInstallationDirs}</li>
|
||||
* </ul>
|
||||
* If users install extensions from other locations, the installed version of
|
||||
* If users install extensions from other locations, the installed version of
|
||||
* the extension will be known, but the source archive location will not be retained.
|
||||
*
|
||||
* @return list of unique extensions
|
||||
@ -87,13 +88,13 @@ public class ExtensionUtils {
|
||||
|
||||
Set<ExtensionDetails> allExtensions = new HashSet<>();
|
||||
|
||||
// First grab anything in the archive and install directories.
|
||||
// First grab anything in the archive and install directories.
|
||||
Set<ExtensionDetails> archived = getArchivedExtensions();
|
||||
Set<ExtensionDetails> installed = getInstalledExtensions(false);
|
||||
|
||||
// Now we need to combine the two lists. For items that are in both lists, we have to ensure
|
||||
// that the one we return in the final list has all attributes from both
|
||||
// versions.
|
||||
// versions.
|
||||
//
|
||||
// Note that we prefer attributes in the installed version over the archived version; this is
|
||||
// because users may manually update the .properties file at runtime, but only for the installed
|
||||
@ -113,7 +114,7 @@ public class ExtensionUtils {
|
||||
|
||||
allExtensions.addAll(archived);
|
||||
|
||||
// Finally add all the installed extensions that aren't in the archive set we
|
||||
// Finally add all the installed extensions that aren't in the archive set we
|
||||
// just added. Because these are sets, we're ensuring there aren't any dupes.
|
||||
allExtensions.addAll(installed);
|
||||
|
||||
@ -236,7 +237,7 @@ public class ExtensionUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs the given extension file. This can be either an archive (zip) or a
|
||||
* Installs the given extension file. This can be either an archive (zip) or a
|
||||
* directory that contains an extension.properties file.
|
||||
*
|
||||
* @param rFile the extension to install
|
||||
@ -284,7 +285,7 @@ public class ExtensionUtils {
|
||||
extension.getName()).getFile(false);
|
||||
|
||||
if (extension.getArchivePath() == null) {
|
||||
// Special Case: If the archive path is null then this must be an extension that
|
||||
// Special Case: If the archive path is null then this must be an extension that
|
||||
// was installed from an external location, then uninstalled. In this case, there
|
||||
// should be a Module.manifest.uninstalled and extension.properties.uninstalled
|
||||
// present. If so, just restore them. If not, there's a problem.
|
||||
@ -297,20 +298,19 @@ public class ExtensionUtils {
|
||||
|
||||
// Verify that the version of the extension is valid for this version of Ghidra. If not,
|
||||
// just exit without installing.
|
||||
String ghidraVersion = Application.getApplicationVersion();
|
||||
if (!extension.getVersion().equals(ghidraVersion)) {
|
||||
Msg.warn(null, "Extension version for [" + extension.getName() +
|
||||
"] does not match Ghidra version; cannot install.");
|
||||
if (!validateExtensionVersion(extension.getVersion())) {
|
||||
Msg.warn(ExtensionUtils.class, "Extension version for [" + extension.getName() +
|
||||
"] does not match Ghidra version; did not install.");
|
||||
return false;
|
||||
}
|
||||
|
||||
ResourceFile file = new ResourceFile(extension.getArchivePath());
|
||||
|
||||
// We need to handle a special case: If the user selects an extension to uninstall using
|
||||
// We need to handle a special case: If the user selects an extension to uninstall using
|
||||
// the GUI then tries to reinstall it without restarting Ghidra, the extension hasn't actually
|
||||
// been removed yet; just the manifest file has been renamed. In this case we don't need to go through
|
||||
// been removed yet; just the manifest file has been renamed. In this case we don't need to go through
|
||||
// the full install process of unzipping or copying files to the install location. All we need
|
||||
// to do is rename the manifest file from Module.manifest.uninstall back to Module.manifest.
|
||||
// to do is rename the manifest file from Module.manifest.uninstall back to Module.manifest.
|
||||
if (installDir.exists()) {
|
||||
return restoreStateFiles(installDir);
|
||||
}
|
||||
@ -350,6 +350,31 @@ public class ExtensionUtils {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the given extension version to the current Ghidra version. If they are different,
|
||||
* then the user will be prompted to confirm the installation. This method will return true
|
||||
* if the versions match or the user has chosen to install anyway.
|
||||
*
|
||||
* @param extensionVersion the extension version
|
||||
* @return true if the versions match or the user has chosen to install anyway
|
||||
*/
|
||||
public static boolean validateExtensionVersion(String extensionVersion) {
|
||||
if (extensionVersion == null) {
|
||||
return false;
|
||||
}
|
||||
String ghidraVersion = Application.getApplicationVersion();
|
||||
if (extensionVersion.equals(ghidraVersion)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int choice = OptionDialog.showOptionDialogWithCancelAsDefaultButton(null,
|
||||
"Extension Version Mismatch",
|
||||
"Extension version mismatch.\nExtension version: " + extensionVersion + "\n" +
|
||||
"Ghidra version: " + ghidraVersion,
|
||||
"Install Anyway");
|
||||
return choice == OptionDialog.OPTION_ONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given file or directory is a valid ghidra extension.
|
||||
* <p>
|
||||
@ -374,7 +399,7 @@ public class ExtensionUtils {
|
||||
}
|
||||
|
||||
// If the given file is a zip, it's an extension if there's an extension.properties
|
||||
// file at the TOP LEVEL ONLY; we don't want to search for nested property files (this
|
||||
// file at the TOP LEVEL ONLY; we don't want to search for nested property files (this
|
||||
// would cause us to match things like the main ghidra distribution zip file.
|
||||
// eg: DatabaseTools/extension.properties is valid
|
||||
// DatabaseTools/foo/extension.properties is no.
|
||||
@ -385,10 +410,10 @@ public class ExtensionUtils {
|
||||
ZipArchiveEntry entry = zipEntries.nextElement();
|
||||
|
||||
// This is a bit ugly, but to ensure we only search for the property file at the
|
||||
// top level, only inspect file names that contain a single path separator.
|
||||
// top level, only inspect file names that contain a single path separator.
|
||||
// Also normalize the file path so separators (slashes) are checked correctly
|
||||
// on any platform.
|
||||
//
|
||||
//
|
||||
// Note: We have a PathUtilties method for this, but this package cannot access it and
|
||||
// i don't want to add a dependency just for this case.
|
||||
String path = entry.getName();
|
||||
@ -455,14 +480,14 @@ public class ExtensionUtils {
|
||||
|
||||
/**
|
||||
* Returns a list of files representing all the <code>extension.properties</code> files found
|
||||
* under a given directory. This will ONLY search the given directory and its immediate children.
|
||||
* under a given directory. This will ONLY search the given directory and its immediate children.
|
||||
* The conops are as follows:
|
||||
* <ul>
|
||||
* <li>If sourceFile is a directory and it contains an extension.properties file, then that file is returned</li>
|
||||
* <li>If sourceFile does not contain an extension.properties file, then any immediate directories are searched (ignoring Skeleton directory)</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Note: This will NOT search zip files. If you have a zip, call {@link #getPropertiesFromArchive(File)}
|
||||
* Note: This will NOT search zip files. If you have a zip, call {@link #getPropertiesFromArchive(File)}
|
||||
* instead.
|
||||
*
|
||||
* @param sourceFile the directory to inspect
|
||||
@ -537,7 +562,7 @@ public class ExtensionUtils {
|
||||
*/
|
||||
private static boolean runInstallTask(File file) {
|
||||
|
||||
// Keeps track of whether the install operation succeeds or fails. Must use AtomicBoolean
|
||||
// Keeps track of whether the install operation succeeds or fails. Must use AtomicBoolean
|
||||
// so we can safely update the value in the task thread.
|
||||
AtomicBoolean installed = new AtomicBoolean(false);
|
||||
|
||||
@ -552,9 +577,9 @@ public class ExtensionUtils {
|
||||
installed.set(true);
|
||||
}
|
||||
catch (ExtensionException e) {
|
||||
// If there's a problem copying files, check to see if there's already an extension
|
||||
// with this name in the install location that was slated for removal. If so, just
|
||||
// restore the extension properties and manifest files.
|
||||
// If there's a problem copying files, check to see if there's already an extension
|
||||
// with this name in the install location that was slated for removal. If so, just
|
||||
// restore the extension properties and manifest files.
|
||||
if (e.getExceptionType() == ExtensionExceptionType.COPY_ERROR ||
|
||||
e.getExceptionType() == ExtensionExceptionType.DUPLICATE_FILE_ERROR) {
|
||||
|
||||
@ -606,7 +631,7 @@ public class ExtensionUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively searches a given directory for any module manifest and extension
|
||||
* Recursively searches a given directory for any module manifest and extension
|
||||
* properties files that are in an installed state and converts them to an uninstalled
|
||||
* state.
|
||||
*
|
||||
@ -661,7 +686,7 @@ public class ExtensionUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively searches a given directory for any module manifest and extension
|
||||
* Recursively searches a given directory for any module manifest and extension
|
||||
* properties files that are in an uninstalled state and restores them.
|
||||
*
|
||||
* Specifically, the following will be renamed:
|
||||
@ -731,7 +756,7 @@ public class ExtensionUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a zip file, returns the {@link Properties} defined in the embedded extension.properties file.
|
||||
* Given a zip file, returns the {@link Properties} defined in the embedded extension.properties file.
|
||||
*
|
||||
* @param file the extension archive file
|
||||
* @return the properties file, or null if doesn't exist
|
||||
@ -786,10 +811,10 @@ public class ExtensionUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpacks a given zip file to {@link ApplicationLayout#getExtensionInstallationDirs}. The
|
||||
* Unpacks a given zip file to {@link ApplicationLayout#getExtensionInstallationDirs}. The
|
||||
* file permissions in the original zip will be retained.
|
||||
* <p>
|
||||
* Note: This method uses the Apache zip files since they keep track of permissions info;
|
||||
* Note: This method uses the Apache zip files since they keep track of permissions info;
|
||||
* the built-in java objects (e.g., ZipEntry) do not.
|
||||
*
|
||||
* @param zipFile the zip file to unpack
|
||||
@ -838,7 +863,7 @@ public class ExtensionUtils {
|
||||
|
||||
// ...and update its permissions. But only continue if the zip
|
||||
// was created on a unix platform. If not we cannot use the posix
|
||||
// libraries to set permissions.
|
||||
// libraries to set permissions.
|
||||
if (entry.getPlatform() == ZipArchiveEntry.PLATFORM_UNIX) {
|
||||
|
||||
int mode = entry.getUnixMode();
|
||||
@ -899,7 +924,7 @@ public class ExtensionUtils {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
// Get the set of extensions that the tool already knows about. This information is stored
|
||||
// Get the set of extensions that the tool already knows about. This information is stored
|
||||
// in the tool preferences.
|
||||
Set<ExtensionDetails> knownExtensionsSet = new HashSet<>();
|
||||
DockingWindowManager dockingWindowManager =
|
||||
@ -960,7 +985,7 @@ public class ExtensionUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to delete any extension directories that do not contain a Module.manifest
|
||||
* Attempts to delete any extension directories that do not contain a Module.manifest
|
||||
* file. This indicates that the extension was slated to be uninstalled by the user.
|
||||
*
|
||||
* @see #uninstall
|
||||
|
Loading…
Reference in New Issue
Block a user