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