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