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