2 // ========================================================================
3 // Copyright (c) 1995-2016 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.
9 // The Eclipse Public License is available at
10 // http://www.eclipse.org/legal/epl-v10.html
12 // The Apache License v2.0 is available at
13 // http://www.opensource.org/licenses/apache2.0.php
15 // You may elect to redistribute this code under either of these licenses.
16 // ========================================================================
19 package org.eclipse.jetty.util.resource;
21 import java.io.Closeable;
23 import java.io.FileOutputStream;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.OutputStream;
27 import java.net.MalformedURLException;
30 import java.nio.channels.ReadableByteChannel;
31 import java.text.DateFormat;
32 import java.util.ArrayList;
33 import java.util.Arrays;
34 import java.util.Collection;
35 import java.util.Date;
37 import org.eclipse.jetty.util.B64Code;
38 import org.eclipse.jetty.util.IO;
39 import org.eclipse.jetty.util.Loader;
40 import org.eclipse.jetty.util.StringUtil;
41 import org.eclipse.jetty.util.URIUtil;
42 import org.eclipse.jetty.util.log.Log;
43 import org.eclipse.jetty.util.log.Logger;
46 /* ------------------------------------------------------------ */
48 * Abstract resource class.
50 * This class provides a resource abstraction, where a resource may be
51 * a file, a URL or an entry in a jar file.
54 public abstract class Resource implements ResourceFactory, Closeable
56 private static final Logger LOG = Log.getLogger(Resource.class);
57 public static boolean __defaultUseCaches = true;
58 volatile Object _associate;
60 /* ------------------------------------------------------------ */
62 * Change the default setting for url connection caches.
63 * Subsequent URLConnections will use this default.
66 public static void setDefaultUseCaches (boolean useCaches)
68 __defaultUseCaches=useCaches;
71 /* ------------------------------------------------------------ */
72 public static boolean getDefaultUseCaches ()
74 return __defaultUseCaches;
77 /* ------------------------------------------------------------ */
78 /** Construct a resource from a uri.
80 * @return A Resource object.
81 * @throws MalformedURLException Problem accessing URI
83 public static Resource newResource(URI uri)
84 throws MalformedURLException
86 return newResource(uri.toURL());
89 /* ------------------------------------------------------------ */
90 /** Construct a resource from a url.
92 * @return A Resource object.
94 public static Resource newResource(URL url)
96 return newResource(url, __defaultUseCaches);
99 /* ------------------------------------------------------------ */
101 * Construct a resource from a url.
102 * @param url the url for which to make the resource
103 * @param useCaches true enables URLConnection caching if applicable to the type of resource
106 static Resource newResource(URL url, boolean useCaches)
111 String url_string=url.toExternalForm();
112 if( url_string.startsWith( "file:"))
116 FileResource fileResource= new FileResource(url);
121 LOG.warn(e.toString());
122 LOG.debug(Log.EXCEPTION,e);
123 return new BadResource(url,e.toString());
126 else if( url_string.startsWith( "jar:file:"))
128 return new JarFileResource(url, useCaches);
130 else if( url_string.startsWith( "jar:"))
132 return new JarResource(url, useCaches);
135 return new URLResource(url,null,useCaches);
140 /* ------------------------------------------------------------ */
141 /** Construct a resource from a string.
142 * @param resource A URL or filename.
143 * @throws MalformedURLException Problem accessing URI
144 * @return A Resource object.
146 public static Resource newResource(String resource)
147 throws MalformedURLException
149 return newResource(resource, __defaultUseCaches);
152 /* ------------------------------------------------------------ */
153 /** Construct a resource from a string.
154 * @param resource A URL or filename.
155 * @param useCaches controls URLConnection caching
156 * @return A Resource object.
157 * @throws MalformedURLException Problem accessing URI
159 public static Resource newResource(String resource, boolean useCaches)
160 throws MalformedURLException
165 // Try to format as a URL?
166 url = new URL(resource);
168 catch(MalformedURLException e)
170 if(!resource.startsWith("ftp:") &&
171 !resource.startsWith("file:") &&
172 !resource.startsWith("jar:"))
177 if (resource.startsWith("./"))
178 resource=resource.substring(2);
180 File file=new File(resource).getCanonicalFile();
181 return new FileResource(file);
185 LOG.debug(Log.EXCEPTION,e2);
191 LOG.warn("Bad Resource: "+resource);
196 return newResource(url, useCaches);
199 /* ------------------------------------------------------------ */
200 public static Resource newResource(File file)
202 return new FileResource(file);
205 /* ------------------------------------------------------------ */
206 /** Construct a system resource from a string.
207 * The resource is tried as classloader resource before being
208 * treated as a normal resource.
209 * @param resource Resource as string representation
210 * @return The new Resource
211 * @throws IOException Problem accessing resource.
213 public static Resource newSystemResource(String resource)
217 // Try to format as a URL?
218 ClassLoader loader=Thread.currentThread().getContextClassLoader();
223 url = loader.getResource(resource);
224 if (url == null && resource.startsWith("/"))
225 url = loader.getResource(resource.substring(1));
227 catch (IllegalArgumentException e)
229 // Catches scenario where a bad Windows path like "C:\dev" is
230 // improperly escaped, which various downstream classloaders
231 // tend to have a problem with
237 loader=Resource.class.getClassLoader();
240 url=loader.getResource(resource);
241 if (url==null && resource.startsWith("/"))
242 url=loader.getResource(resource.substring(1));
248 url=ClassLoader.getSystemResource(resource);
249 if (url==null && resource.startsWith("/"))
250 url=ClassLoader.getSystemResource(resource.substring(1));
256 return newResource(url);
259 /* ------------------------------------------------------------ */
260 /** Find a classpath resource.
262 public static Resource newClassPathResource(String resource)
264 return newClassPathResource(resource,true,false);
267 /* ------------------------------------------------------------ */
268 /** Find a classpath resource.
269 * The {@link java.lang.Class#getResource(String)} method is used to lookup the resource. If it is not
270 * found, then the {@link Loader#getResource(Class, String)} method is used.
271 * If it is still not found, then {@link ClassLoader#getSystemResource(String)} is used.
272 * Unlike {@link ClassLoader#getSystemResource(String)} this method does not check for normal resources.
273 * @param name The relative name of the resource
274 * @param useCaches True if URL caches are to be used.
275 * @param checkParents True if forced searching of parent Classloaders is performed to work around
276 * loaders with inverted priorities
277 * @return Resource or null
279 public static Resource newClassPathResource(String name,boolean useCaches,boolean checkParents)
281 URL url=Resource.class.getResource(name);
284 url=Loader.getResource(Resource.class,name);
287 return newResource(url,useCaches);
290 /* ------------------------------------------------------------ */
291 public static boolean isContainedIn (Resource r, Resource containingResource) throws MalformedURLException
293 return r.isContainedIn(containingResource);
296 /* ------------------------------------------------------------ */
298 protected void finalize()
303 /* ------------------------------------------------------------ */
304 public abstract boolean isContainedIn (Resource r) throws MalformedURLException;
307 /* ------------------------------------------------------------ */
308 /** Release any temporary resources held by the resource.
309 * @deprecated use {@link #close()}
311 public final void release()
316 /* ------------------------------------------------------------ */
317 /** Release any temporary resources held by the resource.
320 public abstract void close();
322 /* ------------------------------------------------------------ */
324 * Returns true if the respresened resource exists.
326 public abstract boolean exists();
329 /* ------------------------------------------------------------ */
331 * Returns true if the respresenetd resource is a container/directory.
332 * If the resource is not a file, resources ending with "/" are
333 * considered directories.
335 public abstract boolean isDirectory();
337 /* ------------------------------------------------------------ */
339 * Returns the last modified time
341 public abstract long lastModified();
344 /* ------------------------------------------------------------ */
346 * Return the length of the resource
348 public abstract long length();
351 /* ------------------------------------------------------------ */
353 * Returns an URL representing the given resource
355 // TODO: should deprecate this one and only use getURI()
356 public abstract URL getURL();
358 /* ------------------------------------------------------------ */
360 * Returns an URI representing the given resource
366 return getURL().toURI();
370 throw new RuntimeException(e);
375 /* ------------------------------------------------------------ */
377 * Returns an File representing the given resource or NULL if this
380 public abstract File getFile()
384 /* ------------------------------------------------------------ */
386 * Returns the name of the resource
388 public abstract String getName();
391 /* ------------------------------------------------------------ */
393 * Returns an input stream to the resource
395 public abstract InputStream getInputStream()
396 throws java.io.IOException;
398 /* ------------------------------------------------------------ */
400 * Returns an readable bytechannel to the resource or null if one is not available.
402 public abstract ReadableByteChannel getReadableByteChannel()
403 throws java.io.IOException;
405 /* ------------------------------------------------------------ */
407 * Deletes the given resource
409 // TODO: can throw IOException
410 public abstract boolean delete()
411 throws SecurityException;
413 /* ------------------------------------------------------------ */
415 * Rename the given resource
417 // TODO: can throw IOException
418 public abstract boolean renameTo( Resource dest)
419 throws SecurityException;
421 /* ------------------------------------------------------------ */
423 * Returns a list of resource names contained in the given resource
424 * The resource names are not URL encoded.
426 // TODO: can throw IOException
427 public abstract String[] list();
429 /* ------------------------------------------------------------ */
431 * Returns the resource contained inside the current resource with the
433 * @param path The path segment to add, which is not encoded
435 public abstract Resource addPath(String path)
436 throws IOException,MalformedURLException;
438 /* ------------------------------------------------------------ */
439 /** Get a resource from within this resource.
441 * This method is essentially an alias for {@link #addPath(String)}, but without checked exceptions.
442 * This method satisfied the {@link ResourceFactory} interface.
443 * @see org.eclipse.jetty.util.resource.ResourceFactory#getResource(java.lang.String)
446 public Resource getResource(String path)
450 return addPath(path);
459 /* ------------------------------------------------------------ */
463 public String encode(String uri)
468 /* ------------------------------------------------------------ */
469 public Object getAssociate()
474 /* ------------------------------------------------------------ */
475 public void setAssociate(Object o)
480 /* ------------------------------------------------------------ */
482 * @return The canonical Alias of this resource or null if none.
484 public URI getAlias()
489 /* ------------------------------------------------------------ */
490 /** Get the resource list as a HTML directory listing.
491 * @param base The base URL
492 * @param parent True if the parent directory should be included
493 * @return String of HTML
495 public String getListHTML(String base,boolean parent)
498 base=URIUtil.canonicalPath(base);
499 if (base==null || !isDirectory())
502 String[] ls = list();
507 String decodedBase = URIUtil.decodePath(base);
508 String title = "Directory: "+deTag(decodedBase);
510 StringBuilder buf=new StringBuilder(4096);
511 buf.append("<HTML><HEAD>");
512 buf.append("<LINK HREF=\"").append("jetty-dir.css").append("\" REL=\"stylesheet\" TYPE=\"text/css\"/><TITLE>");
514 buf.append("</TITLE></HEAD><BODY>\n<H1>");
516 buf.append("</H1>\n<TABLE BORDER=0>\n");
520 buf.append("<TR><TD><A HREF=\"");
521 buf.append(URIUtil.addPaths(base,"../"));
522 buf.append("\">Parent Directory</A></TD><TD></TD><TD></TD></TR>\n");
525 String encodedBase = hrefEncodeURI(base);
527 DateFormat dfmt=DateFormat.getDateTimeInstance(DateFormat.MEDIUM,
529 for (int i=0 ; i< ls.length ; i++)
531 Resource item = addPath(ls[i]);
533 buf.append("\n<TR><TD><A HREF=\"");
534 String path=URIUtil.addPaths(encodedBase,URIUtil.encodePath(ls[i]));
538 if (item.isDirectory() && !path.endsWith("/"))
539 buf.append(URIUtil.SLASH);
541 // URIUtil.encodePath(buf,path);
543 buf.append(deTag(ls[i]));
544 buf.append(" ");
545 buf.append("</A></TD><TD ALIGN=right>");
546 buf.append(item.length());
547 buf.append(" bytes </TD><TD>");
548 buf.append(dfmt.format(new Date(item.lastModified())));
549 buf.append("</TD></TR>");
551 buf.append("</TABLE>\n");
552 buf.append("</BODY></HTML>\n");
554 return buf.toString();
558 * Encode any characters that could break the URI string in an HREF.
559 * Such as <a href="/path/to;<script>Window.alert("XSS"+'%20'+"here");</script>">Link</a>
561 * The above example would parse incorrectly on various browsers as the "<" or '"' characters
562 * would end the href attribute value string prematurely.
564 * @param raw the raw text to encode.
565 * @return the defanged text.
567 private static String hrefEncodeURI(String raw)
569 StringBuffer buf = null;
572 for (int i=0;i<raw.length();i++)
574 char c=raw.charAt(i);
581 buf=new StringBuffer(raw.length()<<1);
588 for (int i=0;i<raw.length();i++)
590 char c=raw.charAt(i);
611 return buf.toString();
614 private static String deTag(String raw)
616 return StringUtil.sanitizeXmlString(raw);
619 /* ------------------------------------------------------------ */
622 * @param start First byte to write
623 * @param count Bytes to write or -1 for all of them.
625 public void writeTo(OutputStream out,long start,long count)
628 try (InputStream in = getInputStream())
634 IO.copy(in,out,count);
638 /* ------------------------------------------------------------ */
639 public void copyTo(File destination)
642 if (destination.exists())
643 throw new IllegalArgumentException(destination+" exists");
644 try (OutputStream out = new FileOutputStream(destination))
650 /* ------------------------------------------------------------ */
651 public String getWeakETag()
655 StringBuilder b = new StringBuilder(32);
658 String name=getName();
659 int length=name.length();
661 for (int i=0; i<length;i++)
662 lhash=31*lhash+name.charAt(i);
664 B64Code.encode(lastModified()^lhash,b);
665 B64Code.encode(length()^lhash,b);
669 catch (IOException e)
671 throw new RuntimeException(e);
675 /* ------------------------------------------------------------ */
676 public Collection<Resource> getAllResources()
680 ArrayList<Resource> deep=new ArrayList<>();
682 String[] list=list();
687 Resource r=addPath(i);
689 deep.addAll(r.getAllResources());
699 throw new IllegalStateException(e);
703 /* ------------------------------------------------------------ */
704 /** Generate a properly encoded URL from a {@link File} instance.
705 * @param file Target file.
706 * @return URL of the target file.
707 * @throws MalformedURLException
709 public static URL toURL(File file) throws MalformedURLException
711 return file.toURI().toURL();