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.toString());
158             Log.debug(e);
159             throw new IllegalArgumentException(resourceBase);
160         }
161     }
162 
163     /* ------------------------------------------------------------ */
164     /**
165      * @return the cacheControl header to set on all static content.
166      */
167     public String getCacheControl()
168     {
169         return _cacheControl.toString();
170     }
171 
172     /* ------------------------------------------------------------ */
173     /**
174      * @param cacheControl the cacheControl header to set on all static content.
175      */
176     public void setCacheControl(String cacheControl)
177     {
178         _cacheControl=cacheControl==null?null:new ByteArrayBuffer(cacheControl);
179     }
180 
181     /* ------------------------------------------------------------ */
182     /* 
183      */
184     public Resource getResource(String path) throws MalformedURLException
185     {
186         if (path==null || !path.startsWith("/"))
187             throw new MalformedURLException(path);
188         
189         Resource base = _baseResource;
190         if (base==null)
191         {
192             if (_context==null)
193                 return null;            
194             base=_context.getBaseResource();
195             if (base==null)
196                 return null;
197         }
198 
199         try
200         {
201             path=URIUtil.canonicalPath(path);
202             Resource resource=base.addPath(path);
203             return resource;
204         }
205         catch(Exception e)
206         {
207             Log.ignore(e);
208         }
209                     
210         return null;
211     }
212 
213     /* ------------------------------------------------------------ */
214     protected Resource getResource(HttpServletRequest request) throws MalformedURLException
215     {
216         String path_info=request.getPathInfo();
217         if (path_info==null)
218             return null;
219         return getResource(path_info);
220     }
221 
222 
223     /* ------------------------------------------------------------ */
224     public String[] getWelcomeFiles()
225     {
226         return _welcomeFiles;
227     }
228 
229     /* ------------------------------------------------------------ */
230     public void setWelcomeFiles(String[] welcomeFiles)
231     {
232         _welcomeFiles=welcomeFiles;
233     }
234     
235     /* ------------------------------------------------------------ */
236     protected Resource getWelcome(Resource directory) throws MalformedURLException, IOException
237     {
238         for (int i=0;i<_welcomeFiles.length;i++)
239         {
240             Resource welcome=directory.addPath(_welcomeFiles[i]);
241             if (welcome.exists() && !welcome.isDirectory())
242                 return welcome;
243         }
244 
245         return null;
246     }
247 
248     /* ------------------------------------------------------------ */
249     /* 
250      * @see org.mortbay.jetty.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
251      */
252     public void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) throws IOException, ServletException
253     {
254         Request base_request = request instanceof Request?(Request)request:HttpConnection.getCurrentConnection().getRequest();
255         if (base_request.isHandled())
256             return;
257         
258         boolean skipContentBody = false;
259         if(!HttpMethods.GET.equals(request.getMethod()))
260         {
261             if(!HttpMethods.HEAD.equals(request.getMethod()))
262                 return;
263             skipContentBody = true;
264         }
265      
266         Resource resource=getResource(request);
267         
268         if (resource==null || !resource.exists())
269             return;
270         if (!_aliases && resource.getAlias()!=null)
271         {
272             Log.info(resource+" aliased to "+resource.getAlias());
273             return;
274         }
275 
276         // We are going to server something
277         base_request.setHandled(true);
278         
279         if (resource.isDirectory())
280         {
281             if (!request.getPathInfo().endsWith(URIUtil.SLASH))
282             {
283                 response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH)));
284                 return;
285             }
286             resource=getWelcome(resource);
287 
288             if (resource==null || !resource.exists() || resource.isDirectory())
289             {
290                 response.sendError(HttpServletResponse.SC_FORBIDDEN);
291                 return;
292             }
293         }
294         
295         // set some headers
296         long last_modified=resource.lastModified();
297         if (last_modified>0)
298         {
299             long if_modified=request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE);
300             if (if_modified>0 && last_modified/1000<=if_modified/1000)
301             {
302                 response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
303                 return;
304             }
305         }
306         
307         Buffer mime=_mimeTypes.getMimeByExtension(resource.toString());
308         if (mime==null)
309             mime=_mimeTypes.getMimeByExtension(request.getPathInfo());
310         
311         // set the headers
312         doResponseHeaders(response,resource,mime!=null?mime.toString():null);
313         response.setDateHeader(HttpHeaders.LAST_MODIFIED,last_modified);
314         if(skipContentBody)
315             return;
316         // Send the content
317         OutputStream out =null;
318         try {out = response.getOutputStream();}
319         catch(IllegalStateException e) {out = new WriterOutputStream(response.getWriter());}
320         
321         // See if a short direct method can be used?
322         if (out instanceof HttpConnection.Output)
323         {
324             // TODO file mapped buffers
325             ((HttpConnection.Output)out).sendContent(resource.getInputStream());
326         }
327         else
328         {
329             // Write content normally
330             resource.writeTo(out,0,resource.length());
331         }
332     }
333 
334     /* ------------------------------------------------------------ */
335     /** Set the response headers.
336      * This method is called to set the response headers such as content type and content length.
337      * May be extended to add additional headers.
338      * @param response
339      * @param resource
340      * @param mimeType
341      */
342     protected void doResponseHeaders(HttpServletResponse response, Resource resource, String mimeType)
343     {
344         if (mimeType!=null)
345             response.setContentType(mimeType);
346 
347         long length=resource.length();
348         
349         if (response instanceof Response)
350         {
351             HttpFields fields = ((Response)response).getHttpFields();
352 
353             if (length>0)
354                 fields.putLongField(HttpHeaders.CONTENT_LENGTH_BUFFER,length);
355                 
356             if (_cacheControl!=null)
357                 fields.put(HttpHeaders.CACHE_CONTROL_BUFFER,_cacheControl);
358         }
359         else
360         {
361             if (length>0)
362                 response.setHeader(HttpHeaders.CONTENT_LENGTH,TypeUtil.toString(length));
363                 
364             if (_cacheControl!=null)
365                 response.setHeader(HttpHeaders.CACHE_CONTROL,_cacheControl.toString());
366         }
367         
368     }
369 }