// // ======================================================================== // Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. // // The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php // // You may elect to redistribute this code under either of these licenses. // ======================================================================== // 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.log.Log; import org.eclipse.jetty.util.log.Logger; /* ------------------------------------------------------------ */ class JarFileResource extends JarResource { private static final Logger LOG = Log.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) { super(url); } /* ------------------------------------------------------------ */ protected JarFileResource(URL url, boolean useCaches) { super(url, useCaches); } /* ------------------------------------------------------------ */ @Override public synchronized void close() { _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.ignore(ioe); } } } _jarFile=null; super.close(); } /* ------------------------------------------------------------ */ @Override protected synchronized boolean checkConnection() { try { super.checkConnection(); } finally { if (_jarConnection==null) { _entry=null; _file=null; _jarFile=null; _list=null; } } return _jarFile!=null; } /* ------------------------------------------------------------ */ @Override protected synchronized void newConnection() throws IOException { super.newConnection(); _entry=null; _file=null; _jarFile=null; _list=null; 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. */ @Override public boolean exists() { if (_exists) return true; if (_urlString.endsWith("!/")) { String file_url=_urlString.substring(4,_urlString.length()-2); try{return newResource(file_url).exists();} catch(Exception e) {LOG.ignore(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 close_jar_file= false; JarFile jar_file=null; if (check) // Yes jar_file=_jarFile; else { // No - so lets look if the root entry exists. try { JarURLConnection c=(JarURLConnection)((new URL(_jarUrl)).openConnection()); c.setUseCaches(getUseCaches()); jar_file=c.getJarFile(); close_jar_file = !getUseCaches(); } catch(Exception e) { LOG.ignore(e); } } // Do we need to look more closely? if (jar_file!=null && _entry==null && !_directory) { // OK - we have a JarFile, lets look for the entry JarEntry entry = jar_file.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 = jar_file.getJarEntry(_path + '/'); if (directory != null) { _directory = true; _entry = directory; } else { // OK is a file _directory = false; _entry = entry; } } } if(close_jar_file && jar_file!=null) { try { jar_file.close(); } catch (IOException ioe) { LOG.ignore(ioe); } } } _exists= ( _directory || _entry!=null); return _exists; } /* ------------------------------------------------------------ */ /** * Returns true if the represented resource is a container/directory. * If the resource is not a file, resources ending with "/" are * considered directories. */ @Override public boolean isDirectory() { return _urlString.endsWith("/") || exists() && _directory; } /* ------------------------------------------------------------ */ /** * 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 synchronized String[] list() { 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 LOG.warn("Retrying list:"+e); LOG.debug(e); 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.ignore(e); } if(jarFile==null) throw new IllegalStateException(); } Enumeration<JarEntry> e=jarFile.entries(); String dir=_urlString.substring(_urlString.lastIndexOf("!/")+2); while(e.hasMoreElements()) { JarEntry entry = e.nextElement(); String name=entry.getName().replace('\\','/'); 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 */ @Override public long length() { if (isDirectory()) return -1; if (_entry!=null) return _entry.getSize(); return -1; } /** * Take a Resource that possibly might use URLConnection caching * and turn it into one that doesn't. * @param resource the JarFileResource to obtain without URLConnection caching. * @return the non-caching resource */ public static Resource getNonCachingResource (Resource resource) { if (!(resource instanceof JarFileResource)) return resource; JarFileResource oldResource = (JarFileResource)resource; JarFileResource newResource = new JarFileResource(oldResource.getURL(), false); return newResource; } /** * 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()); } }