| /* |
| * 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? |
| } |
| } |
| } |
| } |