blob: 39b48338826ab105bbc8dfaa6a2116472b2c4140 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2015 SAP SE
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php.
* You may elect to redistribute this code under either of these licenses.
*
* Contributors:
* Violeta Georgieva - initial contribution
*******************************************************************************/
package org.eclipse.gemini.web.tomcat.internal.bundleresources;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.jar.Manifest;
import org.apache.catalina.WebResourceRoot;
import org.apache.catalina.webresources.AbstractResource;
import org.apache.juli.logging.Log;
import org.eclipse.gemini.web.tomcat.internal.support.BundleFileResolver;
import org.eclipse.gemini.web.tomcat.internal.support.BundleFileResolverFactory;
import org.osgi.framework.Bundle;
import org.osgi.framework.wiring.BundleRevision;
import org.osgi.framework.wiring.BundleWire;
import org.osgi.framework.wiring.BundleWiring;
final class BundleWebResource extends AbstractResource {
private static final String WEB_INF_DOT = "WEB-INF.";
private static final String META_INF_DOT = "META-INF.";
private static final String META_INF = "META-INF";
private static final String OSGI_INF_DOT = "OSGI-INF.";
private static final String OSGI_OPT_DOT = "OSGI-OPT.";
private static final String PATH_SEPARATOR = "/";
private static final String DOT = ".";
private static final long TIME_NOT_SET = -1L;
private static final int CREATION_DATE_UNKNOWN = 0;
private static final long CONTENT_LENGTH_NOT_SET = -1;
private final WebResourceRoot root;
private final String path;
private final Bundle bundle;
private final List<Bundle> fragments;
private final BundleFileResolver bundleFileResolver = BundleFileResolverFactory.createBundleFileResolver();
private final boolean checkEntryPath;
private String bundleLocationCanonicalPath;
private boolean isBundleLocationDirectory;
private long lastModified = TIME_NOT_SET;
private long creation = TIME_NOT_SET;
private long contentLength = CONTENT_LENGTH_NOT_SET;
private URL url;
BundleWebResource(Bundle bundle, WebResourceRoot root) {
super(root, "");
this.root = root;
this.path = "";
this.bundle = bundle;
this.fragments = getFragments(bundle);
this.checkEntryPath = checkEntryPath();
File bundleLocation = this.bundleFileResolver.resolve(bundle);
if (bundleLocation != null) {
try {
this.bundleLocationCanonicalPath = bundleLocation.getCanonicalPath();
} catch (IOException e) {
}
if (bundleLocation.isDirectory()) {
this.isBundleLocationDirectory = true;
}
}
}
private BundleWebResource(Bundle bundle, WebResourceRoot root, List<Bundle> fragments, String path, boolean checkEntryPath,
String bundleLocationCanonicalPath, boolean isBundleLocationDirectory) {
super(root, path);
this.root = root;
this.path = path;
this.bundle = bundle;
this.fragments = fragments;
this.checkEntryPath = checkEntryPath;
this.bundleLocationCanonicalPath = bundleLocationCanonicalPath;
this.isBundleLocationDirectory = isBundleLocationDirectory;
}
Bundle getBundle() {
return this.bundle;
}
List<BundleWebResource> list() {
List<BundleWebResource> entries = new ArrayList<>();
Set<String> paths = getEntryPathsFromBundle();
if (paths != null) {
Iterator<String> iterator = paths.iterator();
while (iterator.hasNext()) {
String subPath = iterator.next();
entries.add(createBundleEntry(subPath));
}
}
return entries;
}
private BundleWebResource createBundleEntry(String path) {
return new BundleWebResource(this.bundle, this.root, this.fragments, path, this.checkEntryPath, this.bundleLocationCanonicalPath,
this.isBundleLocationDirectory);
}
private Set<String> getEntryPathsFromBundle() {
Set<String> paths = getEntryPathsFromBundle(this.bundle);
for (int i = 0; i < this.fragments.size(); i++) {
paths.addAll(getEntryPathsFromBundle(this.fragments.get(i)));
}
if (paths.isEmpty()) {
return null;
}
return paths;
}
private Set<String> getEntryPathsFromBundle(Bundle bundle) {
final Enumeration<String> ep = bundle.getEntryPaths(this.path);
Set<String> paths = new HashSet<String>();
if (ep != null) {
while (ep.hasMoreElements()) {
paths.add(ep.nextElement());
}
}
return paths;
}
Entry<BundleWebResource, URL> getEntry(String subPath) {
String finalPath = this.path + subPath;
URL entryURL = getEntryFromBundle(finalPath);
if (entryURL != null) {
Map<BundleWebResource, URL> result = new HashMap<>();
result.put(createBundleEntry(finalPath), entryURL);
return result.entrySet().iterator().next();
} else {
return null;
}
}
/**
* This method has been generalized from this.bundle.getEntry(path) to allow entries to be supplied by a fragment.
*/
private URL getEntryFromBundle(String path) {
if (this.checkEntryPath
&& (checkNotAttemptingToAccess(path, META_INF_DOT) || checkNotAttemptingToAccess(path, WEB_INF_DOT)
|| checkNotAttemptingToAccess(path, OSGI_INF_DOT) || checkNotAttemptingToAccess(path, OSGI_OPT_DOT))) {
return null;
}
if (path.endsWith(PATH_SEPARATOR) || path.length() == 0) {
return this.bundle.getEntry(path);
}
String searchPath;
String searchFile;
int lastSlashIndex = path.lastIndexOf(PATH_SEPARATOR);
if (lastSlashIndex == -1) {
searchPath = PATH_SEPARATOR;
searchFile = path;
} else {
searchPath = path.substring(0, lastSlashIndex);
searchFile = path.substring(lastSlashIndex + 1);
}
if (searchFile.equals(DOT)) {
return this.bundle.getEntry(path.substring(0, path.length() - 1));
}
Enumeration<URL> entries = this.bundle.findEntries(searchPath, searchFile, false);
if (entries != null) {
if (entries.hasMoreElements()) {
return entries.nextElement();
}
}
return null;
}
private boolean checkNotAttemptingToAccess(String path, String prefix) {
return path.startsWith(prefix + PATH_SEPARATOR) || path.startsWith(PATH_SEPARATOR + prefix + PATH_SEPARATOR)
|| path.startsWith(DOT + PATH_SEPARATOR + prefix + PATH_SEPARATOR);
}
@Override
public String getName() {
String name = this.path;
if (name.endsWith(PATH_SEPARATOR)) {
name = name.substring(0, this.path.length() - 1);
}
int index = name.lastIndexOf(PATH_SEPARATOR);
if (index > -1) {
name = name.substring(index + 1);
}
if (name.length() == 0) {
return PATH_SEPARATOR;
} else {
return name;
}
}
@Override
public URL getURL() {
if (this.url == null) {
this.url = getEntryFromBundle(this.path);
}
return this.url;
}
void setURL(URL url) {
this.url = url;
}
@Override
public String toString() {
return String.format("BundleWebResource [bundle=%s,path=%s]", this.bundle, this.path);
}
/**
* Returns the bundle entry size. If the BundleFileResolver is EquinoxBundleFileResolver then we will use equinox
* specific functionality to get BundleEntry and its size. If the BundleFileResolver is NoOpBundleFileResolver we
* will use URLConnection.getContentLength(). Note: URLConnection.getContentLength() returns "int", if the bundle
* entry size exceeds max "int", then the content length will not be correct.
*
* @return the bundle entry size
*/
private long determineContentLength(URLConnection urlConnection) {
long size = this.bundleFileResolver.resolveBundleEntrySize(this.bundle, this.path);
if (size == -1 && urlConnection != null) {
size = urlConnection.getContentLength();
}
return size;
}
private List<Bundle> getFragments(Bundle bundle) {
List<Bundle> fragments = new ArrayList<Bundle>();
BundleRevision bundleRevision = bundle.adapt(BundleRevision.class);
if (bundleRevision != null) {
BundleWiring bundleWiring = bundleRevision.getWiring();
List<BundleWire> bundleWires = bundleWiring.getProvidedWires(BundleRevision.HOST_NAMESPACE);
for (int i = 0; bundleWires != null && i < bundleWires.size(); i++) {
fragments.add(bundleWires.get(i).getRequirerWiring().getRevision().getBundle());
}
}
return fragments;
}
private boolean checkEntryPath() {
try {
return Paths.get(META_INF).toRealPath().equals(Paths.get(META_INF_DOT).toRealPath());
} catch (IOException e) {
return true;
}
}
@Override
public boolean canRead() {
return true;
}
@Override
public boolean delete() {
return false;
}
@Override
public boolean exists() {
return true;
}
@Override
public String getCanonicalPath() {
if (isBundleLocationDirectory()) {
boolean checkInBundleLocation = this.path != null && this.path.indexOf("..") >= 0;
String bundleLocationCanonicalPath = getBundleLocationCanonicalPath();
Path entry = Paths.get(bundleLocationCanonicalPath, this.path);
if (checkInBundleLocation) {
try {
if (!entry.toRealPath().startsWith(bundleLocationCanonicalPath)) {
return null;
}
} catch (IOException e) {
return null;
}
}
return entry.toAbsolutePath().toString();
}
return null;
}
private String getBundleLocationCanonicalPath() {
return this.bundleLocationCanonicalPath;
}
private boolean isBundleLocationDirectory() {
return this.isBundleLocationDirectory;
}
@Override
public Certificate[] getCertificates() {
return null;
}
@Override
public URL getCodeBase() {
return getURL();
}
@Override
public byte[] getContent() {
long len = getContentLength();
if (len > Integer.MAX_VALUE) {
// Can't create an array that big
throw new ArrayIndexOutOfBoundsException("Unable to return [" + getWebappPath() + "] as a byte array since the resource is ["
+ Long.valueOf(len) + "] bytes in size which is larger than the maximum size of a byte array.");
}
int size = (int) len;
byte[] result = new byte[size];
int pos = 0;
try (InputStream is = getURL().openStream()) {
while (pos < size) {
int n = is.read(result, pos, size - pos);
if (n < 0) {
break;
}
pos += n;
}
} catch (IOException ioe) {
}
return result;
}
@Override
public long getContentLength() {
return getContentLength(null);
}
private long getContentLength(URLConnection urlConnection) {
if (this.contentLength == CONTENT_LENGTH_NOT_SET) {
if (urlConnection == null) {
urlConnection = getURLConnection();
}
if (urlConnection != null) {
this.contentLength = determineContentLength(urlConnection);
}
}
return this.contentLength;
}
private URLConnection getURLConnection() {
try {
URL url = getURL();
if (url != null) {
return url.openConnection();
} else {
return null;
}
} catch (IOException e) {
return null;
}
}
@Override
public long getCreation() {
return getCreation(null, TIME_NOT_SET);
}
private long getCreation(URLConnection urlConnection, long lastModified) {
if (this.creation == TIME_NOT_SET) {
if (urlConnection == null) {
urlConnection = getURLConnection();
}
if (urlConnection != null) {
this.creation = urlConnection.getDate();
if (this.creation == CREATION_DATE_UNKNOWN) {
if (lastModified == TIME_NOT_SET) {
lastModified = urlConnection.getLastModified();
}
this.creation = lastModified;
}
}
}
return this.creation;
}
@Override
public long getLastModified() {
return getLastModified(null);
}
private long getLastModified(URLConnection urlConnection) {
if (this.lastModified == TIME_NOT_SET) {
if (urlConnection == null) {
urlConnection = getURLConnection();
}
if (urlConnection != null) {
this.lastModified = urlConnection.getLastModified();
}
}
return this.lastModified;
}
@Override
public Manifest getManifest() {
return null;
}
@Override
public boolean isDirectory() {
return getURL().getFile().endsWith(PATH_SEPARATOR);
}
@Override
public boolean isFile() {
return !getURL().getFile().endsWith(PATH_SEPARATOR);
}
@Override
public boolean isVirtual() {
return false;
}
@Override
protected InputStream doGetInputStream() {
try {
return getURL().openStream();
} catch (IOException e) {
return null;
}
}
@Override
protected Log getLog() {
return null;
}
Entry<BundleWebResource, URL> getNamedEntry(String name) {
checkCanLookup(name);
return getEntry(name);
}
private void checkCanLookup(String name) {
if (getBundle().getState() == Bundle.UNINSTALLED) {
throw new IllegalArgumentException("Resource not found [" + name + "].");
}
checkNotAttemptingToLookupFromProtectedLocation(name);
}
private void checkNotAttemptingToLookupFromProtectedLocation(String name) {
checkNotAttemptingToLookupFrom(name, "/OSGI-INF/");
checkNotAttemptingToLookupFrom(name, "/OSGI-OPT/");
}
private void checkNotAttemptingToLookupFrom(String name, String prefix) {
if (name.startsWith(prefix)) {
throw new IllegalArgumentException("Resource cannot be obtained from [" + prefix + "].");
}
}
}