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 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