Bug 565522 - EquinoxClassLoader returns wrong resource content

When searching for a resource that is indexed by a bundle URL the search
must not return an entry unless the index properly matches the class
path index.

This issue is surfaced when taking the external String from a bundle
resource URL and creating a new URL from it.  Here the index is stored
in the port of the URL and it must be validated when searching for the
entry when opening the connection to the new URL

Change-Id: I74685811c39ba10af2c92851759cd1eb6ffcf339
Signed-off-by: Thomas Watson <tjwatson@us.ibm.com>
diff --git a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/bundles/ClassLoadingBundleTests.java b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/bundles/ClassLoadingBundleTests.java
index f279c8c..516f80c 100644
--- a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/bundles/ClassLoadingBundleTests.java
+++ b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/bundles/ClassLoadingBundleTests.java
@@ -14,6 +14,7 @@
 package org.eclipse.osgi.tests.bundles;
 
 import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -39,6 +40,8 @@
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
 import junit.framework.AssertionFailedError;
 import junit.framework.Test;
 import junit.framework.TestSuite;
@@ -2454,6 +2457,80 @@
 		assertNull("Found more events.", events.poll(1, TimeUnit.SECONDS));
 	}
 
+	public void testBug565522FragmentClasspath() throws IOException, BundleException {
+		ByteArrayOutputStream libResourceJarBytes1 = new ByteArrayOutputStream();
+		try (JarOutputStream libResourceJar = new JarOutputStream(libResourceJarBytes1)) {
+			libResourceJar.putNextEntry(new JarEntry("META-INF/"));
+			libResourceJar.closeEntry();
+			libResourceJar.putNextEntry(new JarEntry("META-INF/services/"));
+			libResourceJar.closeEntry();
+			libResourceJar.putNextEntry(new JarEntry("META-INF/services/some.bundle.Factory"));
+			libResourceJar.write("testFactory1".getBytes());
+			libResourceJar.closeEntry();
+		}
+		ByteArrayOutputStream libResourceJarBytes2 = new ByteArrayOutputStream();
+		try (JarOutputStream libResourceJar = new JarOutputStream(libResourceJarBytes2)) {
+			libResourceJar.putNextEntry(new JarEntry("META-INF/"));
+			libResourceJar.closeEntry();
+			libResourceJar.putNextEntry(new JarEntry("META-INF/services/"));
+			libResourceJar.closeEntry();
+			libResourceJar.putNextEntry(new JarEntry("META-INF/services/some.bundle.Factory"));
+			libResourceJar.write("testFactory2".getBytes());
+			libResourceJar.closeEntry();
+		}
+
+		File outputDir = OSGiTestsActivator.getContext().getDataFile(getName()); // $NON-NLS-1$
+		outputDir.mkdirs();
+
+		Map<String, String> hostHeaders = new HashMap<>();
+		hostHeaders.put(Constants.BUNDLE_MANIFESTVERSION, "2");
+		hostHeaders.put(Constants.BUNDLE_SYMBOLICNAME, "host");
+		hostHeaders.put(Constants.BUNDLE_CLASSPATH, "., lib/resource.jar");
+		Map<String, byte[]> hostEntries = new HashMap<>();
+		hostEntries.put("lib/", null);
+		hostEntries.put("lib/resource.jar", libResourceJarBytes1.toByteArray());
+		File hostFile = SystemBundleTests.createBundleWithBytes(outputDir, "host", hostHeaders, hostEntries);
+
+		Map<String, String> fragHeaders = new HashMap<>();
+		fragHeaders.put(Constants.BUNDLE_MANIFESTVERSION, "2");
+		fragHeaders.put(Constants.BUNDLE_SYMBOLICNAME, "fragment");
+		fragHeaders.put(Constants.BUNDLE_CLASSPATH, "., lib/resource.jar");
+		fragHeaders.put(Constants.FRAGMENT_HOST, "host");
+		Map<String, byte[]> fragEntries = new HashMap<>();
+		fragEntries.put("lib/", null);
+		fragEntries.put("lib/resource.jar", libResourceJarBytes2.toByteArray());
+		File fragFile = SystemBundleTests.createBundleWithBytes(outputDir, "frag", fragHeaders, fragEntries);
+
+		Bundle host = null, frag = null;
+		try {
+			host = getContext().installBundle(hostFile.toURI().toASCIIString());
+			frag = getContext().installBundle(fragFile.toURI().toASCIIString());
+			host.start();
+			Enumeration<URL> eResources = host.getResources("META-INF/services/some.bundle.Factory");
+			assertNotNull("No resources found.", eResources);
+			List<URL> resources = new ArrayList<>();
+			while (eResources.hasMoreElements()) {
+				resources.add(eResources.nextElement());
+			}
+			assertEquals("Wrong number of resources.", 2, resources.size());
+			assertEquals("Wrong content for resource 1", "testFactory1", readURL(resources.get(0)));
+			assertEquals("Wrong content for resource 2", "testFactory2", readURL(resources.get(1)));
+
+			// round trip the URLs
+			URL copyURL1 = new URL(resources.get(0).toExternalForm());
+			URL copyURL2 = new URL(resources.get(1).toExternalForm());
+			assertEquals("Wrong content for url copy 1", "testFactory1", readURL(copyURL1));
+			assertEquals("Wrong content for url copy 2", "testFactory2", readURL(copyURL2));
+		} finally {
+			if (host != null) {
+				host.uninstall();
+			}
+			if (frag != null) {
+				frag.uninstall();
+			}
+		}
+	}
+
 	void refreshBundles(Collection<Bundle> bundles) throws InterruptedException {
 		final CountDownLatch refreshSignal = new CountDownLatch(1);
 		getContext().getBundle(Constants.SYSTEM_BUNDLE_LOCATION).adapt(FrameworkWiring.class).refreshBundles(bundles, new FrameworkListener() {
diff --git a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/bundles/SystemBundleTests.java b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/bundles/SystemBundleTests.java
index 018a040..b0e9ab9 100755
--- a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/bundles/SystemBundleTests.java
+++ b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/bundles/SystemBundleTests.java
@@ -2442,6 +2442,32 @@
 		return file;
 	}
 
+	public static File createBundleWithBytes(File outputDir, String bundleName, Map<String, String> headers,
+			Map<String, byte[]>... entries) throws IOException {
+		Manifest m = new Manifest();
+		Attributes attributes = m.getMainAttributes();
+		attributes.putValue("Manifest-Version", "1.0");
+		for (Map.Entry<String, String> entry : headers.entrySet()) {
+			attributes.putValue(entry.getKey(), entry.getValue());
+		}
+		File file = new File(outputDir, "bundle" + bundleName + ".jar"); //$NON-NLS-1$ //$NON-NLS-2$
+		JarOutputStream jos = new JarOutputStream(new FileOutputStream(file), m);
+		if (entries != null) {
+			for (Map<String, byte[]> entryMap : entries) {
+				for (Map.Entry<String, byte[]> entry : entryMap.entrySet()) {
+					jos.putNextEntry(new JarEntry(entry.getKey()));
+					if (entry.getValue() != null) {
+						jos.write(entry.getValue());
+					}
+					jos.closeEntry();
+				}
+			}
+		}
+		jos.flush();
+		jos.close();
+		return file;
+	}
+
 	public void testBug405919() throws Exception {
 		File config = OSGiTestsActivator.getContext().getDataFile(getName());
 		config.mkdirs();
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/classpath/ClasspathManager.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/classpath/ClasspathManager.java
index fa24f45..ec13c90 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/classpath/ClasspathManager.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/loader/classpath/ClasspathManager.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2005, 2017 IBM Corporation and others.
+ * Copyright (c) 2005, 2020 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -521,17 +521,18 @@
 	}
 
 	private BundleEntry findLocalEntry(String path, ClasspathEntry[] cpEntries, int classPathIndex, int[] curIndex) {
-		BundleEntry result = null;
 		for (ClasspathEntry cpEntry : cpEntries) {
 			if (cpEntry != null) {
-				result = cpEntry.findEntry(path);
-				if (result != null && (classPathIndex == -1 || classPathIndex == curIndex[0])) {
-					return result;
+				if (classPathIndex == -1 || classPathIndex == curIndex[0]) {
+					BundleEntry result = cpEntry.findEntry(path);
+					if (result != null) {
+						return result;
+					}
 				}
 			}
 			curIndex[0]++;
 		}
-		return result;
+		return null;
 	}
 
 	/**