1 //
2 // ========================================================================
3 // Copyright (c) 1995-2013 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 */
50 public abstract class Resource implements ResourceFactory, Closeable
51 {
52 private static final Logger LOG = Log.getLogger(Resource.class);
53 public static boolean __defaultUseCaches = true;
54 volatile Object _associate;
55
56 /* ------------------------------------------------------------ */
57 /**
58 * Change the default setting for url connection caches.
59 * Subsequent URLConnections will use this default.
60 * @param useCaches
61 */
62 public static void setDefaultUseCaches (boolean useCaches)
63 {
64 __defaultUseCaches=useCaches;
65 }
66
67 /* ------------------------------------------------------------ */
68 public static boolean getDefaultUseCaches ()
69 {
70 return __defaultUseCaches;
71 }
72
73 /* ------------------------------------------------------------ */
74 /** Construct a resource from a uri.
75 * @param uri A URI.
76 * @return A Resource object.
77 * @throws IOException Problem accessing URI
78 */
79 public static Resource newResource(URI uri)
80 throws IOException
81 {
82 return newResource(uri.toURL());
83 }
84
85 /* ------------------------------------------------------------ */
86 /** Construct a resource from a url.
87 * @param url A URL.
88 * @return A Resource object.
89 * @throws IOException Problem accessing URL
90 */
91 public static Resource newResource(URL url)
92 throws IOException
93 {
94 return newResource(url, __defaultUseCaches);
95 }
96
97 /* ------------------------------------------------------------ */
98 /**
99 * Construct a resource from a url.
100 * @param url the url for which to make the resource
101 * @param useCaches true enables URLConnection caching if applicable to the type of resource
102 * @return
103 */
104 static Resource newResource(URL url, boolean useCaches)
105 {
106 if (url==null)
107 return null;
108
109 String url_string=url.toExternalForm();
110 if( url_string.startsWith( "file:"))
111 {
112 try
113 {
114 FileResource fileResource= new FileResource(url);
115 return fileResource;
116 }
117 catch(Exception e)
118 {
119 LOG.warn(e.toString());
120 LOG.debug(Log.EXCEPTION,e);
121 return new BadResource(url,e.toString());
122 }
123 }
124 else if( url_string.startsWith( "jar:file:"))
125 {
126 return new JarFileResource(url, useCaches);
127 }
128 else if( url_string.startsWith( "jar:"))
129 {
130 return new JarResource(url, useCaches);
131 }
132
133 return new URLResource(url,null,useCaches);
134 }
135
136
137
138 /* ------------------------------------------------------------ */
139 /** Construct a resource from a string.
140 * @param resource A URL or filename.
141 * @return A Resource object.
142 */
143 public static Resource newResource(String resource)
144 throws MalformedURLException, IOException
145 {
146 return newResource(resource, __defaultUseCaches);
147 }
148
149 /* ------------------------------------------------------------ */
150 /** Construct a resource from a string.
151 * @param resource A URL or filename.
152 * @param useCaches controls URLConnection caching
153 * @return A Resource object.
154 */
155 public static Resource newResource(String resource, boolean useCaches)
156 throws MalformedURLException, IOException
157 {
158 URL url=null;
159 try
160 {
161 // Try to format as a URL?
162 url = new URL(resource);
163 }
164 catch(MalformedURLException e)
165 {
166 if(!resource.startsWith("ftp:") &&
167 !resource.startsWith("file:") &&
168 !resource.startsWith("jar:"))
169 {
170 try
171 {
172 // It's a file.
173 if (resource.startsWith("./"))
174 resource=resource.substring(2);
175
176 File file=new File(resource).getCanonicalFile();
177 return new FileResource(file);
178 }
179 catch(Exception e2)
180 {
181 LOG.debug(Log.EXCEPTION,e2);
182 throw e;
183 }
184 }
185 else
186 {
187 LOG.warn("Bad Resource: "+resource);
188 throw e;
189 }
190 }
191
192 return newResource(url);
193 }
194
195 /* ------------------------------------------------------------ */
196 public static Resource newResource(File file)
197 {
198 return new FileResource(file);
199 }
200
201 /* ------------------------------------------------------------ */
202 /** Construct a system resource from a string.
203 * The resource is tried as classloader resource before being
204 * treated as a normal resource.
205 * @param resource Resource as string representation
206 * @return The new Resource
207 * @throws IOException Problem accessing resource.
208 */
209 public static Resource newSystemResource(String resource)
210 throws IOException
211 {
212 URL url=null;
213 // Try to format as a URL?
214 ClassLoader 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=ClassLoader.getSystemResource(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)} 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 url=Loader.getResource(Resource.class,name);
281 if (url==null)
282 return null;
283 return newResource(url,useCaches);
284 }
285
286 /* ------------------------------------------------------------ */
287 public static boolean isContainedIn (Resource r, Resource containingResource) throws MalformedURLException
288 {
289 return r.isContainedIn(containingResource);
290 }
291
292 /* ------------------------------------------------------------ */
293 @Override
294 protected void finalize()
295 {
296 close();
297 }
298
299 /* ------------------------------------------------------------ */
300 public abstract boolean isContainedIn (Resource r) throws MalformedURLException;
301
302
303 /* ------------------------------------------------------------ */
304 /** Release any temporary resources held by the resource.
305 * @deprecated use {@link #close()}
306 */
307 public final void release()
308 {
309 close();
310 }
311
312 /* ------------------------------------------------------------ */
313 /** Release any temporary resources held by the resource.
314 */
315 @Override
316 public abstract void close();
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 readable bytechannel to the resource or null if one is not available.
396 */
397 public abstract ReadableByteChannel getReadableByteChannel()
398 throws java.io.IOException;
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 is not encoded
426 */
427 public abstract Resource addPath(String path)
428 throws IOException,MalformedURLException;
429
430 /* ------------------------------------------------------------ */
431 /** Get a resource from within this resource.
432 * <p>
433 * This method is essentially an alias for {@link #addPath(String)}, but without checked exceptions.
434 * This method satisfied the {@link ResourceFactory} interface.
435 * @see org.eclipse.jetty.util.resource.ResourceFactory#getResource(java.lang.String)
436 */
437 @Override
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 /**
453 * @deprecated
454 */
455 public String encode(String uri)
456 {
457 return null;
458 }
459
460 /* ------------------------------------------------------------ */
461 public Object getAssociate()
462 {
463 return _associate;
464 }
465
466 /* ------------------------------------------------------------ */
467 public void setAssociate(Object o)
468 {
469 _associate=o;
470 }
471
472 /* ------------------------------------------------------------ */
473 /**
474 * @return The canonical Alias of this resource or null if none.
475 */
476 public URI getAlias()
477 {
478 return null;
479 }
480
481 /* ------------------------------------------------------------ */
482 /** Get the resource list as a HTML directory listing.
483 * @param base The base URL
484 * @param parent True if the parent directory should be included
485 * @return String of HTML
486 */
487 public String getListHTML(String base,boolean parent)
488 throws IOException
489 {
490 base=URIUtil.canonicalPath(base);
491 if (base==null || !isDirectory())
492 return null;
493
494 String[] ls = list();
495 if (ls==null)
496 return null;
497 Arrays.sort(ls);
498
499 String decodedBase = URIUtil.decodePath(base);
500 String title = "Directory: "+deTag(decodedBase);
501
502 StringBuilder buf=new StringBuilder(4096);
503 buf.append("<HTML><HEAD>");
504 buf.append("<LINK HREF=\"").append("jetty-dir.css").append("\" REL=\"stylesheet\" TYPE=\"text/css\"/><TITLE>");
505 buf.append(title);
506 buf.append("</TITLE></HEAD><BODY>\n<H1>");
507 buf.append(title);
508 buf.append("</H1>\n<TABLE BORDER=0>\n");
509
510 if (parent)
511 {
512 buf.append("<TR><TD><A HREF=\"");
513 buf.append(URIUtil.addPaths(base,"../"));
514 buf.append("\">Parent Directory</A></TD><TD></TD><TD></TD></TR>\n");
515 }
516
517 String encodedBase = hrefEncodeURI(base);
518
519 DateFormat dfmt=DateFormat.getDateTimeInstance(DateFormat.MEDIUM,
520 DateFormat.MEDIUM);
521 for (int i=0 ; i< ls.length ; i++)
522 {
523 Resource item = addPath(ls[i]);
524
525 buf.append("\n<TR><TD><A HREF=\"");
526 String path=URIUtil.addPaths(encodedBase,URIUtil.encodePath(ls[i]));
527
528 buf.append(path);
529
530 if (item.isDirectory() && !path.endsWith("/"))
531 buf.append(URIUtil.SLASH);
532
533 // URIUtil.encodePath(buf,path);
534 buf.append("\">");
535 buf.append(deTag(ls[i]));
536 buf.append(" ");
537 buf.append("</A></TD><TD ALIGN=right>");
538 buf.append(item.length());
539 buf.append(" bytes </TD><TD>");
540 buf.append(dfmt.format(new Date(item.lastModified())));
541 buf.append("</TD></TR>");
542 }
543 buf.append("</TABLE>\n");
544 buf.append("</BODY></HTML>\n");
545
546 return buf.toString();
547 }
548
549 /**
550 * Encode any characters that could break the URI string in an HREF.
551 * Such as <a href="/path/to;<script>Window.alert("XSS"+'%20'+"here");</script>">Link</a>
552 *
553 * The above example would parse incorrectly on various browsers as the "<" or '"' characters
554 * would end the href attribute value string prematurely.
555 *
556 * @param raw the raw text to encode.
557 * @return the defanged text.
558 */
559 private static String hrefEncodeURI(String raw)
560 {
561 StringBuffer buf = null;
562
563 loop:
564 for (int i=0;i<raw.length();i++)
565 {
566 char c=raw.charAt(i);
567 switch(c)
568 {
569 case '\'':
570 case '"':
571 case '<':
572 case '>':
573 buf=new StringBuffer(raw.length()<<1);
574 break loop;
575 }
576 }
577 if (buf==null)
578 return raw;
579
580 for (int i=0;i<raw.length();i++)
581 {
582 char c=raw.charAt(i);
583 switch(c)
584 {
585 case '"':
586 buf.append("%22");
587 continue;
588 case '\'':
589 buf.append("%27");
590 continue;
591 case '<':
592 buf.append("%3C");
593 continue;
594 case '>':
595 buf.append("%3E");
596 continue;
597 default:
598 buf.append(c);
599 continue;
600 }
601 }
602
603 return buf.toString();
604 }
605
606 private static String deTag(String raw)
607 {
608 return StringUtil.replace( StringUtil.replace(raw,"<","<"), ">", ">");
609 }
610
611 /* ------------------------------------------------------------ */
612 /**
613 * @param out
614 * @param start First byte to write
615 * @param count Bytes to write or -1 for all of them.
616 */
617 public void writeTo(OutputStream out,long start,long count)
618 throws IOException
619 {
620 try (InputStream in = getInputStream())
621 {
622 in.skip(start);
623 if (count<0)
624 IO.copy(in,out);
625 else
626 IO.copy(in,out,count);
627 }
628 }
629
630 /* ------------------------------------------------------------ */
631 public void copyTo(File destination)
632 throws IOException
633 {
634 if (destination.exists())
635 throw new IllegalArgumentException(destination+" exists");
636 try (OutputStream out = new FileOutputStream(destination))
637 {
638 writeTo(out,0,-1);
639 }
640 }
641
642 /* ------------------------------------------------------------ */
643 public String getWeakETag()
644 {
645 try
646 {
647 StringBuilder b = new StringBuilder(32);
648 b.append("W/\"");
649
650 String name=getName();
651 int length=name.length();
652 long lhash=0;
653 for (int i=0; i<length;i++)
654 lhash=31*lhash+name.charAt(i);
655
656 B64Code.encode(lastModified()^lhash,b);
657 B64Code.encode(length()^lhash,b);
658 b.append('"');
659 return b.toString();
660 }
661 catch (IOException e)
662 {
663 throw new RuntimeException(e);
664 }
665 }
666
667 /* ------------------------------------------------------------ */
668 public Collection<Resource> getAllResources()
669 {
670 try
671 {
672 ArrayList<Resource> deep=new ArrayList<>();
673 {
674 String[] list=list();
675 if (list!=null)
676 {
677 for (String i:list)
678 {
679 Resource r=addPath(i);
680 if (r.isDirectory())
681 deep.addAll(r.getAllResources());
682 else
683 deep.add(r);
684 }
685 }
686 }
687 return deep;
688 }
689 catch(Exception e)
690 {
691 throw new IllegalStateException(e);
692 }
693 }
694
695 /* ------------------------------------------------------------ */
696 /** Generate a properly encoded URL from a {@link File} instance.
697 * @param file Target file.
698 * @return URL of the target file.
699 * @throws MalformedURLException
700 */
701 public static URL toURL(File file) throws MalformedURLException
702 {
703 return file.toURI().toURL();
704 }
705 }