Bug 507178 - Issues with encoding/decoding file URLs in various
scenarios

Change-Id: I2cf6b79d055eef7fb180f3f0f75032cbb84b7d51
Signed-off-by: Thomas Watson <tjwatson@us.ibm.com>
diff --git a/bundles/org.eclipse.equinox.launcher/src/org/eclipse/equinox/launcher/Main.java b/bundles/org.eclipse.equinox.launcher/src/org/eclipse/equinox/launcher/Main.java
index 34bcf44..bbec0b6 100644
--- a/bundles/org.eclipse.equinox.launcher/src/org/eclipse/equinox/launcher/Main.java
+++ b/bundles/org.eclipse.equinox.launcher/src/org/eclipse/equinox/launcher/Main.java
@@ -2137,7 +2137,7 @@
 		Properties props = new Properties();
 		InputStream is = null;
 		try {
-			is = url.openStream();
+			is = getStream(url);
 			props.load(is);
 		} finally {
 			if (is != null)
@@ -2150,6 +2150,17 @@
 		return props;
 	}
 
+	private InputStream getStream(URL location) throws IOException {
+		if ("file".equalsIgnoreCase(location.getProtocol())) { //$NON-NLS-1$
+			// this is done to handle URLs with invalid syntax in the path
+			File f = new File(location.getPath());
+			if (f.exists()) {
+				return new FileInputStream(f);
+			}
+		}
+		return location.openStream();
+	}
+
 	/*
 	 * Handle splash screen.
 	 *  The splash screen is displayed natively.  Whether or not the splash screen
diff --git a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/bundles/BundleInstallUpdateTests.java b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/bundles/BundleInstallUpdateTests.java
index 92048c5..029f856 100644
--- a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/bundles/BundleInstallUpdateTests.java
+++ b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/bundles/BundleInstallUpdateTests.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2009, 2015 IBM Corporation and others.
+ * Copyright (c) 2009, 2016 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -11,9 +11,8 @@
 package org.eclipse.osgi.tests.bundles;
 
 import java.io.*;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.Collection;
+import java.net.*;
+import java.util.*;
 import junit.framework.Test;
 import junit.framework.TestSuite;
 import org.eclipse.osgi.tests.OSGiTestsActivator;
@@ -290,4 +289,82 @@
 			}
 		}
 	}
+
+	public void testPercentLocation() throws BundleException, IOException {
+		doTestSpecialChars('%', false);
+		doTestSpecialChars('%', true);
+	}
+
+	public void testSpaceLocation() throws BundleException, IOException {
+		doTestSpecialChars(' ', false);
+		doTestSpecialChars(' ', true);
+	}
+
+	private void doTestSpecialChars(char c, boolean encode) throws BundleException, IOException {
+		File bundlesDirectory = OSGiTestsActivator.getContext().getDataFile("file_with_" + c + "_char");
+		bundlesDirectory.mkdirs();
+
+		File testBundleJarFile = SystemBundleTests.createBundle(bundlesDirectory, getName() + 1, false, false);
+		@SuppressWarnings("deprecation")
+		String testBundleJarFileURL = encode ? testBundleJarFile.toURI().toString() : testBundleJarFile.toURL().toString();
+		File testBundleDirFile = SystemBundleTests.createBundle(bundlesDirectory, getName() + 2, false, true);
+		@SuppressWarnings("deprecation")
+		String testBundleDirFileURL = encode ? testBundleDirFile.toURI().toString() : testBundleDirFile.toURL().toString();
+
+		// Test with reference URL to jar bundle
+		Bundle testBundleJarRef = getContext().installBundle("reference:" + testBundleJarFileURL);
+		testBundleJarRef.start();
+		testBundleJarRef.uninstall();
+
+		// Test with reference URL to dir bundle
+		Bundle testBundleDirRef = getContext().installBundle("reference:" + testBundleDirFileURL);
+		testBundleDirRef.start();
+		testBundleDirRef.uninstall();
+
+		// Test with jar bundle
+		Bundle testBundleJar = getContext().installBundle(testBundleJarFileURL);
+		testBundleJar.start();
+		testBundleJar.uninstall();
+
+		// Test with dir bundle
+		Bundle testBundleDir = getContext().installBundle(testBundleDirFileURL);
+		testBundleDir.start();
+		testBundleDir.uninstall();
+	}
+
+	public void testPercentCharBundleEntry() throws IOException, BundleException {
+		doTestSpaceCharsBundleEntry('%');
+	}
+
+	public void testSpaceCharBundleEntry() throws IOException, BundleException {
+		doTestSpaceCharsBundleEntry(' ');
+	}
+
+	public void testPlusCharBundleEntry() throws IOException, BundleException {
+		doTestSpaceCharsBundleEntry('+');
+	}
+
+	public void doTestSpaceCharsBundleEntry(char c) throws IOException, BundleException {
+		String entryName = "file_with_" + c + "_char";
+		File bundlesDirectory = OSGiTestsActivator.getContext().getDataFile(getName());
+		bundlesDirectory.mkdirs();
+		Map<String, String> headers = new HashMap<String, String>();
+		headers.put(Constants.BUNDLE_MANIFESTVERSION, "2");
+		headers.put(Constants.BUNDLE_SYMBOLICNAME, getName());
+		Map<String, String> entry = Collections.singletonMap(entryName, "value");
+
+		File testBundleJarFile = SystemBundleTests.createBundle(bundlesDirectory, getName(), headers, entry);
+		Bundle testBundle = getContext().installBundle(getName(), new FileInputStream(testBundleJarFile));
+		URL entryURL = testBundle.getEntry(entryName);
+		assertNotNull("Entry not found.", entryURL);
+		InputStream is = entryURL.openStream();
+		is.close();
+
+		String encodeEntry = URLEncoder.encode(entryName, "UTF-8");
+		String urlString = entryURL.toExternalForm();
+		urlString = urlString.substring(0, urlString.indexOf(entryName)) + encodeEntry;
+		URL encodedURL = new URL(urlString);
+		is = encodedURL.openStream();
+		is.close();
+	}
 }
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 7ff00b5..89d0aa3 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
@@ -2737,7 +2737,7 @@
 		return bundles;
 	}
 
-	private static File createBundle(File outputDir, String id, boolean emptyManifest, boolean dirBundle) throws IOException {
+	static File createBundle(File outputDir, String id, boolean emptyManifest, boolean dirBundle) throws IOException {
 		File file = new File(outputDir, "bundle" + id + (dirBundle ? "" : ".jar")); //$NON-NLS-1$ //$NON-NLS-2$
 		if (!dirBundle) {
 			JarOutputStream jos = new JarOutputStream(new FileOutputStream(file), createManifest(id, emptyManifest));
@@ -2987,6 +2987,45 @@
 		equinox.stop();
 	}
 
+	public void testConfigPercentChar() throws BundleException, IOException {
+		doTestConfigSpecialChar('%');
+	}
+
+	public void testConfigSpaceChar() throws BundleException, IOException {
+		doTestConfigSpecialChar(' ');
+	}
+
+	public void testConfigPlusChar() throws BundleException, IOException {
+		doTestConfigSpecialChar('+');
+	}
+
+	private void doTestConfigSpecialChar(char c) throws BundleException, IOException {
+		File config = OSGiTestsActivator.getContext().getDataFile(getName() + c + "config");
+		config.mkdirs();
+		// create a config.ini with some system property substitutes
+		Properties configIni = new Properties();
+		configIni.setProperty("test.config", getName());
+		configIni.store(new FileOutputStream(new File(config, "config.ini")), "Test config.ini");
+
+		Map<String, Object> configuration = new HashMap<String, Object>();
+		configuration.put(Constants.FRAMEWORK_STORAGE, config.getAbsolutePath());
+		Equinox equinox = new Equinox(configuration);
+		equinox.init();
+
+		BundleContext systemContext = equinox.getBundleContext();
+		// check for substitution
+		assertEquals("Wrong value for test.config", getName(), systemContext.getProperty("test.config"));
+
+		equinox.stop();
+		try {
+			equinox.waitForStop(5000);
+		} catch (InterruptedException e) {
+			Thread.currentThread().interrupt();
+			fail("Unexpected interruption.", e);
+		}
+
+	}
+
 	void checkActiveThreadType(Equinox equinox, boolean expectIsDeamon) {
 		String uuid = equinox.getBundleContext().getProperty(Constants.FRAMEWORK_UUID);
 		ThreadGroup topGroup = Thread.currentThread().getThreadGroup();
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/adaptor/EclipseStarter.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/adaptor/EclipseStarter.java
index 60b24eb..068bf39 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/adaptor/EclipseStarter.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/core/runtime/adaptor/EclipseStarter.java
@@ -517,7 +517,7 @@
 			// if it is a reference URL then strip off the reference: and set base to the file:...
 			if (url.getProtocol().equals(REFERENCE_PROTOCOL)) {
 				reference = true;
-				String baseSpec = url.getFile();
+				String baseSpec = url.getPath();
 				if (baseSpec.startsWith(FILE_SCHEME)) {
 					File child = new File(baseSpec.substring(5));
 					baseURL = child.isAbsolute() ? child.toURL() : new File(parent, child.getPath()).toURL();
@@ -525,7 +525,7 @@
 					baseURL = createURL(baseSpec);
 			}
 
-			fileLocation = new File(baseURL.getFile());
+			fileLocation = new File(baseURL.getPath());
 			// if the location is relative, prefix it with the parent
 			if (!fileLocation.isAbsolute())
 				fileLocation = new File(parent, fileLocation.toString());
@@ -542,7 +542,7 @@
 
 		// finally we have something worth trying	
 		try {
-			URLConnection result = url.openConnection();
+			URLConnection result = LocationHelper.getConnection(url);
 			result.connect();
 			return url;
 		} catch (IOException e) {
@@ -886,7 +886,7 @@
 		URL url = LocationHelper.buildURL(urlSpec, false);
 		if (url == null)
 			return null;
-		File fwkFile = new File(url.getFile());
+		File fwkFile = LocationHelper.decodePath(new File(url.getPath()));
 		fwkFile = new File(fwkFile.getAbsolutePath());
 		fwkFile = new File(fwkFile.getParent());
 		return fwkFile.getAbsolutePath();
@@ -902,7 +902,7 @@
 		URL url = cs.getLocation();
 		if (url == null)
 			return null;
-		String result = url.getFile();
+		String result = url.getPath();
 		if (File.separatorChar == '\\') {
 			// in case on windows the \ is used
 			result = result.replace('\\', '/');
@@ -969,7 +969,7 @@
 			try {
 				// don't need to install if it is already installed
 				if (osgiBundle == null) {
-					InputStream in = initialBundles[i].location.openStream();
+					InputStream in = LocationHelper.getStream(initialBundles[i].location);
 					try {
 						osgiBundle = context.installBundle(initialBundles[i].locationString, in);
 					} catch (BundleException e) {
@@ -1202,14 +1202,7 @@
 		String[] candidates = searchCandidates.get(start);
 		if (candidates == null) {
 			File startFile = new File(start);
-			// Pre-check if file exists, if not, and it contains escape characters,
-			// try decoding the path
-			if (!startFile.exists() && start.indexOf('%') >= 0) {
-				String decodePath = EquinoxConfiguration.decode(start);
-				File f = new File(decodePath);
-				if (f.exists())
-					startFile = f;
-			}
+			startFile = LocationHelper.decodePath(startFile);
 			candidates = startFile.list();
 			if (candidates != null)
 				searchCandidates.put(start, candidates);
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/debug/FrameworkDebugOptions.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/debug/FrameworkDebugOptions.java
index e1f347b..c81dcb6 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/debug/FrameworkDebugOptions.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/debug/FrameworkDebugOptions.java
@@ -11,10 +11,10 @@
 package org.eclipse.osgi.internal.debug;
 
 import java.io.*;
-import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.*;
 import org.eclipse.osgi.internal.framework.EquinoxConfiguration;
+import org.eclipse.osgi.internal.location.LocationHelper;
 import org.eclipse.osgi.service.debug.*;
 import org.osgi.framework.*;
 import org.osgi.util.tracker.ServiceTracker;
@@ -76,14 +76,14 @@
 				userDir += "/"; //$NON-NLS-1$
 			debugOptionsFilename = new File(userDir, OPTIONS).toString();
 		}
-		optionsFile = buildURL(debugOptionsFilename, false);
+		optionsFile = LocationHelper.buildURL(debugOptionsFilename, false);
 		if (optionsFile == null) {
 			System.out.println("Unable to construct URL for options file: " + debugOptionsFilename); //$NON-NLS-1$
 			return;
 		}
 		System.out.print("Debug options:\n    " + optionsFile.toExternalForm()); //$NON-NLS-1$
 		try {
-			InputStream input = optionsFile.openStream();
+			InputStream input = LocationHelper.getStream(optionsFile);
 			try {
 				options.load(input);
 				System.out.println(" loaded"); //$NON-NLS-1$
@@ -114,36 +114,6 @@
 		this.context = null;
 	}
 
-	@SuppressWarnings("deprecation")
-	private static URL buildURL(String spec, boolean trailingSlash) {
-		if (spec == null)
-			return null;
-		boolean isFile = spec.startsWith("file:"); //$NON-NLS-1$
-		try {
-			if (isFile)
-				return adjustTrailingSlash(new File(spec.substring(5)).toURL(), trailingSlash);
-			return new URL(spec);
-		} catch (MalformedURLException e) {
-			// if we failed and it is a file spec, there is nothing more we can do
-			// otherwise, try to make the spec into a file URL.
-			if (isFile)
-				return null;
-			try {
-				return adjustTrailingSlash(new File(spec).toURL(), trailingSlash);
-			} catch (MalformedURLException e1) {
-				return null;
-			}
-		}
-	}
-
-	private static URL adjustTrailingSlash(URL url, boolean trailingSlash) throws MalformedURLException {
-		String file = url.getFile();
-		if (trailingSlash == (file.endsWith("/"))) //$NON-NLS-1$
-			return url;
-		file = trailingSlash ? file + "/" : file.substring(0, file.length() - 1); //$NON-NLS-1$
-		return new URL(url.getProtocol(), url.getHost(), file);
-	}
-
 	/**
 	 * @see DebugOptions#getBooleanOption(String, boolean)
 	 */
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/framework/EquinoxConfiguration.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/framework/EquinoxConfiguration.java
index 54327d4..7283c00 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/framework/EquinoxConfiguration.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/framework/EquinoxConfiguration.java
@@ -34,6 +34,7 @@
 import org.eclipse.osgi.internal.debug.FrameworkDebugOptions;
 import org.eclipse.osgi.internal.hookregistry.HookRegistry;
 import org.eclipse.osgi.internal.location.EquinoxLocations;
+import org.eclipse.osgi.internal.location.LocationHelper;
 import org.eclipse.osgi.internal.messages.Msg;
 import org.eclipse.osgi.service.datalocation.Location;
 import org.eclipse.osgi.service.debug.DebugOptions;
@@ -322,7 +323,7 @@
 			if (location == null)
 				return result;
 			try {
-				InputStream in = location.openStream();
+				InputStream in = LocationHelper.getStream(location);
 				try {
 					result.load(in);
 				} finally {
@@ -525,13 +526,13 @@
 				URL location = new URL(osgiDev);
 
 				if ("file".equals(location.getProtocol())) { //$NON-NLS-1$
-					f = new File(location.getFile());
+					f = LocationHelper.decodePath(new File(location.getPath()));
 					devLastModified = f.lastModified();
 				}
 
 				// Check the osgi.dev property to see if dev classpath entries have been defined.
 				try {
-					loadDevProperties(location.openStream());
+					loadDevProperties(LocationHelper.getStream(location));
 					devMode = true;
 				} catch (IOException e) {
 					// TODO consider logging
@@ -903,13 +904,13 @@
 				setConfiguration(PROP_FRAMEWORK, externalForm);
 			}
 			if (getConfiguration(EquinoxLocations.PROP_INSTALL_AREA) == null) {
-				String filePart = getFrameworkPath(url.getFile(), true);
+				String filePart = getFrameworkPath(url.getPath(), true);
 				setConfiguration(EquinoxLocations.PROP_INSTALL_AREA, filePart);
 			}
 		}
 		// always decode these properties
-		setConfiguration(PROP_FRAMEWORK, decode(getConfiguration(PROP_FRAMEWORK)));
-		setConfiguration(EquinoxLocations.PROP_INSTALL_AREA, decode(getConfiguration(EquinoxLocations.PROP_INSTALL_AREA)));
+		setConfiguration(PROP_FRAMEWORK, LocationHelper.decode(getConfiguration(PROP_FRAMEWORK), true));
+		setConfiguration(EquinoxLocations.PROP_INSTALL_AREA, LocationHelper.decode(getConfiguration(EquinoxLocations.PROP_INSTALL_AREA), true));
 
 		setConfiguration(FRAMEWORK_VENDOR, ECLIPSE_FRAMEWORK_VENDOR);
 		String value = getConfiguration(FRAMEWORK_PROCESSOR);
@@ -1072,30 +1073,6 @@
 		}
 	}
 
-	public static String decode(String urlString) {
-		//first encode '+' characters, because URLDecoder incorrectly converts 
-		//them to spaces on certain class library implementations.
-		if (urlString.indexOf('+') >= 0) {
-			int len = urlString.length();
-			StringBuffer buf = new StringBuffer(len);
-			for (int i = 0; i < len; i++) {
-				char c = urlString.charAt(i);
-				if (c == '+')
-					buf.append("%2B"); //$NON-NLS-1$
-				else
-					buf.append(c);
-			}
-			urlString = buf.toString();
-		}
-		try {
-			return URLDecoder.decode(urlString, "UTF-8"); //$NON-NLS-1$
-		} catch (UnsupportedEncodingException e) {
-			// Tried but failed
-			// TODO should we throw runtime exception here?
-			return urlString;
-		}
-	}
-
 	public String substituteVars(String path) {
 		return substituteVars(path, false);
 	}
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/location/BasicLocation.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/location/BasicLocation.java
index d8519ac..bd56fa4 100755
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/location/BasicLocation.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/location/BasicLocation.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2004, 2015 IBM Corporation and others.
+ * Copyright (c) 2004, 2016 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -114,7 +114,8 @@
 		File file = null;
 		if (value.getProtocol().equalsIgnoreCase("file")) { //$NON-NLS-1$
 			try {
-				String basePath = new File(value.getFile()).getCanonicalPath();
+				File f = LocationHelper.decodePath(new File(value.getPath()));
+				String basePath = f.getCanonicalPath();
 				value = LocationHelper.buildURL("file:" + basePath, true); //$NON-NLS-1$
 			} catch (IOException e) {
 				// do nothing just use the original value
@@ -124,10 +125,10 @@
 				if (givenLockFile.isAbsolute()) {
 					file = givenLockFile;
 				} else {
-					file = new File(value.getFile(), lockFilePath);
+					file = new File(value.getPath(), lockFilePath);
 				}
 			} else {
-				file = new File(value.getFile(), DEFAULT_LOCK_FILENAME);
+				file = new File(value.getPath(), DEFAULT_LOCK_FILENAME);
 			}
 		}
 		lock = lock && !isReadOnly;
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/location/EquinoxLocations.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/location/EquinoxLocations.java
index e5cf6db..8d623a7 100755
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/location/EquinoxLocations.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/location/EquinoxLocations.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2004, 2015 IBM Corporation and others.
+ * Copyright (c) 2004, 2016 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -251,7 +251,7 @@
 
 		URL installURL = computeInstallConfigurationLocation();
 		if (installURL != null && "file".equals(installURL.getProtocol())) { //$NON-NLS-1$
-			File installDir = new File(installURL.getFile());
+			File installDir = new File(installURL.getPath());
 			File defaultConfigDir = new File(installDir, CONFIG_DIR);
 			if (!defaultConfigDir.exists())
 				defaultConfigDir.mkdirs();
@@ -264,7 +264,7 @@
 
 	private static boolean canWrite(URL location) {
 		if (location != null && "file".equals(location.getProtocol())) { //$NON-NLS-1$
-			File locationDir = new File(location.getFile());
+			File locationDir = new File(location.getPath());
 			if (!locationDir.exists())
 				locationDir.mkdirs();
 			if (locationDir.exists() && StorageUtil.canWrite(locationDir))
@@ -282,7 +282,7 @@
 		URL installURL = buildURL(installProperty, true);
 		if (installURL == null)
 			return null;
-		File installDir = new File(installURL.getFile());
+		File installDir = new File(installURL.getPath());
 		String installDirHash = getInstallDirHash();
 
 		String appName = "." + ECLIPSE; //$NON-NLS-1$
@@ -322,7 +322,7 @@
 		URL installURL = buildURL(installProperty, true);
 		if (installURL == null)
 			return ""; //$NON-NLS-1$
-		File installDir = new File(installURL.getFile());
+		File installDir = new File(installURL.getPath());
 		int hashCode;
 		try {
 			hashCode = installDir.getCanonicalPath().hashCode();
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/log/EquinoxLogServices.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/log/EquinoxLogServices.java
index c97cdde..de64ca6 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/log/EquinoxLogServices.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/log/EquinoxLogServices.java
@@ -48,7 +48,7 @@
 			File configAreaDirectory = null;
 			if (configuration != null)
 				// TODO assumes the URL is a file: url
-				configAreaDirectory = new File(configuration.getURL().getFile());
+				configAreaDirectory = new File(configuration.getURL().getPath());
 
 			if (configAreaDirectory != null) {
 				logFile = new File(configAreaDirectory, logFilePath);
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/Storage.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/Storage.java
index 0bcbfdd..702c800 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/Storage.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/Storage.java
@@ -115,7 +115,7 @@
 			osgiParentLocation = parentConfigLocation.createLocation(null, parentConfigLocation.getDataArea(EquinoxContainer.NAME), true);
 		}
 		this.osgiLocation = configLocation.createLocation(osgiParentLocation, configLocation.getDataArea(EquinoxContainer.NAME), configLocation.isReadOnly());
-		this.childRoot = new File(osgiLocation.getURL().getFile());
+		this.childRoot = new File(osgiLocation.getURL().getPath());
 
 		if (Boolean.valueOf(container.getConfiguration().getConfiguration(EquinoxConfiguration.PROP_CLEAN)).booleanValue()) {
 			cleanOSGiStorage(osgiLocation, childRoot);
@@ -124,7 +124,7 @@
 			this.childRoot.mkdirs();
 		}
 		Location parent = this.osgiLocation.getParentLocation();
-		parentRoot = parent == null ? null : new File(parent.getURL().getFile());
+		parentRoot = parent == null ? null : new File(parent.getURL().getPath());
 
 		if (container.getConfiguration().getConfiguration(Constants.FRAMEWORK_STORAGE) == null) {
 			// Set the derived value if not already set as part of configuration.
@@ -466,13 +466,13 @@
 
 	private URLConnection getContentConnection(final String spec) throws IOException {
 		if (System.getSecurityManager() == null) {
-			return createURL(spec).openConnection();
+			return LocationHelper.getConnection(createURL(spec));
 		}
 		try {
 			return AccessController.doPrivileged(new PrivilegedExceptionAction<URLConnection>() {
 				@Override
 				public URLConnection run() throws IOException {
-					return createURL(spec).openConnection();
+					return LocationHelper.getConnection(createURL(spec));
 				}
 			});
 		} catch (PrivilegedActionException e) {
@@ -855,6 +855,7 @@
 
 			if ("file".equals(protocol)) { //$NON-NLS-1$
 				File inFile = new File(sourceURL.getPath());
+				inFile = LocationHelper.decodePath(inFile);
 				if (inFile.isDirectory()) {
 					// need to delete the outFile because it is not a directory
 					outFile.delete();
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/url/bundleentry/Handler.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/url/bundleentry/Handler.java
index aff50f1..3565735 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/url/bundleentry/Handler.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/url/bundleentry/Handler.java
@@ -15,6 +15,7 @@
 import java.io.IOException;
 import java.net.URL;
 import org.eclipse.osgi.container.*;
+import org.eclipse.osgi.internal.location.LocationHelper;
 import org.eclipse.osgi.storage.BundleInfo;
 import org.eclipse.osgi.storage.bundlefile.BundleEntry;
 import org.eclipse.osgi.storage.url.BundleResourceHandler;
@@ -33,8 +34,20 @@
 		ModuleRevision revision = module.getCurrentRevision();
 		BundleInfo.Generation revisionInfo = (BundleInfo.Generation) revision.getRevisionInfo();
 		BundleEntry entry = revisionInfo == null ? null : revisionInfo.getBundleFile().getEntry(url.getPath());
-		if (entry == null)
+		if (entry == null) {
+			String path = url.getPath();
+			if (revisionInfo != null && (path.indexOf('%') >= 0 || path.indexOf('+') >= 0)) {
+				entry = revisionInfo.getBundleFile().getEntry(LocationHelper.decode(path, true));
+				if (entry != null) {
+					return entry;
+				}
+				entry = revisionInfo.getBundleFile().getEntry(LocationHelper.decode(path, false));
+				if (entry != null) {
+					return entry;
+				}
+			}
 			throw new FileNotFoundException(url.getPath());
+		}
 		return entry;
 	}
 }
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/url/reference/ReferenceURLConnection.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/url/reference/ReferenceURLConnection.java
index 82c847d..c776c8b 100644
--- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/url/reference/ReferenceURLConnection.java
+++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/storage/url/reference/ReferenceURLConnection.java
@@ -15,7 +15,7 @@
 import java.net.URL;
 import java.net.URLConnection;
 import org.eclipse.osgi.framework.util.FilePath;
-import org.eclipse.osgi.internal.framework.EquinoxConfiguration;
+import org.eclipse.osgi.internal.location.LocationHelper;
 
 /**
  * URLConnection for the reference protocol.
@@ -45,14 +45,7 @@
 					file = makeAbsolute(installPath, file);
 			}
 
-			// Pre-check if file exists, if not, and it contains escape characters,
-			// try decoding the absolute path generated by makeAbsolute
-			if (!file.exists() && path.indexOf('%') >= 0) {
-				String decodePath = EquinoxConfiguration.decode(file.getAbsolutePath());
-				File f = new File(decodePath);
-				if (f.exists())
-					file = f;
-			}
+			file = LocationHelper.decodePath(file);
 
 			ref = file.toURL();
 			checkRead(file);
diff --git a/bundles/org.eclipse.osgi/supplement/src/org/eclipse/osgi/internal/location/LocationHelper.java b/bundles/org.eclipse.osgi/supplement/src/org/eclipse/osgi/internal/location/LocationHelper.java
index 75da9a9..73050d4 100644
--- a/bundles/org.eclipse.osgi/supplement/src/org/eclipse/osgi/internal/location/LocationHelper.java
+++ b/bundles/org.eclipse.osgi/supplement/src/org/eclipse/osgi/internal/location/LocationHelper.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2006, 2013 IBM Corporation and others.
+ * Copyright (c) 2006, 2016 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials 
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -10,9 +10,8 @@
  *******************************************************************************/
 package org.eclipse.osgi.internal.location;
 
-import java.io.File;
-import java.net.MalformedURLException;
-import java.net.URL;
+import java.io.*;
+import java.net.*;
 import org.eclipse.osgi.internal.location.Locker.MockLocker;
 
 /**
@@ -55,7 +54,7 @@
 	}
 
 	private static URL adjustTrailingSlash(URL url, boolean trailingSlash) throws MalformedURLException {
-		String file = url.getFile();
+		String file = url.getPath();
 		if (trailingSlash == (file.endsWith("/"))) //$NON-NLS-1$
 			return url;
 		file = trailingSlash ? file + "/" : file.substring(0, file.length() - 1); //$NON-NLS-1$
@@ -80,4 +79,76 @@
 		//	Backup case if an invalid value has been specified
 		return new Locker_JavaNio(lock, debug);
 	}
+
+	public static InputStream getStream(URL location) throws IOException {
+		if ("file".equalsIgnoreCase(location.getProtocol())) { //$NON-NLS-1$
+			// this is done to handle URLs with invalid syntax in the path
+			File f = new File(location.getPath());
+			if (f.exists()) {
+				return new FileInputStream(f);
+			}
+		}
+		return location.openStream();
+	}
+
+	public static URLConnection getConnection(URL url) throws IOException {
+		if ("file".equalsIgnoreCase(url.getProtocol())) { //$NON-NLS-1$
+			try {
+				return url.openConnection();
+			} catch (IllegalArgumentException e) {
+				// this is done to handle URLs with invalid syntax in the path for URIs
+				File f = new File(url.getPath());
+				if (f.exists()) {
+					return f.toURI().toURL().openConnection();
+				}
+			}
+		}
+		return url.openConnection();
+	}
+
+	public static File decodePath(File file) {
+		// Pre-check if file exists, if not, and it contains escape characters,
+		// try decoding the absolute path generated by makeAbsolute
+		if (!file.exists() && (file.getPath().indexOf('%') >= 0 || file.getPath().indexOf('+') >= 0)) {
+			String absolute = file.getAbsolutePath();
+			String decodePath = LocationHelper.decode(absolute, true);
+			File f = new File(decodePath);
+			if (f.exists()) {
+				return f;
+			}
+			decodePath = LocationHelper.decode(absolute, false);
+			f = new File(decodePath);
+			if (f.exists()) {
+				return f;
+			}
+		}
+		return file;
+	}
+
+	public static String decode(String urlString, boolean plusEncoded) {
+		//first encode '+' characters, because URLDecoder incorrectly converts 
+		//them to spaces on certain class library implementations.
+		if (plusEncoded && urlString.indexOf('+') >= 0) {
+			int len = urlString.length();
+			StringBuffer buf = new StringBuffer(len);
+			for (int i = 0; i < len; i++) {
+				char c = urlString.charAt(i);
+				if (c == '+')
+					buf.append("%2B"); //$NON-NLS-1$
+				else
+					buf.append(c);
+			}
+			urlString = buf.toString();
+		}
+		try {
+			return URLDecoder.decode(urlString, "UTF-8"); //$NON-NLS-1$
+		} catch (UnsupportedEncodingException e) {
+			// Tried but failed
+			// TODO should we throw runtime exception here?
+			return urlString;
+		} catch (RuntimeException e) {
+			// May have illegal characters for decoding
+			return urlString;
+		}
+	}
 }