View Javadoc

1   // ========================================================================
2   // Copyright 1999-2005 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // Licensed under the Apache License, Version 2.0 (the "License");
5   // you may not use this file except in compliance with the License.
6   // You may obtain a copy of the License at 
7   // http://www.apache.org/licenses/LICENSE-2.0
8   // Unless required by applicable law or agreed to in writing, software
9   // distributed under the License is distributed on an "AS IS" BASIS,
10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11  // See the License for the specific language governing permissions and
12  // limitations under the License.
13  // ========================================================================
14  
15  package org.mortbay.jetty.handler;
16  
17  import java.io.IOException;
18  import java.io.OutputStream;
19  import java.net.MalformedURLException;
20  
21  import javax.servlet.ServletException;
22  import javax.servlet.http.HttpServletRequest;
23  import javax.servlet.http.HttpServletResponse;
24  
25  import org.mortbay.io.Buffer;
26  import org.mortbay.io.ByteArrayBuffer;
27  import org.mortbay.io.WriterOutputStream;
28  import org.mortbay.jetty.HttpConnection;
29  import org.mortbay.jetty.HttpFields;
30  import org.mortbay.jetty.HttpHeaders;
31  import org.mortbay.jetty.HttpMethods;
32  import org.mortbay.jetty.MimeTypes;
33  import org.mortbay.jetty.Request;
34  import org.mortbay.jetty.Response;
35  import org.mortbay.jetty.handler.ContextHandler.SContext;
36  import org.mortbay.log.Log;
37  import org.mortbay.resource.FileResource;
38  import org.mortbay.resource.Resource;
39  import org.mortbay.util.StringUtil;
40  import org.mortbay.util.TypeUtil;
41  import org.mortbay.util.URIUtil;
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 that cannot be handled are let pass (Eg no 404's)
50   * 
51   * @author Greg Wilkins (gregw)
52   * @org.apache.xbean.XBean
53   */
54  public class ResourceHandler extends AbstractHandler
55  {
56      ContextHandler _context;
57      Resource _baseResource;
58      String[] _welcomeFiles={"index.html"};
59      MimeTypes _mimeTypes = new MimeTypes();
60      ByteArrayBuffer _cacheControl;
61      boolean _aliases;
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       * @param aliases True if aliases are supported.
94       */
95      public void setAliases(boolean aliases)
96      {
97          _aliases = aliases;
98      }
99  
100     /* ------------------------------------------------------------ */
101     public void doStart()
102     throws Exception
103     {
104         SContext scontext = ContextHandler.getCurrentContext();
105         _context = (scontext==null?null:scontext.getContextHandler());
106         
107         if (!_aliases && !FileResource.getCheckAliases())
108             throw new IllegalStateException("Alias checking disabled");
109         
110         super.doStart();
111     }
112 
113     /* ------------------------------------------------------------ */
114     /**
115      * @return Returns the resourceBase.
116      */
117     public Resource getBaseResource()
118     {
119         if (_baseResource==null)
120             return null;
121         return _baseResource;
122     }
123 
124     /* ------------------------------------------------------------ */
125     /**
126      * @return Returns the base resource as a string.
127      */
128     public String getResourceBase()
129     {
130         if (_baseResource==null)
131             return null;
132         return _baseResource.toString();
133     }
134 
135     
136     /* ------------------------------------------------------------ */
137     /**
138      * @param base The resourceBase to set.
139      */
140     public void setBaseResource(Resource base) 
141     {
142         _baseResource=base;
143     }
144 
145     /* ------------------------------------------------------------ */
146     /**
147      * @param resourceBase The base resource as a string.
148      */
149     public void setResourceBase(String resourceBase) 
150     {
151         try
152         {
153             setBaseResource(Resource.newResource(resourceBase));
154         }
155         catch (Exception e)
156         {
157             Log.warn(e);
158             throw new IllegalArgumentException(resourceBase);
159         }
160     }
161 
162     /* ------------------------------------------------------------ */
163     /**
164      * @return the cacheControl header to set on all static content.
165      */
166     public String getCacheControl()
167     {
168         return _cacheControl.toString();
169     }
170 
171     /* ------------------------------------------------------------ */
172     /**
173      * @param cacheControl the cacheControl header to set on all static content.
174      */
175     public void setCacheControl(String cacheControl)
176     {
177         _cacheControl=cacheControl==null?null:new ByteArrayBuffer(cacheControl);
178     }
179 
180     /* ------------------------------------------------------------ */
181     /* 
182      */
183     public Resource getResource(String path) throws MalformedURLException
184     {
185         if (path==null || !path.startsWith("/"))
186             throw new MalformedURLException(path);
187         
188         Resource base = _baseResource;
189         if (base==null)
190         {
191             if (_context==null)
192                 return null;            
193             base=_context.getBaseResource();
194             if (base==null)
195                 return null;
196         }
197 
198         try
199         {
200             path=URIUtil.canonicalPath(path);
201             Resource resource=base.addPath(path);
202             return resource;
203         }
204         catch(Exception e)
205         {
206             Log.ignore(e);
207         }
208                     
209         return null;
210     }
211 
212     /* ------------------------------------------------------------ */
213     protected Resource getResource(HttpServletRequest request) throws MalformedURLException
214     {
215         String path_info=request.getPathInfo();
216         if (path_info==null)
217             return null;
218         return getResource(path_info);
219     }
220 
221 
222     /* ------------------------------------------------------------ */
223     public String[] getWelcomeFiles()
224     {
225         return _welcomeFiles;
226     }
227 
228     /* ------------------------------------------------------------ */
229     public void setWelcomeFiles(String[] welcomeFiles)
230     {
231         _welcomeFiles=welcomeFiles;
232     }
233     
234     /* ------------------------------------------------------------ */
235     protected Resource getWelcome(Resource directory) throws MalformedURLException, IOException
236     {
237         for (int i=0;i<_welcomeFiles.length;i++)
238         {
239             Resource welcome=directory.addPath(_welcomeFiles[i]);
240             if (welcome.exists() && !welcome.isDirectory())
241                 return welcome;
242         }
243 
244         return null;
245     }
246 
247     /* ------------------------------------------------------------ */
248     /* 
249      * @see org.mortbay.jetty.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
250      */
251     public void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) throws IOException, ServletException
252     {
253         Request base_request = request instanceof Request?(Request)request:HttpConnection.getCurrentConnection().getRequest();
254         if (base_request.isHandled())
255             return;
256         
257         boolean skipContentBody = false;
258         if(!HttpMethods.GET.equals(request.getMethod()))
259         {
260             if(!HttpMethods.HEAD.equals(request.getMethod()))
261                 return;
262             skipContentBody = true;
263         }
264      
265         Resource resource=getResource(request);
266         
267         if (resource==null || !resource.exists())
268             return;
269         if (!_aliases && resource.getAlias()!=null)
270         {
271             Log.info(resource+" aliased to "+resource.getAlias());
272             return;
273         }
274 
275         // We are going to server something
276         base_request.setHandled(true);
277         
278         if (resource.isDirectory())
279         {
280             if (!request.getPathInfo().endsWith(URIUtil.SLASH))
281             {
282                 response.sendRedirect(URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH));
283                 return;
284             }
285             resource=getWelcome(resource);
286 
287             if (resource==null || !resource.exists() || resource.isDirectory())
288             {
289                 response.sendError(HttpServletResponse.SC_FORBIDDEN);
290                 return;
291             }
292         }
293         
294         // set some headers
295         long last_modified=resource.lastModified();
296         if (last_modified>0)
297         {
298             long if_modified=request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE);
299             if (if_modified>0 && last_modified/1000<=if_modified/1000)
300             {
301                 response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
302                 return;
303             }
304         }
305         
306         Buffer mime=_mimeTypes.getMimeByExtension(resource.toString());
307         if (mime==null)
308             mime=_mimeTypes.getMimeByExtension(request.getPathInfo());
309         
310         // set the headers
311         doResponseHeaders(response,resource,mime!=null?mime.toString():null);
312         response.setDateHeader(HttpHeaders.LAST_MODIFIED,last_modified);
313         if(skipContentBody)
314             return;
315         // Send the content
316         OutputStream out =null;
317         try {out = response.getOutputStream();}
318         catch(IllegalStateException e) {out = new WriterOutputStream(response.getWriter());}
319         
320         // See if a short direct method can be used?
321         if (out instanceof HttpConnection.Output)
322         {
323             // TODO file mapped buffers
324             ((HttpConnection.Output)out).sendContent(resource.getInputStream());
325         }
326         else
327         {
328             // Write content normally
329             resource.writeTo(out,0,resource.length());
330         }
331     }
332 
333     /* ------------------------------------------------------------ */
334     /** Set the response headers.
335      * This method is called to set the response headers such as content type and content length.
336      * May be extended to add additional headers.
337      * @param response
338      * @param resource
339      * @param mimeType
340      */
341     protected void doResponseHeaders(HttpServletResponse response, Resource resource, String mimeType)
342     {
343         if (mimeType!=null)
344             response.setContentType(mimeType);
345 
346         long length=resource.length();
347         
348         if (response instanceof Response)
349         {
350             HttpFields fields = ((Response)response).getHttpFields();
351 
352             if (length>0)
353                 fields.putLongField(HttpHeaders.CONTENT_LENGTH_BUFFER,length);
354                 
355             if (_cacheControl!=null)
356                 fields.put(HttpHeaders.CACHE_CONTROL_BUFFER,_cacheControl);
357         }
358         else
359         {
360             if (length>0)
361                 response.setHeader(HttpHeaders.CONTENT_LENGTH,TypeUtil.toString(length));
362                 
363             if (_cacheControl!=null)
364                 response.setHeader(HttpHeaders.CACHE_CONTROL,_cacheControl.toString());
365         }
366         
367     }
368 }