blob: 35afc9cb62e50af3e61840be092b79261547171e [file] [log] [blame]
/*
* 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.felix.framework.cache;
import org.apache.felix.framework.Logger;
import org.apache.felix.framework.util.FelixConstants;
import org.apache.felix.framework.util.Util;
import org.apache.felix.framework.util.WeakZipFileFactory;
import org.apache.felix.framework.util.WeakZipFileFactory.WeakZipFile;
import org.osgi.framework.Constants;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.zip.ZipEntry;
public class JarContent implements Content
{
private static final transient String EMBEDDED_DIRECTORY = "-embedded";
private static final transient String LIBRARY_DIRECTORY = "-lib";
private final Logger m_logger;
private final Map m_configMap;
private final WeakZipFileFactory m_zipFactory;
private final Object m_revisionLock;
private final File m_rootDir;
private final File m_file;
private final WeakZipFile m_zipFile;
private final boolean m_isZipFileOwner;
private Map m_nativeLibMap;
public JarContent(Logger logger, Map configMap, WeakZipFileFactory zipFactory,
Object revisionLock, File rootDir, File file, WeakZipFile zipFile)
{
m_logger = logger;
m_configMap = configMap;
m_zipFactory = zipFactory;
m_revisionLock = revisionLock;
m_rootDir = rootDir;
m_file = file;
if (zipFile == null)
{
try
{
m_zipFile = m_zipFactory.create(m_file);
}
catch (IOException ex)
{
throw new RuntimeException(
"Unable to open JAR file, probably deleted: " + ex.getMessage());
}
}
else
{
m_zipFile = zipFile;
}
m_isZipFileOwner = (zipFile == null);
}
protected void finalize()
{
close();
}
public void close()
{
try
{
if (m_isZipFileOwner)
{
m_zipFile.close();
}
}
catch (Exception ex)
{
m_logger.log(
Logger.LOG_ERROR,
"JarContent: Unable to close JAR file.", ex);
}
}
public boolean hasEntry(String name) throws IllegalStateException
{
try
{
ZipEntry ze = m_zipFile.getEntry(name);
return ze != null;
}
catch (Exception ex)
{
return false;
}
finally
{
}
}
public Enumeration<String> getEntries()
{
// Wrap entries enumeration to filter non-matching entries.
Enumeration<String> e = m_zipFile.names();
// Spec says to return null if there are no entries.
return (e.hasMoreElements()) ? e : null;
}
public byte[] getEntryAsBytes(String name) throws IllegalStateException
{
// Get the embedded resource.
try
{
ZipEntry ze = m_zipFile.getEntry(name);
if (ze == null)
{
return null;
}
return BundleCache.read(m_zipFile.getInputStream(ze), ze.getSize());
}
catch (Exception ex)
{
m_logger.log(
Logger.LOG_ERROR,
"JarContent: Unable to read bytes for file " + name + " in ZIP file " + m_file.getAbsolutePath(), ex);
return null;
}
}
public InputStream getEntryAsStream(String name)
throws IllegalStateException, IOException
{
// Get the embedded resource.
InputStream is = null;
try
{
ZipEntry ze = m_zipFile.getEntry(name);
if (ze == null)
{
return null;
}
is = m_zipFile.getInputStream(ze);
if (is == null)
{
return null;
}
}
catch (Exception ex)
{
return null;
}
return is;
}
public URL getEntryAsURL(String name)
{
if (hasEntry(name))
{
try
{
return new URL("jar:" + m_file.toURI().toURL().toExternalForm() + "!/" + name);
}
catch (MalformedURLException e)
{
return null;
}
}
else
{
return null;
}
}
public Content getEntryAsContent(String entryName)
{
// If the entry name refers to the content itself, then
// just return it immediately.
if (entryName.equals(FelixConstants.CLASS_PATH_DOT))
{
return new JarContent(m_logger, m_configMap, m_zipFactory, m_revisionLock,
m_rootDir, m_file, m_zipFile);
}
// Remove any leading slash.
entryName = (entryName.startsWith("/")) ? entryName.substring(1) : entryName;
if (entryName.trim().startsWith(".." + File.separatorChar) ||
entryName.contains(File.separator + ".." + File.separatorChar) ||
entryName.trim().endsWith(File.separator + "..") ||
entryName.trim().equals(".."))
{
return null;
}
// Any embedded JAR files will be extracted to the embedded directory.
// Since embedded JAR file names may clash when extracting from multiple
// embedded JAR files, the embedded directory is per embedded JAR file.
File embedDir = new File(m_rootDir, m_file.getName() + EMBEDDED_DIRECTORY);
// Find the entry in the JAR file and create the
// appropriate content type for it.
// Determine if the entry is an emdedded JAR file or
// directory in the bundle JAR file. Ignore any entries
// that do not exist per the spec.
ZipEntry ze = m_zipFile.getEntry(entryName);
if ((ze != null) && ze.isDirectory())
{
return new ContentDirectoryContent(this, entryName);
}
else if ((ze != null) && ze.getName().endsWith(".jar"))
{
File extractJar = new File(embedDir, entryName);
try
{
if (!BundleCache.getSecureAction().fileExists(extractJar))
{
// Extracting the embedded JAR file impacts all other existing
// contents for this revision, so we have to grab the revision
// lock first before trying to extract the embedded JAR file
// to avoid a race condition.
synchronized (m_revisionLock)
{
if (!BundleCache.getSecureAction().fileExists(extractJar))
{
// Make sure that the embedded JAR's parent directory exists;
// it may be in a sub-directory.
File jarDir = extractJar.getParentFile();
if (!BundleCache.getSecureAction().fileExists(jarDir) && !BundleCache.getSecureAction().mkdirs(jarDir))
{
throw new IOException("Unable to create embedded JAR directory.");
}
// Extract embedded JAR into its directory.
BundleCache.copyStreamToFile(m_zipFile.getInputStream(ze), extractJar);
}
}
}
return new JarContent(
m_logger, m_configMap, m_zipFactory, m_revisionLock,
extractJar.getParentFile(), extractJar, null);
}
catch (Exception ex)
{
m_logger.log(
Logger.LOG_ERROR,
"Unable to extract embedded JAR file.", ex);
}
}
// The entry could not be found, so return null.
return null;
}
// TODO: SECURITY - This will need to consider security.
public String getEntryAsNativeLibrary(String entryName)
{
// Return result.
String result = null;
// Remove any leading slash.
entryName = (entryName.startsWith("/")) ? entryName.substring(1) : entryName;
if (entryName.trim().startsWith(".." + File.separatorChar) ||
entryName.contains(File.separator + ".." + File.separatorChar) ||
entryName.trim().endsWith(File.separator + "..") ||
entryName.trim().equals(".."))
{
return null;
}
// Any embedded native libraries will be extracted to the lib directory.
// Since embedded library file names may clash when extracting from multiple
// embedded JAR files, the embedded lib directory is per embedded JAR file.
File libDir = new File(m_rootDir, m_file.getName() + LIBRARY_DIRECTORY);
// The entry name must refer to a file type, since it is
// a native library, not a directory.
ZipEntry ze = m_zipFile.getEntry(entryName);
if ((ze != null) && !ze.isDirectory())
{
// Extracting the embedded native library file impacts all other
// existing contents for this revision, so we have to grab the
// revision lock first before trying to extract the embedded JAR
// file to avoid a race condition.
synchronized (m_revisionLock)
{
// Since native libraries cannot be shared, we must extract a
// separate copy per request, so use the request library counter
// as part of the extracted path.
if (m_nativeLibMap == null)
{
m_nativeLibMap = new HashMap();
}
Integer libCount = (Integer) m_nativeLibMap.get(entryName);
// Either set or increment the library count.
libCount = (libCount == null) ? new Integer(0) : new Integer(libCount.intValue() + 1);
m_nativeLibMap.put(entryName, libCount);
File libFile = new File(
libDir, libCount.toString() + File.separatorChar + entryName);
if (!BundleCache.getSecureAction().fileExists(libFile))
{
if (!BundleCache.getSecureAction().fileExists(libFile.getParentFile())
&& !BundleCache.getSecureAction().mkdirs(libFile.getParentFile()))
{
m_logger.log(
Logger.LOG_ERROR,
"Unable to create library directory.");
}
else
{
try
{
// Create the file.
BundleCache.copyStreamToFile(m_zipFile.getInputStream(ze), libFile);
// Perform exec permission command on extracted library
// if one is configured.
String command = (String) m_configMap.get(
Constants.FRAMEWORK_EXECPERMISSION);
if (command != null)
{
Properties props = new Properties();
props.setProperty("abspath", libFile.toString());
command = Util.substVars(command, "command", null, props);
Process p = BundleCache.getSecureAction().exec(command);
// We have to make sure we read stdout and stderr because
// otherwise we will block on certain unbuffered os's
// (like eg. windows)
Thread stdOut = new Thread(
new DevNullRunnable(p.getInputStream()));
Thread stdErr = new Thread(
new DevNullRunnable(p.getErrorStream()));
stdOut.setDaemon(true);
stdErr.setDaemon(true);
stdOut.start();
stdErr.start();
p.waitFor();
stdOut.join();
stdErr.join();
}
// Return the path to the extracted native library.
result = BundleCache.getSecureAction().getAbsolutePath(libFile);
}
catch (Exception ex)
{
m_logger.log(
Logger.LOG_ERROR,
"Extracting native library.", ex);
}
}
}
else
{
// Return the path to the extracted native library.
result = BundleCache.getSecureAction().getAbsolutePath(libFile);
}
}
}
return result;
}
public String toString()
{
return "JAR " + m_file.getPath();
}
public File getFile()
{
return m_file;
}
private static class DevNullRunnable implements Runnable
{
private final InputStream m_in;
public DevNullRunnable(InputStream in)
{
m_in = in;
}
public void run()
{
try
{
try
{
while (m_in.read() != -1){}
}
finally
{
m_in.close();
}
}
catch (Exception ex)
{
// Not much we can do - maybe we should log it?
}
}
}
}