View Javadoc

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