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