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