]> WPIA git - gigi.git/blob - lib/jetty/org/eclipse/jetty/util/resource/Resource.java
updating jetty to jetty-9.2.16.v2016040
[gigi.git] / lib / jetty / org / eclipse / jetty / util / resource / Resource.java
1 //
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.
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, useCaches);
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     // TODO: should deprecate this one and only use getURI()
356     public abstract URL getURL();
357
358     /* ------------------------------------------------------------ */
359     /**
360      * Returns an URI representing the given resource
361      */
362     public URI getURI()
363     {
364         try
365         {
366             return getURL().toURI();
367         }
368         catch(Exception e)
369         {
370             throw new RuntimeException(e);
371         }
372     }
373     
374
375     /* ------------------------------------------------------------ */
376     /**
377      * Returns an File representing the given resource or NULL if this
378      * is not possible.
379      */
380     public abstract File getFile()
381         throws IOException;
382     
383
384     /* ------------------------------------------------------------ */
385     /**
386      * Returns the name of the resource
387      */
388     public abstract String getName();
389     
390
391     /* ------------------------------------------------------------ */
392     /**
393      * Returns an input stream to the resource
394      */
395     public abstract InputStream getInputStream()
396         throws java.io.IOException;
397     
398     /* ------------------------------------------------------------ */
399     /**
400      * Returns an readable bytechannel to the resource or null if one is not available.
401      */
402     public abstract ReadableByteChannel getReadableByteChannel()
403         throws java.io.IOException;
404
405     /* ------------------------------------------------------------ */
406     /**
407      * Deletes the given resource
408      */
409     // TODO: can throw IOException
410     public abstract boolean delete()
411         throws SecurityException;
412     
413     /* ------------------------------------------------------------ */
414     /**
415      * Rename the given resource
416      */
417     // TODO: can throw IOException
418     public abstract boolean renameTo( Resource dest)
419         throws SecurityException;
420     
421     /* ------------------------------------------------------------ */
422     /**
423      * Returns a list of resource names contained in the given resource
424      * The resource names are not URL encoded.
425      */
426     // TODO: can throw IOException
427     public abstract String[] list();
428
429     /* ------------------------------------------------------------ */
430     /**
431      * Returns the resource contained inside the current resource with the
432      * given name.
433      * @param path The path segment to add, which is not encoded
434      */
435     public abstract Resource addPath(String path)
436         throws IOException,MalformedURLException;
437
438     /* ------------------------------------------------------------ */
439     /** Get a resource from within this resource.
440      * <p>
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)
444      */
445     @Override
446     public Resource getResource(String path)
447     {
448         try
449         {
450             return addPath(path);
451         }
452         catch(Exception e)
453         {
454             LOG.debug(e);
455             return null;
456         }
457     }
458
459     /* ------------------------------------------------------------ */
460     /** 
461      * @deprecated
462      */
463     public String encode(String uri)
464     {
465         return null;
466     }
467         
468     /* ------------------------------------------------------------ */
469     public Object getAssociate()
470     {
471         return _associate;
472     }
473
474     /* ------------------------------------------------------------ */
475     public void setAssociate(Object o)
476     {
477         _associate=o;
478     }
479     
480     /* ------------------------------------------------------------ */
481     /**
482      * @return The canonical Alias of this resource or null if none.
483      */
484     public URI getAlias()
485     {
486         return null;
487     }
488     
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
494      */
495     public String getListHTML(String base,boolean parent)
496         throws IOException
497     {
498         base=URIUtil.canonicalPath(base);
499         if (base==null || !isDirectory())
500             return null;
501         
502         String[] ls = list();
503         if (ls==null)
504             return null;
505         Arrays.sort(ls);
506         
507         String decodedBase = URIUtil.decodePath(base);
508         String title = "Directory: "+deTag(decodedBase);
509
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>");
513         buf.append(title);
514         buf.append("</TITLE></HEAD><BODY>\n<H1>");
515         buf.append(title);
516         buf.append("</H1>\n<TABLE BORDER=0>\n");
517         
518         if (parent)
519         {
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");
523         }
524         
525         String encodedBase = hrefEncodeURI(base);
526         
527         DateFormat dfmt=DateFormat.getDateTimeInstance(DateFormat.MEDIUM,
528                                                        DateFormat.MEDIUM);
529         for (int i=0 ; i< ls.length ; i++)
530         {
531             Resource item = addPath(ls[i]);
532             
533             buf.append("\n<TR><TD><A HREF=\"");
534             String path=URIUtil.addPaths(encodedBase,URIUtil.encodePath(ls[i]));
535             
536             buf.append(path);
537             
538             if (item.isDirectory() && !path.endsWith("/"))
539                 buf.append(URIUtil.SLASH);
540             
541             // URIUtil.encodePath(buf,path);
542             buf.append("\">");
543             buf.append(deTag(ls[i]));
544             buf.append("&nbsp;");
545             buf.append("</A></TD><TD ALIGN=right>");
546             buf.append(item.length());
547             buf.append(" bytes&nbsp;</TD><TD>");
548             buf.append(dfmt.format(new Date(item.lastModified())));
549             buf.append("</TD></TR>");
550         }
551         buf.append("</TABLE>\n");
552         buf.append("</BODY></HTML>\n");
553         
554         return buf.toString();
555     }
556     
557     /**
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>
560      * 
561      * The above example would parse incorrectly on various browsers as the "<" or '"' characters
562      * would end the href attribute value string prematurely.
563      * 
564      * @param raw the raw text to encode.
565      * @return the defanged text.
566      */
567     private static String hrefEncodeURI(String raw) 
568     {
569         StringBuffer buf = null;
570
571         loop:
572         for (int i=0;i<raw.length();i++)
573         {
574             char c=raw.charAt(i);
575             switch(c)
576             {
577                 case '\'':
578                 case '"':
579                 case '<':
580                 case '>':
581                     buf=new StringBuffer(raw.length()<<1);
582                     break loop;
583             }
584         }
585         if (buf==null)
586             return raw;
587
588         for (int i=0;i<raw.length();i++)
589         {
590             char c=raw.charAt(i);       
591             switch(c)
592             {
593               case '"':
594                   buf.append("%22");
595                   continue;
596               case '\'':
597                   buf.append("%27");
598                   continue;
599               case '<':
600                   buf.append("%3C");
601                   continue;
602               case '>':
603                   buf.append("%3E");
604                   continue;
605               default:
606                   buf.append(c);
607                   continue;
608             }
609         }
610
611         return buf.toString();
612     }
613     
614     private static String deTag(String raw) 
615     {
616         return StringUtil.sanitizeXmlString(raw);
617     }
618     
619     /* ------------------------------------------------------------ */
620     /** 
621      * @param out 
622      * @param start First byte to write
623      * @param count Bytes to write or -1 for all of them.
624      */
625     public void writeTo(OutputStream out,long start,long count)
626         throws IOException
627     {
628         try (InputStream in = getInputStream())
629         {
630             in.skip(start);
631             if (count<0)
632                 IO.copy(in,out);
633             else
634                 IO.copy(in,out,count);
635         }
636     }    
637     
638     /* ------------------------------------------------------------ */
639     public void copyTo(File destination)
640         throws IOException
641     {
642         if (destination.exists())
643             throw new IllegalArgumentException(destination+" exists");
644         try (OutputStream out = new FileOutputStream(destination))
645         {
646             writeTo(out,0,-1);
647         }
648     }
649
650     /* ------------------------------------------------------------ */
651     public String getWeakETag()
652     {
653         try
654         {
655             StringBuilder b = new StringBuilder(32);
656             b.append("W/\"");
657             
658             String name=getName();
659             int length=name.length();
660             long lhash=0;
661             for (int i=0; i<length;i++)
662                 lhash=31*lhash+name.charAt(i);
663             
664             B64Code.encode(lastModified()^lhash,b);
665             B64Code.encode(length()^lhash,b);
666             b.append('"');
667             return b.toString();
668         } 
669         catch (IOException e)
670         {
671             throw new RuntimeException(e);
672         }
673     }
674     
675     /* ------------------------------------------------------------ */
676     public Collection<Resource> getAllResources()
677     {
678         try
679         {
680             ArrayList<Resource> deep=new ArrayList<>();
681             {
682                 String[] list=list();
683                 if (list!=null)
684                 {
685                     for (String i:list)
686                     {
687                         Resource r=addPath(i);
688                         if (r.isDirectory())
689                             deep.addAll(r.getAllResources());
690                         else
691                             deep.add(r);
692                     }
693                 }
694             }
695             return deep;
696         }
697         catch(Exception e)
698         {
699             throw new IllegalStateException(e);
700         }
701     }
702     
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 
708      */
709     public static URL toURL(File file) throws MalformedURLException
710     {
711         return file.toURI().toURL();
712     }
713 }