| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.catalina.webresources; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.security.cert.Certificate; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import java.util.jar.JarEntry; |
| import java.util.jar.Manifest; |
| |
| public abstract class AbstractArchiveResource extends AbstractResource { |
| |
| private final AbstractArchiveResourceSet archiveResourceSet; |
| private final String baseUrl; |
| private final JarEntry resource; |
| private final String codeBaseUrl; |
| private final String name; |
| private boolean readCerts = false; |
| private Certificate[] certificates; |
| |
| protected AbstractArchiveResource(AbstractArchiveResourceSet archiveResourceSet, |
| String webAppPath, String baseUrl, JarEntry jarEntry, String codeBaseUrl) { |
| super(archiveResourceSet.getRoot(), webAppPath); |
| this.archiveResourceSet = archiveResourceSet; |
| this.baseUrl = baseUrl; |
| this.resource = jarEntry; |
| this.codeBaseUrl = codeBaseUrl; |
| |
| String resourceName = resource.getName(); |
| if (resourceName.charAt(resourceName.length() - 1) == '/') { |
| resourceName = resourceName.substring(0, resourceName.length() - 1); |
| } |
| String internalPath = archiveResourceSet.getInternalPath(); |
| if (internalPath.length() > 0 && resourceName.equals( |
| internalPath.subSequence(1, internalPath.length()))) { |
| name = ""; |
| } else { |
| int index = resourceName.lastIndexOf('/'); |
| if (index == -1) { |
| name = resourceName; |
| } else { |
| name = resourceName.substring(index + 1); |
| } |
| } |
| } |
| |
| protected AbstractArchiveResourceSet getArchiveResourceSet() { |
| return archiveResourceSet; |
| } |
| |
| protected final String getBase() { |
| return archiveResourceSet.getBase(); |
| } |
| |
| protected final String getBaseUrl() { |
| return baseUrl; |
| } |
| |
| protected final JarEntry getResource() { |
| return resource; |
| } |
| |
| @Override |
| public long getLastModified() { |
| return resource.getTime(); |
| } |
| |
| @Override |
| public boolean exists() { |
| return true; |
| } |
| |
| @Override |
| public boolean isVirtual() { |
| return false; |
| } |
| |
| @Override |
| public boolean isDirectory() { |
| return resource.isDirectory(); |
| } |
| |
| @Override |
| public boolean isFile() { |
| return !resource.isDirectory(); |
| } |
| |
| @Override |
| public boolean delete() { |
| return false; |
| } |
| |
| @Override |
| public String getName() { |
| return name; |
| } |
| |
| @Override |
| public long getContentLength() { |
| if (isDirectory()) { |
| return -1; |
| } |
| return resource.getSize(); |
| } |
| |
| @Override |
| public String getCanonicalPath() { |
| return null; |
| } |
| |
| @Override |
| public boolean canRead() { |
| return true; |
| } |
| |
| @Override |
| public long getCreation() { |
| return resource.getTime(); |
| } |
| |
| @Override |
| public URL getURL() { |
| String url = baseUrl + resource.getName(); |
| try { |
| return new URL(url); |
| } catch (MalformedURLException e) { |
| if (getLog().isDebugEnabled()) { |
| getLog().debug(sm.getString("fileResource.getUrlFail", url), e); |
| } |
| return null; |
| } |
| } |
| |
| @Override |
| public URL getCodeBase() { |
| try { |
| return new URL(codeBaseUrl); |
| } catch (MalformedURLException e) { |
| if (getLog().isDebugEnabled()) { |
| getLog().debug(sm.getString("fileResource.getUrlFail", codeBaseUrl), e); |
| } |
| return null; |
| } |
| } |
| |
| @Override |
| public final byte[] getContent() { |
| long len = getContentLength(); |
| |
| if (len > Integer.MAX_VALUE) { |
| // Can't create an array that big |
| throw new ArrayIndexOutOfBoundsException(sm.getString( |
| "abstractResource.getContentTooLarge", getWebappPath(), |
| Long.valueOf(len))); |
| } |
| |
| if (len < 0) { |
| // Content is not applicable here (e.g. is a directory) |
| return null; |
| } |
| |
| int size = (int) len; |
| byte[] result = new byte[size]; |
| |
| int pos = 0; |
| try (JarInputStreamWrapper jisw = getJarInputStreamWrapper()) { |
| if (jisw == null) { |
| // An error occurred, don't return corrupted content |
| return null; |
| } |
| while (pos < size) { |
| int n = jisw.read(result, pos, size - pos); |
| if (n < 0) { |
| break; |
| } |
| pos += n; |
| } |
| // Once the stream has been read, read the certs |
| certificates = jisw.getCertificates(); |
| readCerts = true; |
| } catch (IOException ioe) { |
| if (getLog().isDebugEnabled()) { |
| getLog().debug(sm.getString("abstractResource.getContentFail", |
| getWebappPath()), ioe); |
| } |
| // Don't return corrupted content |
| return null; |
| } |
| |
| return result; |
| } |
| |
| |
| @Override |
| public Certificate[] getCertificates() { |
| if (!readCerts) { |
| // TODO - get content first |
| throw new IllegalStateException(); |
| } |
| return certificates; |
| } |
| |
| @Override |
| public Manifest getManifest() { |
| return archiveResourceSet.getManifest(); |
| } |
| |
| @Override |
| protected final InputStream doGetInputStream() { |
| if (isDirectory()) { |
| return null; |
| } |
| return getJarInputStreamWrapper(); |
| } |
| |
| protected abstract JarInputStreamWrapper getJarInputStreamWrapper(); |
| |
| /** |
| * This wrapper assumes that the InputStream was created from a JarFile |
| * obtained from a call to getArchiveResourceSet().openJarFile(). If this is |
| * not the case then the usage counting in AbstractArchiveResourceSet will |
| * break and the JarFile may be unexpectedly closed. |
| */ |
| protected class JarInputStreamWrapper extends InputStream { |
| |
| private final JarEntry jarEntry; |
| private final InputStream is; |
| private final AtomicBoolean closed = new AtomicBoolean(false); |
| |
| |
| public JarInputStreamWrapper(JarEntry jarEntry, InputStream is) { |
| this.jarEntry = jarEntry; |
| this.is = is; |
| } |
| |
| |
| @Override |
| public int read() throws IOException { |
| return is.read(); |
| } |
| |
| |
| @Override |
| public int read(byte[] b) throws IOException { |
| return is.read(b); |
| } |
| |
| |
| @Override |
| public int read(byte[] b, int off, int len) throws IOException { |
| return is.read(b, off, len); |
| } |
| |
| |
| @Override |
| public long skip(long n) throws IOException { |
| return is.skip(n); |
| } |
| |
| |
| @Override |
| public int available() throws IOException { |
| return is.available(); |
| } |
| |
| |
| @Override |
| public void close() throws IOException { |
| if (closed.compareAndSet(false, true)) { |
| // Must only call this once else the usage counting will break |
| archiveResourceSet.closeJarFile(); |
| } |
| is.close(); |
| } |
| |
| |
| @Override |
| public synchronized void mark(int readlimit) { |
| is.mark(readlimit); |
| } |
| |
| |
| @Override |
| public synchronized void reset() throws IOException { |
| is.reset(); |
| } |
| |
| |
| @Override |
| public boolean markSupported() { |
| return is.markSupported(); |
| } |
| |
| public Certificate[] getCertificates() { |
| return jarEntry.getCertificates(); |
| } |
| } |
| } |