blob: f33a490a93f52b2bd3a69755940740f228025fb4 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2013 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.osgi.storage.url;
import java.io.IOException;
import java.net.*;
import org.eclipse.osgi.container.Module;
import org.eclipse.osgi.container.ModuleContainer;
import org.eclipse.osgi.internal.messages.Msg;
import org.eclipse.osgi.storage.bundlefile.BundleEntry;
import org.eclipse.osgi.util.NLS;
import org.osgi.framework.AdminPermission;
import org.osgi.framework.Bundle;
/**
* URLStreamHandler the bundleentry and bundleresource protocols.
*/
public abstract class BundleResourceHandler extends URLStreamHandler {
// Bundle resource URL protocol
public static final String OSGI_RESOURCE_URL_PROTOCOL = "bundleresource"; //$NON-NLS-1$
// Bundle entry URL protocol
public static final String OSGI_ENTRY_URL_PROTOCOL = "bundleentry"; //$NON-NLS-1$
public static final String SECURITY_CHECKED = "SECURITY_CHECKED"; //$NON-NLS-1$
public static final String SECURITY_UNCHECKED = "SECURITY_UNCHECKED"; //$NON-NLS-1$
public static final String BID_FWKID_SEPARATOR = ".fwk"; //$NON-NLS-1$
protected final ModuleContainer container;
protected BundleEntry bundleEntry;
public BundleResourceHandler(ModuleContainer container, BundleEntry bundleEntry) {
this.container = container;
this.bundleEntry = bundleEntry;
}
/**
* Parse reference URL.
*/
protected void parseURL(URL url, String str, int start, int end) {
if (end < start)
return;
if (url.getPath() != null)
// A call to a URL constructor has been made that uses an authorized URL as its context.
// Null out bundleEntry because it will not be valid for the new path
bundleEntry = null;
String spec = ""; //$NON-NLS-1$
if (start < end)
spec = str.substring(start, end);
end -= start;
//Default is to use path and bundleId from context
String path = url.getPath();
String host = url.getHost();
int resIndex = url.getPort();
if (resIndex < 0) // -1 indicates port was not set; must default to 0
resIndex = 0;
int pathIdx = 0;
if (spec.startsWith("//")) { //$NON-NLS-1$
int bundleIdIdx = 2;
pathIdx = spec.indexOf('/', bundleIdIdx);
if (pathIdx == -1) {
pathIdx = end;
// Use default
path = ""; //$NON-NLS-1$
}
int bundleIdEnd = spec.indexOf(':', bundleIdIdx);
if (bundleIdEnd > pathIdx || bundleIdEnd == -1)
bundleIdEnd = pathIdx;
if (bundleIdEnd < pathIdx - 1)
try {
resIndex = Integer.parseInt(spec.substring(bundleIdEnd + 1, pathIdx));
} catch (NumberFormatException e) {
// do nothing; results in resIndex == 0
}
host = spec.substring(bundleIdIdx, bundleIdEnd);
}
if (pathIdx < end && spec.charAt(pathIdx) == '/')
path = spec.substring(pathIdx, end);
else if (end > pathIdx) {
if (path == null || path.equals("")) //$NON-NLS-1$
path = "/"; //$NON-NLS-1$
int last = path.lastIndexOf('/') + 1;
if (last == 0)
path = spec.substring(pathIdx, end);
else
path = path.substring(0, last) + spec.substring(pathIdx, end);
}
if (path == null)
path = ""; //$NON-NLS-1$
//modify path if there's any relative references
// see RFC2396 Section 5.2
// Note: For ".." references above the root the approach taken is removing them from the resolved path
if (path.endsWith("/.") || path.endsWith("/..")) //$NON-NLS-1$ //$NON-NLS-2$
path = path + '/';
int dotIndex;
while ((dotIndex = path.indexOf("/./")) >= 0) //$NON-NLS-1$
path = path.substring(0, dotIndex + 1) + path.substring(dotIndex + 3);
while ((dotIndex = path.indexOf("/../")) >= 0) { //$NON-NLS-1$
if (dotIndex != 0)
path = path.substring(0, path.lastIndexOf('/', dotIndex - 1)) + path.substring(dotIndex + 3);
else
path = path.substring(dotIndex + 3);
}
while ((dotIndex = path.indexOf("//")) >= 0) //$NON-NLS-1$
path = path.substring(0, dotIndex + 1) + path.substring(dotIndex + 2);
// Check the permission of the caller to see if they
// are allowed access to the resource.
String authorized = SECURITY_UNCHECKED;
long bundleId = getBundleID(host);
Module module = getModule(bundleId);
if (checkAuthorization(module))
authorized = SECURITY_CHECKED;
// Always force the use of the hash from the adaptor
host = Long.toString(bundleId) + BID_FWKID_SEPARATOR + Integer.toString(container.hashCode());
// Setting the authority portion of the URL to SECURITY_ATHORIZED
// ensures that this URL was created by using this parseURL
// method. The openConnection method will only open URLs
// that have the authority set to this.
setURL(url, url.getProtocol(), host, resIndex, authorized, null, path, null, url.getRef());
}
private Module getModule(long id) {
return container.getModule(id);
}
/**
* Establishes a connection to the resource specified by <code>URL</code>.
* Since different protocols may have unique ways of connecting, it must be
* overridden by the subclass.
*
* @return java.net.URLConnection
* @param url java.net.URL
*
* @exception IOException thrown if an IO error occurs during connection establishment
*/
protected URLConnection openConnection(URL url) throws IOException {
if (bundleEntry != null) // if the bundleEntry is not null then return quick
return (new BundleURLConnection(url, bundleEntry));
String host = url.getHost();
if (host == null) {
throw new IOException(NLS.bind(Msg.URL_NO_BUNDLE_ID, url.toExternalForm()));
}
long bundleID;
try {
bundleID = getBundleID(host);
} catch (NumberFormatException nfe) {
throw (MalformedURLException) new MalformedURLException(NLS.bind(Msg.URL_INVALID_BUNDLE_ID, host)).initCause(nfe);
}
Module module = getModule(bundleID);
if (module == null)
throw new IOException(NLS.bind(Msg.URL_NO_BUNDLE_FOUND, url.toExternalForm()));
// check to make sure that this URL was created using the
// parseURL method. This ensures the security check was done
// at URL construction.
if (!url.getAuthority().equals(SECURITY_CHECKED)) {
// No admin security check was made better check now.
checkAuthorization(module);
}
return (new BundleURLConnection(url, findBundleEntry(url, module)));
}
/**
* Finds the bundle entry for this protocal. This is handled
* differently for Bundle.gerResource() and Bundle.getEntry()
* because getResource uses the bundle classloader and getEntry
* only used the base bundle file.
* @param url The URL to find the entry for.
* @param module the module to find the entry for.
* @return the bundle entry
*/
abstract protected BundleEntry findBundleEntry(URL url, Module module) throws IOException;
/**
* Converts a bundle URL to a String.
*
* @param url the URL.
* @return a string representation of the URL.
*/
protected String toExternalForm(URL url) {
StringBuffer result = new StringBuffer(url.getProtocol());
result.append("://"); //$NON-NLS-1$
String host = url.getHost();
if ((host != null) && (host.length() > 0))
result.append(host);
int index = url.getPort();
if (index > 0)
result.append(':').append(index);
String path = url.getPath();
if (path != null) {
if ((path.length() > 0) && (path.charAt(0) != '/')) /* if name doesn't have a leading slash */
{
result.append("/"); //$NON-NLS-1$
}
result.append(path);
}
String ref = url.getRef();
if (ref != null && ref.length() > 0)
result.append('#').append(ref);
return (result.toString());
}
protected int hashCode(URL url) {
int hash = 0;
String protocol = url.getProtocol();
if (protocol != null)
hash += protocol.hashCode();
String host = url.getHost();
if (host != null)
hash += host.hashCode();
hash += url.getPort();
String path = url.getPath();
if (path != null)
hash += path.hashCode();
hash += container.hashCode();
return hash;
}
protected boolean equals(URL url1, URL url2) {
return sameFile(url1, url2);
}
protected synchronized InetAddress getHostAddress(URL url) {
return null;
}
protected boolean hostsEqual(URL url1, URL url2) {
String host1 = url1.getHost();
String host2 = url2.getHost();
if (host1 != null && host2 != null)
return host1.equalsIgnoreCase(host2);
return (host1 == null && host2 == null);
}
protected boolean sameFile(URL url1, URL url2) {
// do a hashcode test to allow each handler to check the adaptor first
if (url1.hashCode() != url2.hashCode())
return false;
String p1 = url1.getProtocol();
String p2 = url2.getProtocol();
if (!((p1 == p2) || (p1 != null && p1.equalsIgnoreCase(p2))))
return false;
if (!hostsEqual(url1, url2))
return false;
if (url1.getPort() != url2.getPort())
return false;
String path1 = url1.getPath();
String path2 = url2.getPath();
if (!((path1 == path2) || (path1 != null && path1.equals(path2))))
return false;
return true;
// note that the authority is not checked here because it can be different for two
// URLs depending on how they were constructed.
}
protected boolean checkAuthorization(Module module) {
SecurityManager sm = System.getSecurityManager();
if (sm == null)
return true;
Bundle bundle = module == null ? null : module.getBundle();
if (bundle == null)
return false;
sm.checkPermission(new AdminPermission(bundle, AdminPermission.RESOURCE));
return true;
}
private long getBundleID(String host) {
int dotIndex = host.indexOf('.');
return (dotIndex >= 0 && dotIndex < host.length() - 1) ? Long.parseLong(host.substring(0, dotIndex)) : Long.parseLong(host);
}
}