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