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