/* * 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(); } } }