//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.util.resource;

import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.thread.AutoLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class JarFileResource extends JarResource
{
    private static final Logger LOG = LoggerFactory.getLogger(JarFileResource.class);

    private JarFile _jarFile;
    private File _file;
    private String[] _list;
    private JarEntry _entry;
    private boolean _directory;
    private String _jarUrl;
    private String _path;
    private boolean _exists;

    protected JarFileResource(URL url, boolean useCaches)
    {
        super(url, useCaches);
    }

    @Override
    public void close()
    {
        try (AutoLock l = _lock.lock())
        {
            _exists = false;
            _list = null;
            _entry = null;
            _file = null;
            //if the jvm is not doing url caching, then the JarFiles will not be cached either,
            //and so they are safe to close
            if (!getUseCaches())
            {
                if (_jarFile != null)
                {
                    try
                    {
                        if (LOG.isDebugEnabled())
                            LOG.debug("Closing JarFile {}", _jarFile.getName());
                        _jarFile.close();
                    }
                    catch (IOException ioe)
                    {
                        LOG.trace("IGNORED", ioe);
                    }
                }
            }
            _jarFile = null;
            super.close();
        }
    }

    @Override
    protected boolean checkConnection()
    {
        try (AutoLock l = _lock.lock())
        {
            try
            {
                super.checkConnection();
            }
            finally
            {
                if (_jarConnection == null)
                {
                    _entry = null;
                    _file = null;
                    _jarFile = null;
                    _list = null;
                }
            }
            return _jarFile != null;
        }
    }

    @Override
    protected void newConnection() throws IOException
    {
        try (AutoLock l = _lock.lock())
        {
            super.newConnection();

            _entry = null;
            _file = null;
            _jarFile = null;
            _list = null;

            // Work with encoded URL path (_urlString is assumed to be encoded)
            int sep = _urlString.lastIndexOf("!/");
            _jarUrl = _urlString.substring(0, sep + 2);
            _path = URIUtil.decodePath(_urlString.substring(sep + 2));
            if (_path.length() == 0)
                _path = null;
            _jarFile = _jarConnection.getJarFile();
            _file = new File(_jarFile.getName());
        }
    }

    
Returns true if the represented resource exists.
/** * Returns true if the represented resource exists. */
@Override public boolean exists() { if (_exists) return true; if (_urlString.endsWith("!/")) { String fileUrl = _urlString.substring(4, _urlString.length() - 2); try { return _directory = newResource(fileUrl).exists(); } catch (Exception e) { LOG.trace("IGNORED", e); return false; } } boolean check = checkConnection(); // Is this a root URL? if (_jarUrl != null && _path == null) { // Then if it exists it is a directory _directory = check; return true; } else { // Can we find a file for it? boolean closeJarFile = false; JarFile jarFile = null; if (check) // Yes jarFile = _jarFile; else { // No - so lets look if the root entry exists. try { JarURLConnection c = (JarURLConnection)((new URL(_jarUrl)).openConnection()); c.setUseCaches(getUseCaches()); jarFile = c.getJarFile(); closeJarFile = !getUseCaches(); } catch (Exception e) { LOG.trace("IGNORED", e); } } // Do we need to look more closely? if (jarFile != null && _entry == null && !_directory) { // OK - we have a JarFile, lets look for the entry JarEntry entry = jarFile.getJarEntry(_path); if (entry == null) { // the entry does not exist _exists = false; } else if (entry.isDirectory()) { _directory = true; _entry = entry; } else { // Let's confirm is a file JarEntry directory = jarFile.getJarEntry(_path + '/'); if (directory != null) { _directory = true; _entry = directory; } else { // OK is a file _directory = false; _entry = entry; } } } if (closeJarFile && jarFile != null) { try { jarFile.close(); } catch (IOException ioe) { LOG.trace("IGNORED", ioe); } } } _exists = (_directory || _entry != null); return _exists; } @Override public boolean isDirectory() { return exists() && _directory; }
Returns the last modified time
/** * Returns the last modified time */
@Override public long lastModified() { if (checkConnection() && _file != null) { if (exists() && _entry != null) return _entry.getTime(); return _file.lastModified(); } return -1; } @Override public String[] list() { try (AutoLock l = _lock.lock()) { if (isDirectory() && _list == null) { List<String> list = null; try { list = listEntries(); } catch (Exception e) { //Sun's JarURLConnection impl for jar: protocol will close a JarFile in its connect() method if //useCaches == false (eg someone called URLConnection with defaultUseCaches==true). //As their sun.net.www.protocol.jar package caches JarFiles and/or connections, we can wind up in //the situation where the JarFile we have remembered in our _jarFile member has actually been closed //by other code. //So, do one retry to drop a connection and get a fresh JarFile if (LOG.isDebugEnabled()) LOG.warn("JarFile list failure", e); else LOG.warn("JarFile list failure {}", e.toString()); close(); list = listEntries(); } if (list != null) { _list = new String[list.size()]; list.toArray(_list); } } return _list; } } private List<String> listEntries() { checkConnection(); ArrayList<String> list = new ArrayList<String>(32); JarFile jarFile = _jarFile; if (jarFile == null) { try { JarURLConnection jc = (JarURLConnection)((new URL(_jarUrl)).openConnection()); jc.setUseCaches(getUseCaches()); jarFile = jc.getJarFile(); } catch (Exception e) { e.printStackTrace(); LOG.trace("IGNORED", e); } if (jarFile == null) throw new IllegalStateException(); } Enumeration<JarEntry> e = jarFile.entries(); String encodedDir = _urlString.substring(_urlString.lastIndexOf("!/") + 2); String dir = URIUtil.decodePath(encodedDir); while (e.hasMoreElements()) { JarEntry entry = e.nextElement(); String name = entry.getName(); if (!name.startsWith(dir) || name.length() == dir.length()) { continue; } String listName = name.substring(dir.length()); int dash = listName.indexOf('/'); if (dash >= 0) { //when listing jar:file urls, you get back one //entry for the dir itself, which we ignore if (dash == 0 && listName.length() == 1) continue; //when listing jar:file urls, all files and //subdirs have a leading /, which we remove if (dash == 0) listName = listName.substring(dash + 1, listName.length()); else listName = listName.substring(0, dash + 1); if (list.contains(listName)) continue; } list.add(listName); } return list; }
Return the length of the resource
/** * Return the length of the resource */
@Override public long length() { if (isDirectory()) return -1; if (_entry != null) return _entry.getSize(); return -1; }
Check if this jar:file: resource is contained in the named resource. Eg jar:file:///a/b/c/foo.jar!/x.html isContainedIn file:///a/b/c/foo.jar
Params:
  • resource – the resource to test for
Throws:
Returns:true if resource is contained in the named resource
/** * Check if this jar:file: resource is contained in the * named resource. Eg <code>jar:file:///a/b/c/foo.jar!/x.html</code> isContainedIn <code>file:///a/b/c/foo.jar</code> * * @param resource the resource to test for * @return true if resource is contained in the named resource * @throws MalformedURLException if unable to process is contained due to invalid URL format */
@Override public boolean isContainedIn(Resource resource) throws MalformedURLException { String string = _urlString; int index = string.lastIndexOf("!/"); if (index > 0) string = string.substring(0, index); if (string.startsWith("jar:")) string = string.substring(4); URL url = new URL(string); return url.sameFile(resource.getURI().toURL()); } public File getJarFile() { if (_file != null) return _file; return null; } }