mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-22 04:05:39 +00:00
Merge remote-tracking branch 'origin/GP-894_jpleasu_version_OSGi_extra_packages--SQUASHED'
This commit is contained in:
commit
9c46bb69f1
@ -26,6 +26,7 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
import org.apache.felix.framework.FrameworkFactory;
|
import org.apache.felix.framework.FrameworkFactory;
|
||||||
import org.apache.felix.framework.util.FelixConstants;
|
import org.apache.felix.framework.util.FelixConstants;
|
||||||
|
import org.apache.felix.framework.wiring.BundleRequirementImpl;
|
||||||
import org.jgrapht.graph.DirectedMultigraph;
|
import org.jgrapht.graph.DirectedMultigraph;
|
||||||
import org.jgrapht.traverse.TopologicalOrderIterator;
|
import org.jgrapht.traverse.TopologicalOrderIterator;
|
||||||
import org.osgi.framework.*;
|
import org.osgi.framework.*;
|
||||||
@ -691,7 +692,13 @@ public class BundleHost {
|
|||||||
Map<GhidraBundle, List<BundleRequirement>> requirementMap = new HashMap<>();
|
Map<GhidraBundle, List<BundleRequirement>> requirementMap = new HashMap<>();
|
||||||
for (GhidraBundle bundle : bundles) {
|
for (GhidraBundle bundle : bundles) {
|
||||||
try {
|
try {
|
||||||
requirementMap.put(bundle, bundle.getAllRequirements());
|
List<BundleRequirement> requirements = bundle.getAllRequirements();
|
||||||
|
// remove optional requirements
|
||||||
|
requirements.removeIf(r -> {
|
||||||
|
BundleRequirementImpl rimpl = (BundleRequirementImpl) r;
|
||||||
|
return rimpl.isOptional();
|
||||||
|
});
|
||||||
|
requirementMap.put(bundle, requirements);
|
||||||
}
|
}
|
||||||
catch (GhidraBundleException e) {
|
catch (GhidraBundleException e) {
|
||||||
fireBundleException(e);
|
fireBundleException(e);
|
||||||
|
@ -129,6 +129,10 @@ public class OSGiUtils {
|
|||||||
static List<BundleRequirement> parseImportPackage(String importPackageString)
|
static List<BundleRequirement> parseImportPackage(String importPackageString)
|
||||||
throws BundleException {
|
throws BundleException {
|
||||||
Map<String, Object> headerMap = new HashMap<>();
|
Map<String, Object> headerMap = new HashMap<>();
|
||||||
|
// assume version 2 for a more robust parse
|
||||||
|
headerMap.put(Constants.BUNDLE_MANIFESTVERSION, "2");
|
||||||
|
// symbolic name is required for version 2 bundle manifest
|
||||||
|
headerMap.put(Constants.BUNDLE_SYMBOLICNAME, Constants.SYSTEM_BUNDLE_SYMBOLICNAME);
|
||||||
headerMap.put(Constants.IMPORT_PACKAGE, importPackageString);
|
headerMap.put(Constants.IMPORT_PACKAGE, importPackageString);
|
||||||
ManifestParser manifestParser = new ManifestParser(null, null, null, headerMap);
|
ManifestParser manifestParser = new ManifestParser(null, null, null, headerMap);
|
||||||
return manifestParser.getRequirements();
|
return manifestParser.getRequirements();
|
||||||
@ -144,6 +148,10 @@ public class OSGiUtils {
|
|||||||
static List<BundleCapability> parseExportPackage(String exportPackageString)
|
static List<BundleCapability> parseExportPackage(String exportPackageString)
|
||||||
throws BundleException {
|
throws BundleException {
|
||||||
Map<String, Object> headerMap = new HashMap<>();
|
Map<String, Object> headerMap = new HashMap<>();
|
||||||
|
// assume version 2 for a more robust parse
|
||||||
|
headerMap.put(Constants.BUNDLE_MANIFESTVERSION, "2");
|
||||||
|
// symbolic name is required for version 2 bundle manifest
|
||||||
|
headerMap.put(Constants.BUNDLE_SYMBOLICNAME, Constants.SYSTEM_BUNDLE_SYMBOLICNAME);
|
||||||
headerMap.put(Constants.EXPORT_PACKAGE, exportPackageString);
|
headerMap.put(Constants.EXPORT_PACKAGE, exportPackageString);
|
||||||
ManifestParser manifestParser = new ManifestParser(null, null, null, headerMap);
|
ManifestParser manifestParser = new ManifestParser(null, null, null, headerMap);
|
||||||
return manifestParser.getCapabilities();
|
return manifestParser.getCapabilities();
|
||||||
@ -205,15 +213,64 @@ public class OSGiUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static private boolean hasEvenQuoteCount(String s) {
|
||||||
|
return s.chars().filter(c -> c == '"').count() % 2 == 0;
|
||||||
|
}
|
||||||
|
|
||||||
static void collectPackagesFromJar(Path jarPath, Set<String> packages) {
|
static void collectPackagesFromJar(Path jarPath, Set<String> packages) {
|
||||||
try {
|
try {
|
||||||
try (JarFile j = new JarFile(jarPath.toFile())) {
|
try (JarFile jarFile = new JarFile(jarPath.toFile())) {
|
||||||
j.stream().filter(entry -> entry.getName().endsWith(".class")).forEach(jarEntry -> {
|
// if this jar is an OSGi bundle, use its declared exports
|
||||||
String entryName = jarEntry.getName();
|
String exportPackageString =
|
||||||
int lastSlash = entryName.lastIndexOf('/');
|
jarFile.getManifest().getMainAttributes().getValue(Constants.EXPORT_PACKAGE);
|
||||||
packages.add(
|
if (exportPackageString != null) {
|
||||||
lastSlash > 0 ? entryName.substring(0, lastSlash).replace('/', '.') : "");
|
String saved = null;
|
||||||
});
|
/*
|
||||||
|
* split on commas not contained in quotes.
|
||||||
|
*
|
||||||
|
* e.g.
|
||||||
|
* org.foo,org.bar;uses="org.baz,org.qux"
|
||||||
|
* ^- should split here ^- not here
|
||||||
|
*
|
||||||
|
* We first split on all commas. The first entry,
|
||||||
|
* org.foo
|
||||||
|
* has an even number of quotes, so it's added as is to packages.
|
||||||
|
* The second entry,
|
||||||
|
* org.bar;uses="org.baz
|
||||||
|
* has an odd number of quotes, so we save
|
||||||
|
* org.bar;uses="org.baz,
|
||||||
|
* Then the third entry,
|
||||||
|
* org.qux"
|
||||||
|
* is appended, and the result has an even number of quotes, so is added.
|
||||||
|
*/
|
||||||
|
|
||||||
|
for (String packageName : exportPackageString.split(",")) {
|
||||||
|
boolean evenQuoteCount = hasEvenQuoteCount(packageName);
|
||||||
|
if (saved != null) {
|
||||||
|
packageName = saved + packageName;
|
||||||
|
evenQuoteCount = !evenQuoteCount;
|
||||||
|
saved = null;
|
||||||
|
}
|
||||||
|
if (evenQuoteCount) {
|
||||||
|
packages.add(packageName);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
saved = packageName + ',';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
jarFile.stream()
|
||||||
|
.filter(entry -> entry.getName().endsWith(".class"))
|
||||||
|
.forEach(jarEntry -> {
|
||||||
|
String entryName = jarEntry.getName();
|
||||||
|
int lastSlash = entryName.lastIndexOf('/');
|
||||||
|
if (lastSlash > 0) {
|
||||||
|
packages.add(
|
||||||
|
entryName.substring(0, lastSlash).replace('/', '.'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
catch (IOException e) {
|
||||||
|
@ -34,6 +34,10 @@ import utilities.util.FileUtilities;
|
|||||||
|
|
||||||
public class BundleHostTest extends AbstractGhidraHeadlessIntegrationTest {
|
public class BundleHostTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||||
private static final String TEMP_NAME_PREFIX = "sourcebundle";
|
private static final String TEMP_NAME_PREFIX = "sourcebundle";
|
||||||
|
|
||||||
|
// the version of Guava Ghidra is currently using.
|
||||||
|
private static final int GUAVA_MAJOR_VERSION = 19;
|
||||||
|
|
||||||
private BundleHost bundleHost;
|
private BundleHost bundleHost;
|
||||||
private CapturingBundleHostListener capturingBundleHostListener;
|
private CapturingBundleHostListener capturingBundleHostListener;
|
||||||
|
|
||||||
@ -333,6 +337,52 @@ public class BundleHostTest extends AbstractGhidraHeadlessIntegrationTest {
|
|||||||
// @formatter:on
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testImportFromExtraSystemPackagesWithVersionConstraint() throws Exception {
|
||||||
|
// @formatter:off
|
||||||
|
String goodRange = String.format("[%d,%d)", GUAVA_MAJOR_VERSION, GUAVA_MAJOR_VERSION+1);
|
||||||
|
addClass(
|
||||||
|
"//@importpackage com.google.common.io;version=\""+goodRange+"\"\n",
|
||||||
|
"import com.google.common.io.BaseEncoding;",
|
||||||
|
"AClass",
|
||||||
|
"@Override\n" +
|
||||||
|
"public String toString() {\n" +
|
||||||
|
" return BaseEncoding.base16().encode(new byte[] {0x42});\n" +
|
||||||
|
"}\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
buildAndActivate();
|
||||||
|
assertEquals("wrong response from instantiated class", "42",
|
||||||
|
getInstance("AClass").toString());
|
||||||
|
// @formatter:on
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testImportFromExtraSystemPackagesWithBadVersionConstraint() throws Exception {
|
||||||
|
// @formatter:off
|
||||||
|
String badRange = String.format("[%d,%d)", GUAVA_MAJOR_VERSION+1, GUAVA_MAJOR_VERSION+2);
|
||||||
|
addClass(
|
||||||
|
"//@importpackage com.google.common.io;version=\""+badRange+"\"\n",
|
||||||
|
"import com.google.common.io.BaseEncoding;",
|
||||||
|
"AClass",
|
||||||
|
"@Override\n" +
|
||||||
|
"public String toString() {\n" +
|
||||||
|
" return BaseEncoding.base16().encode(new byte[] {0x42});\n" +
|
||||||
|
"}\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
buildWithExpectations(
|
||||||
|
"1 import requirement remains unresolved:\n" +
|
||||||
|
" [null] osgi.wiring.package; (&(osgi.wiring.package=com.google.common.io)" +
|
||||||
|
"(version>="+(GUAVA_MAJOR_VERSION+1)+".0.0)" +
|
||||||
|
"(!(version>="+(GUAVA_MAJOR_VERSION+2)+".0.0))), " +
|
||||||
|
"from /tmp/ghidra.dev2tmp/sourcebundle000/AClass.java\n",
|
||||||
|
"1 missing package import:com.google.common.io (version>="+(GUAVA_MAJOR_VERSION+1)+".0.0)" +
|
||||||
|
", 1 source file with errors"
|
||||||
|
);
|
||||||
|
// @formatter:on
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLoadLibraryFromOtherBundleWithManifest() throws Exception {
|
public void testLoadLibraryFromOtherBundleWithManifest() throws Exception {
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
|
Loading…
Reference in New Issue
Block a user