| /******************************************************************************* |
| * 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 + "]."); |
| } |
| } |
| } |