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