1//
2//  ========================================================================
3//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
4//  ------------------------------------------------------------------------
5//  All rights reserved. This program and the accompanying materials
6//  are made available under the terms of the Eclipse Public License v1.0
7//  and Apache License v2.0 which accompanies this distribution.
8//
9//      The Eclipse Public License is available at
10//      http://www.eclipse.org/legal/epl-v10.html
11//
12//      The Apache License v2.0 is available at
13//      http://www.opensource.org/licenses/apache2.0.php
14//
15//  You may elect to redistribute this code under either of these licenses.
16//  ========================================================================
17//
18
19package org.eclipse.jetty.util.resource;
20
21import java.io.File;
22import java.io.IOException;
23import java.net.JarURLConnection;
24import java.net.MalformedURLException;
25import java.net.URL;
26import java.util.ArrayList;
27import java.util.Enumeration;
28import java.util.List;
29import java.util.jar.JarEntry;
30import java.util.jar.JarFile;
31
32import org.eclipse.jetty.util.log.Log;
33import org.eclipse.jetty.util.log.Logger;
34
35/* ------------------------------------------------------------ */
36class JarFileResource extends JarResource
37{
38    private static final Logger LOG = Log.getLogger(JarFileResource.class);
39    private JarFile _jarFile;
40    private File _file;
41    private String[] _list;
42    private JarEntry _entry;
43    private boolean _directory;
44    private String _jarUrl;
45    private String _path;
46    private boolean _exists;
47
48    /* -------------------------------------------------------- */
49    JarFileResource(URL url)
50    {
51        super(url);
52    }
53
54    /* ------------------------------------------------------------ */
55    JarFileResource(URL url, boolean useCaches)
56    {
57        super(url, useCaches);
58    }
59
60
61    /* ------------------------------------------------------------ */
62    @Override
63    public synchronized void release()
64    {
65        _list=null;
66        _entry=null;
67        _file=null;
68        //if the jvm is not doing url caching, then the JarFiles will not be cached either,
69        //and so they are safe to close
70        if (!getUseCaches())
71        {
72            if ( _jarFile != null )
73            {
74                try
75                {
76                    LOG.debug("Closing JarFile "+_jarFile.getName());
77                    _jarFile.close();
78                }
79                catch ( IOException ioe )
80                {
81                    LOG.ignore(ioe);
82                }
83            }
84        }
85        _jarFile=null;
86        super.release();
87    }
88
89    /* ------------------------------------------------------------ */
90    @Override
91    protected boolean checkConnection()
92    {
93        try
94        {
95            super.checkConnection();
96        }
97        finally
98        {
99            if (_jarConnection==null)
100            {
101                _entry=null;
102                _file=null;
103                _jarFile=null;
104                _list=null;
105            }
106        }
107        return _jarFile!=null;
108    }
109
110
111    /* ------------------------------------------------------------ */
112    @Override
113    protected synchronized void newConnection()
114        throws IOException
115    {
116        super.newConnection();
117
118        _entry=null;
119        _file=null;
120        _jarFile=null;
121        _list=null;
122
123        int sep = _urlString.indexOf("!/");
124        _jarUrl=_urlString.substring(0,sep+2);
125        _path=_urlString.substring(sep+2);
126        if (_path.length()==0)
127            _path=null;
128        _jarFile=_jarConnection.getJarFile();
129        _file=new File(_jarFile.getName());
130    }
131
132
133    /* ------------------------------------------------------------ */
134    /**
135     * Returns true if the represented resource exists.
136     */
137    @Override
138    public boolean exists()
139    {
140        if (_exists)
141            return true;
142
143        if (_urlString.endsWith("!/"))
144        {
145
146            String file_url=_urlString.substring(4,_urlString.length()-2);
147            try{return newResource(file_url).exists();}
148            catch(Exception e) {LOG.ignore(e); return false;}
149        }
150
151        boolean check=checkConnection();
152
153        // Is this a root URL?
154        if (_jarUrl!=null && _path==null)
155        {
156            // Then if it exists it is a directory
157            _directory=check;
158            return true;
159        }
160        else
161        {
162            // Can we find a file for it?
163            JarFile jarFile=null;
164            if (check)
165                // Yes
166                jarFile=_jarFile;
167            else
168            {
169                // No - so lets look if the root entry exists.
170                try
171                {
172                    JarURLConnection c=(JarURLConnection)((new URL(_jarUrl)).openConnection());
173                    c.setUseCaches(getUseCaches());
174                    jarFile=c.getJarFile();
175                }
176                catch(Exception e)
177                {
178                       LOG.ignore(e);
179                }
180            }
181
182            // Do we need to look more closely?
183            if (jarFile!=null && _entry==null && !_directory)
184            {
185                // OK - we have a JarFile, lets look at the entries for our path
186                Enumeration<JarEntry> e=jarFile.entries();
187                while(e.hasMoreElements())
188                {
189                    JarEntry entry = (JarEntry) e.nextElement();
190                    String name=entry.getName().replace('\\','/');
191
192                    // Do we have a match
193                    if (name.equals(_path))
194                    {
195                        _entry=entry;
196                        // Is the match a directory
197                        _directory=_path.endsWith("/");
198                        break;
199                    }
200                    else if (_path.endsWith("/"))
201                    {
202                        if (name.startsWith(_path))
203                        {
204                            _directory=true;
205                            break;
206                        }
207                    }
208                    else if (name.startsWith(_path) && name.length()>_path.length() && name.charAt(_path.length())=='/')
209                    {
210                        _directory=true;
211                        break;
212                    }
213                }
214
215                if (_directory && !_urlString.endsWith("/"))
216                {
217                    _urlString+="/";
218                    try
219                    {
220                        _url=new URL(_urlString);
221                    }
222                    catch(MalformedURLException ex)
223                    {
224                        LOG.warn(ex);
225                    }
226                }
227            }
228        }
229
230        _exists= ( _directory || _entry!=null);
231        return _exists;
232    }
233
234
235    /* ------------------------------------------------------------ */
236    /**
237     * Returns true if the represented resource is a container/directory.
238     * If the resource is not a file, resources ending with "/" are
239     * considered directories.
240     */
241    @Override
242    public boolean isDirectory()
243    {
244        return _urlString.endsWith("/") || exists() && _directory;
245    }
246
247    /* ------------------------------------------------------------ */
248    /**
249     * Returns the last modified time
250     */
251    @Override
252    public long lastModified()
253    {
254        if (checkConnection() && _file!=null)
255        {
256            if (exists() && _entry!=null)
257                return _entry.getTime();
258            return _file.lastModified();
259        }
260        return -1;
261    }
262
263    /* ------------------------------------------------------------ */
264    @Override
265    public synchronized String[] list()
266    {
267        if (isDirectory() && _list==null)
268        {
269            List<String> list = null;
270            try
271            {
272                list = listEntries();
273            }
274            catch (Exception e)
275            {
276                //Sun's JarURLConnection impl for jar: protocol will close a JarFile in its connect() method if
277                //useCaches == false (eg someone called URLConnection with defaultUseCaches==true).
278                //As their sun.net.www.protocol.jar package caches JarFiles and/or connections, we can wind up in
279                //the situation where the JarFile we have remembered in our _jarFile member has actually been closed
280                //by other code.
281                //So, do one retry to drop a connection and get a fresh JarFile
282                LOG.warn("Retrying list:"+e);
283                LOG.debug(e);
284                release();
285                list = listEntries();
286            }
287
288            if (list != null)
289            {
290                _list=new String[list.size()];
291                list.toArray(_list);
292            }
293        }
294        return _list;
295    }
296
297
298    /* ------------------------------------------------------------ */
299    private List<String> listEntries ()
300    {
301        checkConnection();
302
303        ArrayList<String> list = new ArrayList<String>(32);
304        JarFile jarFile=_jarFile;
305        if(jarFile==null)
306        {
307            try
308            {
309                JarURLConnection jc=(JarURLConnection)((new URL(_jarUrl)).openConnection());
310                jc.setUseCaches(getUseCaches());
311                jarFile=jc.getJarFile();
312            }
313            catch(Exception e)
314            {
315
316                e.printStackTrace();
317                 LOG.ignore(e);
318            }
319        }
320
321        Enumeration<JarEntry> e=jarFile.entries();
322        String dir=_urlString.substring(_urlString.indexOf("!/")+2);
323        while(e.hasMoreElements())
324        {
325            JarEntry entry = e.nextElement();
326            String name=entry.getName().replace('\\','/');
327            if(!name.startsWith(dir) || name.length()==dir.length())
328            {
329                continue;
330            }
331            String listName=name.substring(dir.length());
332            int dash=listName.indexOf('/');
333            if (dash>=0)
334            {
335                //when listing jar:file urls, you get back one
336                //entry for the dir itself, which we ignore
337                if (dash==0 && listName.length()==1)
338                    continue;
339                //when listing jar:file urls, all files and
340                //subdirs have a leading /, which we remove
341                if (dash==0)
342                    listName=listName.substring(dash+1, listName.length());
343                else
344                    listName=listName.substring(0,dash+1);
345
346                if (list.contains(listName))
347                    continue;
348            }
349
350            list.add(listName);
351        }
352
353        return list;
354    }
355
356
357
358
359
360    /* ------------------------------------------------------------ */
361    /**
362     * Return the length of the resource
363     */
364    @Override
365    public long length()
366    {
367        if (isDirectory())
368            return -1;
369
370        if (_entry!=null)
371            return _entry.getSize();
372
373        return -1;
374    }
375
376    /* ------------------------------------------------------------ */
377    /** Encode according to this resource type.
378     * File URIs are not encoded.
379     * @param uri URI to encode.
380     * @return The uri unchanged.
381     */
382    @Override
383    public String encode(String uri)
384    {
385        return uri;
386    }
387
388
389    /**
390     * Take a Resource that possibly might use URLConnection caching
391     * and turn it into one that doesn't.
392     * @param resource
393     * @return the non-caching resource
394     */
395    public static Resource getNonCachingResource (Resource resource)
396    {
397        if (!(resource instanceof JarFileResource))
398            return resource;
399
400        JarFileResource oldResource = (JarFileResource)resource;
401
402        JarFileResource newResource = new JarFileResource(oldResource.getURL(), false);
403        return newResource;
404
405    }
406
407    /**
408     * Check if this jar:file: resource is contained in the
409     * named resource. Eg <code>jar:file:///a/b/c/foo.jar!/x.html</code> isContainedIn <code>file:///a/b/c/foo.jar</code>
410     * @param resource
411     * @return true if resource is contained in the named resource
412     * @throws MalformedURLException
413     */
414    @Override
415    public boolean isContainedIn (Resource resource)
416    throws MalformedURLException
417    {
418        String string = _urlString;
419        int index = string.indexOf("!/");
420        if (index > 0)
421            string = string.substring(0,index);
422        if (string.startsWith("jar:"))
423            string = string.substring(4);
424        URL url = new URL(string);
425        return url.sameFile(resource.getURL());
426    }
427}
428
429
430
431
432
433
434
435
436