View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2012 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.server.handler;
20  
21  import java.io.IOException;
22  import java.io.OutputStream;
23  import java.net.MalformedURLException;
24  
25  import javax.servlet.RequestDispatcher;
26  import javax.servlet.ServletException;
27  import javax.servlet.http.HttpServletRequest;
28  import javax.servlet.http.HttpServletResponse;
29  
30  import org.eclipse.jetty.http.HttpFields;
31  import org.eclipse.jetty.http.HttpHeader;
32  import org.eclipse.jetty.http.HttpMethod;
33  import org.eclipse.jetty.http.HttpStatus;
34  import org.eclipse.jetty.http.MimeTypes;
35  import org.eclipse.jetty.io.WriterOutputStream;
36  import org.eclipse.jetty.server.Request;
37  import org.eclipse.jetty.server.Response;
38  import org.eclipse.jetty.server.handler.ContextHandler.Context;
39  import org.eclipse.jetty.util.URIUtil;
40  import org.eclipse.jetty.util.log.Log;
41  import org.eclipse.jetty.util.log.Logger;
42  import org.eclipse.jetty.util.resource.FileResource;
43  import org.eclipse.jetty.util.resource.Resource;
44  
45  
46  /* ------------------------------------------------------------ */
47  /** Resource Handler.
48   *
49   * This handle will serve static content and handle If-Modified-Since headers.
50   * No caching is done.
51   * Requests for resources that do not exist are let pass (Eg no 404's).
52   *
53   *
54   * @org.apache.xbean.XBean
55   */
56  public class ResourceHandler extends HandlerWrapper
57  {
58      private static final Logger LOG = Log.getLogger(ResourceHandler.class);
59  
60      ContextHandler _context;
61      Resource _baseResource;
62      Resource _defaultStylesheet;
63      Resource _stylesheet;
64      String[] _welcomeFiles={"index.html"};
65      MimeTypes _mimeTypes = new MimeTypes();
66      String _cacheControl;
67      boolean _aliases;
68      boolean _directory;
69      boolean _etags;
70  
71      /* ------------------------------------------------------------ */
72      public ResourceHandler()
73      {
74  
75      }
76  
77      /* ------------------------------------------------------------ */
78      public MimeTypes getMimeTypes()
79      {
80          return _mimeTypes;
81      }
82  
83      /* ------------------------------------------------------------ */
84      public void setMimeTypes(MimeTypes mimeTypes)
85      {
86          _mimeTypes = mimeTypes;
87      }
88  
89      /* ------------------------------------------------------------ */
90      /**
91       * @return True if resource aliases are allowed.
92       */
93      public boolean isAliases()
94      {
95          return _aliases;
96      }
97  
98      /* ------------------------------------------------------------ */
99      /**
100      * Set if resource aliases (eg symlink, 8.3 names, case insensitivity) are allowed.
101      * Allowing aliases can significantly increase security vulnerabilities.
102      * If this handler is deployed inside a ContextHandler, then the
103      * {@link ContextHandler#isAliases()} takes precedent.
104      * @param aliases True if aliases are supported.
105      */
106     public void setAliases(boolean aliases)
107     {
108         _aliases = aliases;
109     }
110 
111     /* ------------------------------------------------------------ */
112     /** Get the directory option.
113      * @return true if directories are listed.
114      */
115     public boolean isDirectoriesListed()
116     {
117         return _directory;
118     }
119 
120     /* ------------------------------------------------------------ */
121     /** Set the directory.
122      * @param directory true if directories are listed.
123      */
124     public void setDirectoriesListed(boolean directory)
125     {
126         _directory = directory;
127     }
128 
129     /* ------------------------------------------------------------ */
130     /**
131      * @return True if ETag processing is done
132      */
133     public boolean isEtags()
134     {
135         return _etags;
136     }
137 
138     /* ------------------------------------------------------------ */
139     /**
140      * @param etags True if ETag processing is done
141      */
142     public void setEtags(boolean etags)
143     {
144         _etags = etags;
145     }
146 
147     /* ------------------------------------------------------------ */
148     @Override
149     public void doStart()
150     throws Exception
151     {
152         Context scontext = ContextHandler.getCurrentContext();
153         _context = (scontext==null?null:scontext.getContextHandler());
154 
155         if (_context!=null)
156             _aliases=_context.isAliases();
157 
158         if (!_aliases && !FileResource.getCheckAliases())
159             throw new IllegalStateException("Alias checking disabled");
160 
161         super.doStart();
162     }
163 
164     /* ------------------------------------------------------------ */
165     /**
166      * @return Returns the resourceBase.
167      */
168     public Resource getBaseResource()
169     {
170         if (_baseResource==null)
171             return null;
172         return _baseResource;
173     }
174 
175     /* ------------------------------------------------------------ */
176     /**
177      * @return Returns the base resource as a string.
178      */
179     public String getResourceBase()
180     {
181         if (_baseResource==null)
182             return null;
183         return _baseResource.toString();
184     }
185 
186 
187     /* ------------------------------------------------------------ */
188     /**
189      * @param base The resourceBase to set.
190      */
191     public void setBaseResource(Resource base)
192     {
193         _baseResource=base;
194     }
195 
196     /* ------------------------------------------------------------ */
197     /**
198      * @param resourceBase The base resource as a string.
199      */
200     public void setResourceBase(String resourceBase)
201     {
202         try
203         {
204             setBaseResource(Resource.newResource(resourceBase));
205         }
206         catch (Exception e)
207         {
208             LOG.warn(e.toString());
209             LOG.debug(e);
210             throw new IllegalArgumentException(resourceBase);
211         }
212     }
213 
214     /* ------------------------------------------------------------ */
215     /**
216      * @return Returns the stylesheet as a Resource.
217      */
218     public Resource getStylesheet()
219     {
220     	if(_stylesheet != null)
221     	{
222     	    return _stylesheet;
223     	}
224     	else
225     	{
226     	    if(_defaultStylesheet == null)
227     	    {
228     	        try
229     	        {
230     	            _defaultStylesheet =  Resource.newResource(this.getClass().getResource("/jetty-dir.css"));
231     	        }
232     	        catch(IOException e)
233     	        {
234     	            LOG.warn(e.toString());
235     	            LOG.debug(e);
236     	        }
237     	    }
238     	    return _defaultStylesheet;
239     	}
240     }
241 
242     /* ------------------------------------------------------------ */
243     /**
244      * @param stylesheet The location of the stylesheet to be used as a String.
245      */
246     public void setStylesheet(String stylesheet)
247     {
248         try
249         {
250             _stylesheet = Resource.newResource(stylesheet);
251             if(!_stylesheet.exists())
252             {
253                 LOG.warn("unable to find custom stylesheet: " + stylesheet);
254                 _stylesheet = null;
255             }
256         }
257     	catch(Exception e)
258     	{
259     		LOG.warn(e.toString());
260             LOG.debug(e);
261             throw new IllegalArgumentException(stylesheet.toString());
262     	}
263     }
264 
265     /* ------------------------------------------------------------ */
266     /**
267      * @return the cacheControl header to set on all static content.
268      */
269     public String getCacheControl()
270     {
271         return _cacheControl.toString();
272     }
273 
274     /* ------------------------------------------------------------ */
275     /**
276      * @param cacheControl the cacheControl header to set on all static content.
277      */
278     public void setCacheControl(String cacheControl)
279     {
280         _cacheControl=cacheControl;
281     }
282 
283     /* ------------------------------------------------------------ */
284     /*
285      */
286     public Resource getResource(String path) throws MalformedURLException
287     {
288         if (path==null || !path.startsWith("/"))
289             throw new MalformedURLException(path);
290 
291         Resource base = _baseResource;
292         if (base==null)
293         {
294             if (_context==null)
295                 return null;
296             base=_context.getBaseResource();
297             if (base==null)
298                 return null;
299         }
300 
301         try
302         {
303             path=URIUtil.canonicalPath(path);
304             return base.addPath(path);
305         }
306         catch(Exception e)
307         {
308             LOG.ignore(e);
309         }
310 
311         return null;
312     }
313 
314     /* ------------------------------------------------------------ */
315     protected Resource getResource(HttpServletRequest request) throws MalformedURLException
316     {
317         String servletPath;
318         String pathInfo;
319         Boolean included = request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI) != null;
320         if (included != null && included.booleanValue())
321         {
322             servletPath = (String)request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
323             pathInfo = (String)request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);
324 
325             if (servletPath == null && pathInfo == null)
326             {
327                 servletPath = request.getServletPath();
328                 pathInfo = request.getPathInfo();
329             }
330         }
331         else
332         {
333             servletPath = request.getServletPath();
334             pathInfo = request.getPathInfo();
335         }
336 
337         String pathInContext=URIUtil.addPaths(servletPath,pathInfo);
338         return getResource(pathInContext);
339     }
340 
341 
342     /* ------------------------------------------------------------ */
343     public String[] getWelcomeFiles()
344     {
345         return _welcomeFiles;
346     }
347 
348     /* ------------------------------------------------------------ */
349     public void setWelcomeFiles(String[] welcomeFiles)
350     {
351         _welcomeFiles=welcomeFiles;
352     }
353 
354     /* ------------------------------------------------------------ */
355     protected Resource getWelcome(Resource directory) throws MalformedURLException, IOException
356     {
357         for (int i=0;i<_welcomeFiles.length;i++)
358         {
359             Resource welcome=directory.addPath(_welcomeFiles[i]);
360             if (welcome.exists() && !welcome.isDirectory())
361                 return welcome;
362         }
363 
364         return null;
365     }
366 
367     /* ------------------------------------------------------------ */
368     /*
369      * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
370      */
371     @Override
372     public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
373     {
374         if (baseRequest.isHandled())
375             return;
376 
377         boolean skipContentBody = false;
378 
379         if(!HttpMethod.GET.is(request.getMethod()))
380         {
381             if(!HttpMethod.HEAD.is(request.getMethod()))
382             {
383                 //try another handler
384                 super.handle(target, baseRequest, request, response);
385                 return;
386             }
387             skipContentBody = true;
388         }
389 
390         Resource resource = getResource(request);
391 
392         if (resource==null || !resource.exists())
393         {
394             if (target.endsWith("/jetty-dir.css"))
395             {
396                 resource = getStylesheet();
397                 if (resource==null)
398                     return;
399                 response.setContentType("text/css");
400             }
401             else
402             {
403                 //no resource - try other handlers
404                 super.handle(target, baseRequest, request, response);
405                 return;
406             }
407         }
408 
409         if (!_aliases && resource.getAlias()!=null)
410         {
411             LOG.info(resource+" aliased to "+resource.getAlias());
412             return;
413         }
414 
415         // We are going to serve something
416         baseRequest.setHandled(true);
417 
418         if (resource.isDirectory())
419         {
420             if (!request.getPathInfo().endsWith(URIUtil.SLASH))
421             {
422                 response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH)));
423                 return;
424             }
425 
426             Resource welcome=getWelcome(resource);
427             if (welcome!=null && welcome.exists())
428                 resource=welcome;
429             else
430             {
431                 doDirectory(request,response,resource);
432                 baseRequest.setHandled(true);
433                 return;
434             }
435         }
436 
437         // set some headers
438         long last_modified=resource.lastModified();
439         String etag=null;
440         if (_etags)
441         {
442             // simple handling of only a single etag
443             String ifnm = request.getHeader(HttpHeader.IF_NONE_MATCH.asString());
444             etag=resource.getWeakETag();
445             if (ifnm!=null && resource!=null && ifnm.equals(etag))
446             {
447                 response.setStatus(HttpStatus.NOT_MODIFIED_304);
448                 baseRequest.getResponse().getHttpFields().put(HttpHeader.ETAG,etag);
449                 return;
450             }
451         }
452         
453         
454         if (last_modified>0)
455         {
456             long if_modified=request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.asString());
457             if (if_modified>0 && last_modified/1000<=if_modified/1000)
458             {
459                 response.setStatus(HttpStatus.NOT_MODIFIED_304);
460                 return;
461             }
462         }
463 
464         String mime=_mimeTypes.getMimeByExtension(resource.toString());
465         if (mime==null)
466             mime=_mimeTypes.getMimeByExtension(request.getPathInfo());
467 
468         // set the headers
469         doResponseHeaders(response,resource,mime!=null?mime.toString():null);
470         response.setDateHeader(HttpHeader.LAST_MODIFIED.asString(),last_modified);
471         if (_etags)
472             baseRequest.getResponse().getHttpFields().put(HttpHeader.ETAG,etag);
473         
474         if(skipContentBody)
475             return;
476         // Send the content
477         OutputStream out =null;
478         try {out = response.getOutputStream();}
479         catch(IllegalStateException e) {out = new WriterOutputStream(response.getWriter());}
480 
481         // See if a short direct method can be used?
482         /* TODO file mapped buffers
483         if (out instanceof HttpOutput)
484         {
485             // TODO file mapped buffers
486             ((HttpOutput)out).send(resource.getInputStream());
487         }
488         else*/
489         {
490             // Write content normally
491             resource.writeTo(out,0,resource.length());
492         }
493     }
494 
495     /* ------------------------------------------------------------ */
496     protected void doDirectory(HttpServletRequest request,HttpServletResponse response, Resource resource)
497         throws IOException
498     {
499         if (_directory)
500         {
501             String listing = resource.getListHTML(request.getRequestURI(),request.getPathInfo().lastIndexOf("/") > 0);
502             response.setContentType("text/html; charset=UTF-8");
503             response.getWriter().println(listing);
504         }
505         else
506             response.sendError(HttpStatus.FORBIDDEN_403);
507     }
508 
509     /* ------------------------------------------------------------ */
510     /** Set the response headers.
511      * This method is called to set the response headers such as content type and content length.
512      * May be extended to add additional headers.
513      * @param response
514      * @param resource
515      * @param mimeType
516      */
517     protected void doResponseHeaders(HttpServletResponse response, Resource resource, String mimeType)
518     {
519         if (mimeType!=null)
520             response.setContentType(mimeType);
521 
522         long length=resource.length();
523 
524         if (response instanceof Response)
525         {
526             HttpFields fields = ((Response)response).getHttpFields();
527 
528             if (length>0)
529                 fields.putLongField(HttpHeader.CONTENT_LENGTH,length);
530 
531             if (_cacheControl!=null)
532                 fields.put(HttpHeader.CACHE_CONTROL,_cacheControl);
533         }
534         else
535         {
536             if (length>0)
537                 response.setHeader(HttpHeader.CONTENT_LENGTH.asString(),Long.toString(length));
538 
539             if (_cacheControl!=null)
540                 response.setHeader(HttpHeader.CACHE_CONTROL.asString(),_cacheControl.toString());
541         }
542 
543     }
544 }