View Javadoc

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