View Javadoc

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