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.FileOutputStream; 23 import java.io.IOException; 24 import java.io.InputStream; 25 import java.io.OutputStream; 26 import java.net.MalformedURLException; 27 import java.net.URI; 28 import java.net.URL; 29 import java.net.URLConnection; 30 import java.text.DateFormat; 31 import java.util.Arrays; 32 import java.util.Date; 33 34 import org.eclipse.jetty.util.B64Code; 35 import org.eclipse.jetty.util.IO; 36 import org.eclipse.jetty.util.Loader; 37 import org.eclipse.jetty.util.StringUtil; 38 import org.eclipse.jetty.util.URIUtil; 39 import org.eclipse.jetty.util.log.Log; 40 import org.eclipse.jetty.util.log.Logger; 41 42 43 /* ------------------------------------------------------------ */ 44 /** 45 * Abstract resource class. 46 */ 47 public abstract class Resource implements ResourceFactory 48 { 49 private static final Logger LOG = Log.getLogger(Resource.class); 50 public static boolean __defaultUseCaches = true; 51 volatile Object _associate; 52 53 /* ------------------------------------------------------------ */ 54 /** 55 * Change the default setting for url connection caches. 56 * Subsequent URLConnections will use this default. 57 * @param useCaches 58 */ setDefaultUseCaches(boolean useCaches)59 public static void setDefaultUseCaches (boolean useCaches) 60 { 61 __defaultUseCaches=useCaches; 62 } 63 64 /* ------------------------------------------------------------ */ getDefaultUseCaches()65 public static boolean getDefaultUseCaches () 66 { 67 return __defaultUseCaches; 68 } 69 70 /* ------------------------------------------------------------ */ 71 /** Construct a resource from a uri. 72 * @param uri A URI. 73 * @return A Resource object. 74 * @throws IOException Problem accessing URI 75 */ newResource(URI uri)76 public static Resource newResource(URI uri) 77 throws IOException 78 { 79 return newResource(uri.toURL()); 80 } 81 82 /* ------------------------------------------------------------ */ 83 /** Construct a resource from a url. 84 * @param url A URL. 85 * @return A Resource object. 86 * @throws IOException Problem accessing URL 87 */ newResource(URL url)88 public static Resource newResource(URL url) 89 throws IOException 90 { 91 return newResource(url, __defaultUseCaches); 92 } 93 94 /* ------------------------------------------------------------ */ 95 /** 96 * Construct a resource from a url. 97 * @param url the url for which to make the resource 98 * @param useCaches true enables URLConnection caching if applicable to the type of resource 99 * @return 100 */ newResource(URL url, boolean useCaches)101 static Resource newResource(URL url, boolean useCaches) 102 { 103 if (url==null) 104 return null; 105 106 String url_string=url.toExternalForm(); 107 if( url_string.startsWith( "file:")) 108 { 109 try 110 { 111 FileResource fileResource= new FileResource(url); 112 return fileResource; 113 } 114 catch(Exception e) 115 { 116 LOG.debug(Log.EXCEPTION,e); 117 return new BadResource(url,e.toString()); 118 } 119 } 120 else if( url_string.startsWith( "jar:file:")) 121 { 122 return new JarFileResource(url, useCaches); 123 } 124 else if( url_string.startsWith( "jar:")) 125 { 126 return new JarResource(url, useCaches); 127 } 128 129 return new URLResource(url,null,useCaches); 130 } 131 132 133 134 /* ------------------------------------------------------------ */ 135 /** Construct a resource from a string. 136 * @param resource A URL or filename. 137 * @return A Resource object. 138 */ newResource(String resource)139 public static Resource newResource(String resource) 140 throws MalformedURLException, IOException 141 { 142 return newResource(resource, __defaultUseCaches); 143 } 144 145 /* ------------------------------------------------------------ */ 146 /** Construct a resource from a string. 147 * @param resource A URL or filename. 148 * @param useCaches controls URLConnection caching 149 * @return A Resource object. 150 */ newResource(String resource, boolean useCaches)151 public static Resource newResource (String resource, boolean useCaches) 152 throws MalformedURLException, IOException 153 { 154 URL url=null; 155 try 156 { 157 // Try to format as a URL? 158 url = new URL(resource); 159 } 160 catch(MalformedURLException e) 161 { 162 if(!resource.startsWith("ftp:") && 163 !resource.startsWith("file:") && 164 !resource.startsWith("jar:")) 165 { 166 try 167 { 168 // It's a file. 169 if (resource.startsWith("./")) 170 resource=resource.substring(2); 171 172 File file=new File(resource).getCanonicalFile(); 173 url=Resource.toURL(file); 174 175 URLConnection connection=url.openConnection(); 176 connection.setUseCaches(useCaches); 177 return new FileResource(url,connection,file); 178 } 179 catch(Exception e2) 180 { 181 LOG.debug(Log.EXCEPTION,e2); 182 throw e; 183 } 184 } 185 else 186 { 187 LOG.warn("Bad Resource: "+resource); 188 throw e; 189 } 190 } 191 192 return newResource(url); 193 } 194 195 /* ------------------------------------------------------------ */ newResource(File file)196 public static Resource newResource (File file) 197 throws MalformedURLException, IOException 198 { 199 file = file.getCanonicalFile(); 200 URL url = Resource.toURL(file); 201 202 URLConnection connection = url.openConnection(); 203 FileResource fileResource = new FileResource(url, connection, file); 204 return fileResource; 205 } 206 207 /* ------------------------------------------------------------ */ 208 /** Construct a system resource from a string. 209 * The resource is tried as classloader resource before being 210 * treated as a normal resource. 211 * @param resource Resource as string representation 212 * @return The new Resource 213 * @throws IOException Problem accessing resource. 214 */ newSystemResource(String resource)215 public static Resource newSystemResource(String resource) 216 throws IOException 217 { 218 URL url=null; 219 // Try to format as a URL? 220 ClassLoader loader=Thread.currentThread().getContextClassLoader(); 221 if (loader!=null) 222 { 223 try 224 { 225 url = loader.getResource(resource); 226 if (url == null && resource.startsWith("/")) 227 url = loader.getResource(resource.substring(1)); 228 } 229 catch (IllegalArgumentException e) 230 { 231 // Catches scenario where a bad Windows path like "C:\dev" is 232 // improperly escaped, which various downstream classloaders 233 // tend to have a problem with 234 url = null; 235 } 236 } 237 if (url==null) 238 { 239 loader=Resource.class.getClassLoader(); 240 if (loader!=null) 241 { 242 url=loader.getResource(resource); 243 if (url==null && resource.startsWith("/")) 244 url=loader.getResource(resource.substring(1)); 245 } 246 } 247 248 if (url==null) 249 { 250 url=ClassLoader.getSystemResource(resource); 251 if (url==null && resource.startsWith("/")) 252 url=ClassLoader.getSystemResource(resource.substring(1)); 253 } 254 255 if (url==null) 256 return null; 257 258 return newResource(url); 259 } 260 261 /* ------------------------------------------------------------ */ 262 /** Find a classpath resource. 263 */ newClassPathResource(String resource)264 public static Resource newClassPathResource(String resource) 265 { 266 return newClassPathResource(resource,true,false); 267 } 268 269 /* ------------------------------------------------------------ */ 270 /** Find a classpath resource. 271 * The {@link java.lang.Class#getResource(String)} method is used to lookup the resource. If it is not 272 * found, then the {@link Loader#getResource(Class, String, boolean)} method is used. 273 * If it is still not found, then {@link ClassLoader#getSystemResource(String)} is used. 274 * Unlike {@link ClassLoader#getSystemResource(String)} this method does not check for normal resources. 275 * @param name The relative name of the resource 276 * @param useCaches True if URL caches are to be used. 277 * @param checkParents True if forced searching of parent Classloaders is performed to work around 278 * loaders with inverted priorities 279 * @return Resource or null 280 */ newClassPathResource(String name,boolean useCaches,boolean checkParents)281 public static Resource newClassPathResource(String name,boolean useCaches,boolean checkParents) 282 { 283 URL url=Resource.class.getResource(name); 284 285 if (url==null) 286 url=Loader.getResource(Resource.class,name,checkParents); 287 if (url==null) 288 return null; 289 return newResource(url,useCaches); 290 } 291 292 /* ------------------------------------------------------------ */ isContainedIn(Resource r, Resource containingResource)293 public static boolean isContainedIn (Resource r, Resource containingResource) throws MalformedURLException 294 { 295 return r.isContainedIn(containingResource); 296 } 297 298 /* ------------------------------------------------------------ */ 299 @Override finalize()300 protected void finalize() 301 { 302 release(); 303 } 304 305 /* ------------------------------------------------------------ */ isContainedIn(Resource r)306 public abstract boolean isContainedIn (Resource r) throws MalformedURLException; 307 308 309 /* ------------------------------------------------------------ */ 310 /** Release any temporary resources held by the resource. 311 */ release()312 public abstract void release(); 313 314 315 /* ------------------------------------------------------------ */ 316 /** 317 * Returns true if the respresened resource exists. 318 */ exists()319 public abstract boolean exists(); 320 321 322 /* ------------------------------------------------------------ */ 323 /** 324 * Returns true if the respresenetd resource is a container/directory. 325 * If the resource is not a file, resources ending with "/" are 326 * considered directories. 327 */ isDirectory()328 public abstract boolean isDirectory(); 329 330 /* ------------------------------------------------------------ */ 331 /** 332 * Returns the last modified time 333 */ lastModified()334 public abstract long lastModified(); 335 336 337 /* ------------------------------------------------------------ */ 338 /** 339 * Return the length of the resource 340 */ length()341 public abstract long length(); 342 343 344 /* ------------------------------------------------------------ */ 345 /** 346 * Returns an URL representing the given resource 347 */ getURL()348 public abstract URL getURL(); 349 350 /* ------------------------------------------------------------ */ 351 /** 352 * Returns an URI representing the given resource 353 */ getURI()354 public URI getURI() 355 { 356 try 357 { 358 return getURL().toURI(); 359 } 360 catch(Exception e) 361 { 362 throw new RuntimeException(e); 363 } 364 } 365 366 367 /* ------------------------------------------------------------ */ 368 /** 369 * Returns an File representing the given resource or NULL if this 370 * is not possible. 371 */ getFile()372 public abstract File getFile() 373 throws IOException; 374 375 376 /* ------------------------------------------------------------ */ 377 /** 378 * Returns the name of the resource 379 */ getName()380 public abstract String getName(); 381 382 383 /* ------------------------------------------------------------ */ 384 /** 385 * Returns an input stream to the resource 386 */ getInputStream()387 public abstract InputStream getInputStream() 388 throws java.io.IOException; 389 390 /* ------------------------------------------------------------ */ 391 /** 392 * Returns an output stream to the resource 393 */ getOutputStream()394 public abstract OutputStream getOutputStream() 395 throws java.io.IOException, SecurityException; 396 397 /* ------------------------------------------------------------ */ 398 /** 399 * Deletes the given resource 400 */ delete()401 public abstract boolean delete() 402 throws SecurityException; 403 404 /* ------------------------------------------------------------ */ 405 /** 406 * Rename the given resource 407 */ renameTo( Resource dest)408 public abstract boolean renameTo( Resource dest) 409 throws SecurityException; 410 411 /* ------------------------------------------------------------ */ 412 /** 413 * Returns a list of resource names contained in the given resource 414 * The resource names are not URL encoded. 415 */ list()416 public abstract String[] list(); 417 418 /* ------------------------------------------------------------ */ 419 /** 420 * Returns the resource contained inside the current resource with the 421 * given name. 422 * @param path The path segment to add, which should be encoded by the 423 * encode method. 424 */ addPath(String path)425 public abstract Resource addPath(String path) 426 throws IOException,MalformedURLException; 427 428 /* ------------------------------------------------------------ */ 429 /** Get a resource from withing this resource. 430 * <p> 431 * This method is essentially an alias for {@link #addPath(String)}, but without checked exceptions. 432 * This method satisfied the {@link ResourceFactory} interface. 433 * @see org.eclipse.jetty.util.resource.ResourceFactory#getResource(java.lang.String) 434 */ getResource(String path)435 public Resource getResource(String path) 436 { 437 try 438 { 439 return addPath(path); 440 } 441 catch(Exception e) 442 { 443 LOG.debug(e); 444 return null; 445 } 446 } 447 448 /* ------------------------------------------------------------ */ 449 /** Encode according to this resource type. 450 * The default implementation calls URI.encodePath(uri) 451 * @param uri 452 * @return String encoded for this resource type. 453 */ encode(String uri)454 public String encode(String uri) 455 { 456 return URIUtil.encodePath(uri); 457 } 458 459 /* ------------------------------------------------------------ */ getAssociate()460 public Object getAssociate() 461 { 462 return _associate; 463 } 464 465 /* ------------------------------------------------------------ */ setAssociate(Object o)466 public void setAssociate(Object o) 467 { 468 _associate=o; 469 } 470 471 /* ------------------------------------------------------------ */ 472 /** 473 * @return The canonical Alias of this resource or null if none. 474 */ getAlias()475 public URL getAlias() 476 { 477 return null; 478 } 479 480 /* ------------------------------------------------------------ */ 481 /** Get the resource list as a HTML directory listing. 482 * @param base The base URL 483 * @param parent True if the parent directory should be included 484 * @return String of HTML 485 */ getListHTML(String base,boolean parent)486 public String getListHTML(String base,boolean parent) 487 throws IOException 488 { 489 base=URIUtil.canonicalPath(base); 490 if (base==null || !isDirectory()) 491 return null; 492 493 String[] ls = list(); 494 if (ls==null) 495 return null; 496 Arrays.sort(ls); 497 498 String decodedBase = URIUtil.decodePath(base); 499 String title = "Directory: "+deTag(decodedBase); 500 501 StringBuilder buf=new StringBuilder(4096); 502 buf.append("<HTML><HEAD>"); 503 buf.append("<LINK HREF=\"").append("jetty-dir.css").append("\" REL=\"stylesheet\" TYPE=\"text/css\"/><TITLE>"); 504 buf.append(title); 505 buf.append("</TITLE></HEAD><BODY>\n<H1>"); 506 buf.append(title); 507 buf.append("</H1>\n<TABLE BORDER=0>\n"); 508 509 if (parent) 510 { 511 buf.append("<TR><TD><A HREF=\""); 512 buf.append(URIUtil.addPaths(base,"../")); 513 buf.append("\">Parent Directory</A></TD><TD></TD><TD></TD></TR>\n"); 514 } 515 516 String encodedBase = hrefEncodeURI(base); 517 518 DateFormat dfmt=DateFormat.getDateTimeInstance(DateFormat.MEDIUM, 519 DateFormat.MEDIUM); 520 for (int i=0 ; i< ls.length ; i++) 521 { 522 Resource item = addPath(ls[i]); 523 524 buf.append("\n<TR><TD><A HREF=\""); 525 String path=URIUtil.addPaths(encodedBase,URIUtil.encodePath(ls[i])); 526 527 buf.append(path); 528 529 if (item.isDirectory() && !path.endsWith("/")) 530 buf.append(URIUtil.SLASH); 531 532 // URIUtil.encodePath(buf,path); 533 buf.append("\">"); 534 buf.append(deTag(ls[i])); 535 buf.append(" "); 536 buf.append("</A></TD><TD ALIGN=right>"); 537 buf.append(item.length()); 538 buf.append(" bytes </TD><TD>"); 539 buf.append(dfmt.format(new Date(item.lastModified()))); 540 buf.append("</TD></TR>"); 541 } 542 buf.append("</TABLE>\n"); 543 buf.append("</BODY></HTML>\n"); 544 545 return buf.toString(); 546 } 547 548 /** 549 * Encode any characters that could break the URI string in an HREF. 550 * Such as <a href="/path/to;<script>Window.alert("XSS"+'%20'+"here");</script>">Link</a> 551 * 552 * The above example would parse incorrectly on various browsers as the "<" or '"' characters 553 * would end the href attribute value string prematurely. 554 * 555 * @param raw the raw text to encode. 556 * @return the defanged text. 557 */ hrefEncodeURI(String raw)558 private static String hrefEncodeURI(String raw) 559 { 560 StringBuffer buf = null; 561 562 loop: 563 for (int i=0;i<raw.length();i++) 564 { 565 char c=raw.charAt(i); 566 switch(c) 567 { 568 case '\'': 569 case '"': 570 case '<': 571 case '>': 572 buf=new StringBuffer(raw.length()<<1); 573 break loop; 574 } 575 } 576 if (buf==null) 577 return raw; 578 579 for (int i=0;i<raw.length();i++) 580 { 581 char c=raw.charAt(i); 582 switch(c) 583 { 584 case '"': 585 buf.append("%22"); 586 continue; 587 case '\'': 588 buf.append("%27"); 589 continue; 590 case '<': 591 buf.append("%3C"); 592 continue; 593 case '>': 594 buf.append("%3E"); 595 continue; 596 default: 597 buf.append(c); 598 continue; 599 } 600 } 601 602 return buf.toString(); 603 } 604 deTag(String raw)605 private static String deTag(String raw) 606 { 607 return StringUtil.replace( StringUtil.replace(raw,"<","<"), ">", ">"); 608 } 609 610 /* ------------------------------------------------------------ */ 611 /** 612 * @param out 613 * @param start First byte to write 614 * @param count Bytes to write or -1 for all of them. 615 */ writeTo(OutputStream out,long start,long count)616 public void writeTo(OutputStream out,long start,long count) 617 throws IOException 618 { 619 InputStream in = getInputStream(); 620 try 621 { 622 in.skip(start); 623 if (count<0) 624 IO.copy(in,out); 625 else 626 IO.copy(in,out,count); 627 } 628 finally 629 { 630 in.close(); 631 } 632 } 633 634 /* ------------------------------------------------------------ */ copyTo(File destination)635 public void copyTo(File destination) 636 throws IOException 637 { 638 if (destination.exists()) 639 throw new IllegalArgumentException(destination+" exists"); 640 writeTo(new FileOutputStream(destination),0,-1); 641 } 642 643 /* ------------------------------------------------------------ */ getWeakETag()644 public String getWeakETag() 645 { 646 try 647 { 648 StringBuilder b = new StringBuilder(32); 649 b.append("W/\""); 650 651 String name=getName(); 652 int length=name.length(); 653 long lhash=0; 654 for (int i=0; i<length;i++) 655 lhash=31*lhash+name.charAt(i); 656 657 B64Code.encode(lastModified()^lhash,b); 658 B64Code.encode(length()^lhash,b); 659 b.append('"'); 660 return b.toString(); 661 } 662 catch (IOException e) 663 { 664 throw new RuntimeException(e); 665 } 666 } 667 668 /* ------------------------------------------------------------ */ 669 /** Generate a properly encoded URL from a {@link File} instance. 670 * @param file Target file. 671 * @return URL of the target file. 672 * @throws MalformedURLException 673 */ toURL(File file)674 public static URL toURL(File file) throws MalformedURLException 675 { 676 return file.toURI().toURL(); 677 } 678 } 679