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