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.servlet;
15  
16  import java.io.IOException;
17  import java.io.InputStream;
18  import java.io.OutputStream;
19  import java.lang.reflect.Method;
20  import java.net.MalformedURLException;
21  import java.net.URL;
22  import java.util.Enumeration;
23  import java.util.List;
24  import java.util.Map;
25  import javax.servlet.RequestDispatcher;
26  import javax.servlet.ServletContext;
27  import javax.servlet.ServletException;
28  import javax.servlet.UnavailableException;
29  import javax.servlet.http.HttpServlet;
30  import javax.servlet.http.HttpServletRequest;
31  import javax.servlet.http.HttpServletResponse;
32  
33  import org.eclipse.jetty.http.HttpContent;
34  import org.eclipse.jetty.http.HttpFields;
35  import org.eclipse.jetty.http.HttpHeaderValues;
36  import org.eclipse.jetty.http.HttpHeaders;
37  import org.eclipse.jetty.http.HttpMethods;
38  import org.eclipse.jetty.http.MimeTypes;
39  import org.eclipse.jetty.io.Buffer;
40  import org.eclipse.jetty.io.ByteArrayBuffer;
41  import org.eclipse.jetty.io.WriterOutputStream;
42  import org.eclipse.jetty.server.Connector;
43  import org.eclipse.jetty.server.Dispatcher;
44  import org.eclipse.jetty.server.HttpConnection;
45  import org.eclipse.jetty.server.InclusiveByteRange;
46  import org.eclipse.jetty.server.ResourceCache;
47  import org.eclipse.jetty.server.Response;
48  import org.eclipse.jetty.server.handler.ContextHandler;
49  import org.eclipse.jetty.server.nio.NIOConnector;
50  import org.eclipse.jetty.server.ssl.SslConnector;
51  import org.eclipse.jetty.util.IO;
52  import org.eclipse.jetty.util.MultiPartOutputStream;
53  import org.eclipse.jetty.util.URIUtil;
54  import org.eclipse.jetty.util.log.Log;
55  import org.eclipse.jetty.util.resource.FileResource;
56  import org.eclipse.jetty.util.resource.Resource;
57  import org.eclipse.jetty.util.resource.ResourceCollection;
58  import org.eclipse.jetty.util.resource.ResourceFactory;
59  
60  
61  
62  /* ------------------------------------------------------------ */
63  /** The default servlet.
64   * This servlet, normally mapped to /, provides the handling for static
65   * content, OPTION and TRACE methods for the context.
66   * The following initParameters are supported, these can be set either
67   * on the servlet itself or as ServletContext initParameters with a prefix
68   * of org.eclipse.jetty.servlet.Default. :
69   * <PRE>
70   *   acceptRanges     If true, range requests and responses are
71   *                    supported
72   *
73   *   dirAllowed       If true, directory listings are returned if no
74   *                    welcome file is found. Else 403 Forbidden.
75   *
76   *   welcomeServlets  If true, attempt to dispatch to welcome files
77   *                    that are servlets, but only after no matching static
78   *                    resources could be found. If false, then a welcome
79   *                    file must exist on disk. If "exact", then exact
80   *                    servlet matches are supported without an existing file.
81   *                    Default is true.
82   *
83   *                    This must be false if you want directory listings,
84   *                    but have index.jsp in your welcome file list.
85   *
86   *   redirectWelcome  If true, welcome files are redirected rather than
87   *                    forwarded to.
88   *
89   *   gzip             If set to true, then static content will be served as
90   *                    gzip content encoded if a matching resource is
91   *                    found ending with ".gz"
92   *
93   *  resourceBase      Set to replace the context resource base
94   *
95   *  relativeResourceBase
96   *                    Set with a pathname relative to the base of the
97   *                    servlet context root. Useful for only serving static content out
98   *                    of only specific subdirectories.
99   *
100  *  aliases           If True, aliases of resources are allowed (eg. symbolic
101  *                    links and caps variations). May bypass security constraints.
102  *
103  *  maxCacheSize      The maximum total size of the cache or 0 for no cache.
104  *  maxCachedFileSize The maximum size of a file to cache
105  *  maxCachedFiles    The maximum number of files to cache
106  *  cacheType         Set to "bio", "nio" or "both" to determine the type resource cache.
107  *                    A bio cached buffer may be used by nio but is not as efficient as an
108  *                    nio buffer.  An nio cached buffer may not be used by bio.
109  *
110  *  useFileMappedBuffer
111  *                    If set to true, it will use mapped file buffer to serve static content
112  *                    when using NIO connector. Setting this value to false means that
113  *                    a direct buffer will be used instead of a mapped file buffer.
114  *                    By default, this is set to true.
115  *
116  *  cacheControl      If set, all static content will have this value set as the cache-control
117  *                    header.
118  *
119  *
120  * </PRE>
121  *
122  *
123  *
124  *
125  */
126 public class DefaultServlet extends HttpServlet implements ResourceFactory
127 {
128     private ServletContext _servletContext;
129     private ContextHandler _contextHandler;
130 
131     private boolean _acceptRanges=true;
132     private boolean _dirAllowed=true;
133     private boolean _welcomeServlets=true;
134     private boolean _welcomeExactServlets=false;
135     private boolean _redirectWelcome=false;
136     private boolean _gzip=true;
137 
138     private Resource _resourceBase;
139     private NIOResourceCache _nioCache;
140     private ResourceCache _bioCache;
141 
142     private MimeTypes _mimeTypes;
143     private String[] _welcomes;
144     private boolean _useFileMappedBuffer=false;
145     private ByteArrayBuffer _cacheControl;
146     private String _relativeResourceBase;
147     private ServletHandler _servletHandler;
148     private ServletHolder _defaultHolder;
149 
150 
151     /* ------------------------------------------------------------ */
152     @Override
153     public void init()
154         throws UnavailableException
155     {
156         _servletContext=getServletContext();
157         ContextHandler.Context scontext=ContextHandler.getCurrentContext();
158         if (scontext==null)
159             _contextHandler=((ContextHandler.Context)_servletContext).getContextHandler();
160         else
161             _contextHandler = ContextHandler.getCurrentContext().getContextHandler();
162 
163         _mimeTypes = _contextHandler.getMimeTypes();
164 
165         _welcomes = _contextHandler.getWelcomeFiles();
166         if (_welcomes==null)
167             _welcomes=new String[] {"index.html","index.jsp"};
168 
169         _acceptRanges=getInitBoolean("acceptRanges",_acceptRanges);
170         _dirAllowed=getInitBoolean("dirAllowed",_dirAllowed);
171         _redirectWelcome=getInitBoolean("redirectWelcome",_redirectWelcome);
172         _gzip=getInitBoolean("gzip",_gzip);
173 
174         if ("exact".equals(getInitParameter("welcomeServlets")))
175         {
176             _welcomeExactServlets=true;
177             _welcomeServlets=false;
178         }
179         else
180             _welcomeServlets=getInitBoolean("welcomeServlets", _welcomeServlets);
181 
182         if (getInitParameter("aliases")!=null)
183             _contextHandler.setAliases(getInitBoolean("aliases",false));
184 
185         boolean aliases=_contextHandler.isAliases();
186         if (!aliases && !FileResource.getCheckAliases())
187             throw new IllegalStateException("Alias checking disabled");
188         if (aliases)
189             _servletContext.log("Aliases are enabled");
190 
191         _useFileMappedBuffer=getInitBoolean("useFileMappedBuffer",_useFileMappedBuffer);
192 
193         _relativeResourceBase = getInitParameter("relativeResourceBase");
194 
195         String rb=getInitParameter("resourceBase");
196         if (rb!=null)
197         {
198             if (_relativeResourceBase!=null)
199                 throw new  UnavailableException("resourceBase & relativeResourceBase");
200             try{_resourceBase=_contextHandler.newResource(rb);}
201             catch (Exception e)
202             {
203                 Log.warn(Log.EXCEPTION,e);
204                 throw new UnavailableException(e.toString());
205             }
206         }
207 
208         String t=getInitParameter("cacheControl");
209         if (t!=null)
210             _cacheControl=new ByteArrayBuffer(t);
211 
212         try
213         {
214             String cache_type =getInitParameter("cacheType");
215             int max_cache_size=getInitInt("maxCacheSize", -2);
216             int max_cached_file_size=getInitInt("maxCachedFileSize", -2);
217             int max_cached_files=getInitInt("maxCachedFiles", -2);
218 
219             if (cache_type==null || "nio".equals(cache_type)|| "both".equals(cache_type))
220             {
221                 if (max_cache_size==-2 || max_cache_size>0)
222                 {
223                     _nioCache=new NIOResourceCache(_mimeTypes);
224                     _nioCache.setUseFileMappedBuffer(_useFileMappedBuffer);
225                     if (max_cache_size>0)
226                         _nioCache.setMaxCacheSize(max_cache_size);
227                     if (max_cached_file_size>=-1)
228                         _nioCache.setMaxCachedFileSize(max_cached_file_size);
229                     if (max_cached_files>=-1)
230                         _nioCache.setMaxCachedFiles(max_cached_files);
231                     _nioCache.start();
232                 }
233             }
234             if ("bio".equals(cache_type)|| "both".equals(cache_type))
235             {
236                 if (max_cache_size==-2 || max_cache_size>0)
237                 {
238                     _bioCache=new ResourceCache(_mimeTypes);
239                     if (max_cache_size>0)
240                         _bioCache.setMaxCacheSize(max_cache_size);
241                     if (max_cached_file_size>=-1)
242                         _bioCache.setMaxCachedFileSize(max_cached_file_size);
243                     if (max_cached_files>=-1)
244                         _bioCache.setMaxCachedFiles(max_cached_files);
245                     _bioCache.start();
246                 }
247             }
248             if (_nioCache==null)
249                 _bioCache=null;
250 
251         }
252         catch (Exception e)
253         {
254             Log.warn(Log.EXCEPTION,e);
255             throw new UnavailableException(e.toString());
256         }
257 
258         _servletHandler= (ServletHandler) _contextHandler.getChildHandlerByClass(ServletHandler.class);
259         for (ServletHolder h :_servletHandler.getServlets())
260             if (h.getServletInstance()==this)
261                 _defaultHolder=h;
262 
263         if (Log.isDebugEnabled()) Log.debug("resource base = "+_resourceBase);
264     }
265 
266     /* ------------------------------------------------------------ */
267     @Override
268     public String getInitParameter(String name)
269     {
270         String value=getServletContext().getInitParameter("org.eclipse.jetty.servlet.Default."+name);
271 	if (value==null)
272 	    value=super.getInitParameter(name);
273 	return value;
274     }
275 
276     /* ------------------------------------------------------------ */
277     private boolean getInitBoolean(String name, boolean dft)
278     {
279         String value=getInitParameter(name);
280         if (value==null || value.length()==0)
281             return dft;
282         return (value.startsWith("t")||
283                 value.startsWith("T")||
284                 value.startsWith("y")||
285                 value.startsWith("Y")||
286                 value.startsWith("1"));
287     }
288 
289     /* ------------------------------------------------------------ */
290     private int getInitInt(String name, int dft)
291     {
292         String value=getInitParameter(name);
293 	if (value==null)
294             value=getInitParameter(name);
295         if (value!=null && value.length()>0)
296             return Integer.parseInt(value);
297         return dft;
298     }
299 
300     /* ------------------------------------------------------------ */
301     /** get Resource to serve.
302      * Map a path to a resource. The default implementation calls
303      * HttpContext.getResource but derived servlets may provide
304      * their own mapping.
305      * @param pathInContext The path to find a resource for.
306      * @return The resource to serve.
307      */
308     public Resource getResource(String pathInContext)
309     {
310         Resource r=null;
311         if (_relativeResourceBase!=null)
312             pathInContext=URIUtil.addPaths(_relativeResourceBase,pathInContext);
313 
314         try
315         {
316             if (_resourceBase!=null)
317                 r = _resourceBase.addPath(pathInContext);
318             else
319             {
320                 URL u = _servletContext.getResource(pathInContext);
321                 r = _contextHandler.newResource(u);
322             }
323 
324             if (Log.isDebugEnabled())
325                 Log.debug("RESOURCE "+pathInContext+"="+r);
326         }
327         catch (IOException e)
328         {
329             Log.ignore(e);
330         }
331         return r;
332     }
333 
334     /* ------------------------------------------------------------ */
335     @Override
336     protected void doGet(HttpServletRequest request, HttpServletResponse response)
337     	throws ServletException, IOException
338     {
339         String servletPath=null;
340         String pathInfo=null;
341         Enumeration reqRanges = null;
342         Boolean included =request.getAttribute(Dispatcher.INCLUDE_REQUEST_URI)!=null;
343         if (included!=null && included.booleanValue())
344         {
345             servletPath=(String)request.getAttribute(Dispatcher.INCLUDE_SERVLET_PATH);
346             pathInfo=(String)request.getAttribute(Dispatcher.INCLUDE_PATH_INFO);
347             if (servletPath==null)
348             {
349                 servletPath=request.getServletPath();
350                 pathInfo=request.getPathInfo();
351             }
352         }
353         else
354         {
355             included = Boolean.FALSE;
356             servletPath = request.getServletPath();
357             pathInfo = request.getPathInfo();
358 
359             // Is this a Range request?
360             reqRanges = request.getHeaders(HttpHeaders.RANGE);
361             if (!hasDefinedRange(reqRanges))
362                 reqRanges = null;
363         }
364 
365         String pathInContext=URIUtil.addPaths(servletPath,pathInfo);
366         boolean endsWithSlash=pathInContext.endsWith(URIUtil.SLASH);
367 
368         // Can we gzip this request?
369         String pathInContextGz=null;
370         boolean gzip=false;
371         if (!included.booleanValue() && _gzip && reqRanges==null && !endsWithSlash )
372         {
373             String accept=request.getHeader(HttpHeaders.ACCEPT_ENCODING);
374             if (accept!=null && accept.indexOf("gzip")>=0)
375                 gzip=true;
376         }
377 
378         // Find the resource and content
379         Resource resource=null;
380         HttpContent content=null;
381 
382         Connector connector = HttpConnection.getCurrentConnection().getConnector();
383         ResourceCache cache=(connector instanceof NIOConnector && !(connector instanceof SslConnector)) ?_nioCache:_bioCache;
384         try
385         {
386             // Try gzipped content first
387             if (gzip)
388             {
389                 pathInContextGz=pathInContext+".gz";
390 
391                 if (cache==null)
392                 {
393                     resource=getResource(pathInContextGz);
394                 }
395                 else
396                 {
397                     content=cache.lookup(pathInContextGz,this);
398 
399                     if (content!=null)
400                         resource=content.getResource();
401                     else
402                         resource=getResource(pathInContextGz);
403                 }
404 
405                 if (resource==null || !resource.exists()|| resource.isDirectory())
406                 {
407                     if (cache!=null && content==null)
408                     {
409                         String real_path=_servletContext.getRealPath(pathInContextGz);
410                         if (real_path!=null)
411                             cache.miss(pathInContextGz,_contextHandler.newResource(real_path));
412                     }
413                     gzip=false;
414                     pathInContextGz=null;
415                 }
416             }
417 
418             // find resource
419             if (!gzip)
420             {
421                 if (cache==null)
422                     resource=getResource(pathInContext);
423                 else
424                 {
425                     content=cache.lookup(pathInContext,this);
426 
427                     if (content!=null)
428                         resource=content.getResource();
429                     else
430                         resource=getResource(pathInContext);
431                 }
432             }
433 
434             if (Log.isDebugEnabled())
435                 Log.debug("resource="+resource+(content!=null?" content":""));
436 
437             // Handle resource
438             if (resource==null || !resource.exists())
439                 response.sendError(HttpServletResponse.SC_NOT_FOUND);
440             else if (!resource.isDirectory())
441             {
442                 if (endsWithSlash && _contextHandler.isAliases() && pathInContext.length()>1)
443                 {
444                     String q=request.getQueryString();
445                     pathInContext=pathInContext.substring(0,pathInContext.length()-1);
446                     if (q!=null&&q.length()!=0)
447                         pathInContext+="?"+q;
448                     response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(_servletContext.getContextPath(),pathInContext)));
449                 }
450 		else
451 		{
452 		    // ensure we have content
453 		    if (content==null)
454 			content=new UnCachedContent(resource);
455 
456 		    if (included.booleanValue() || passConditionalHeaders(request,response, resource,content))
457 		    {
458 			if (gzip)
459 			{
460 			   response.setHeader(HttpHeaders.CONTENT_ENCODING,"gzip");
461 			   String mt=_servletContext.getMimeType(pathInContext);
462 			   if (mt!=null)
463 			       response.setContentType(mt);
464 			}
465 			sendData(request,response,included.booleanValue(),resource,content,reqRanges);
466 		    }
467 		}
468             }
469             else
470             {
471                 String welcome=null;
472 
473                 if (!endsWithSlash || (pathInContext.length()==1 && request.getAttribute("org.eclipse.jetty.server.nullPathInfo")!=null))
474                 {
475                     StringBuffer buf=request.getRequestURL();
476                     synchronized(buf)
477                     {
478                         int param=buf.lastIndexOf(";");
479                         if (param<0)
480                             buf.append('/');
481                         else
482                             buf.insert(param,'/');
483                         String q=request.getQueryString();
484                         if (q!=null&&q.length()!=0)
485                         {
486                             buf.append('?');
487                             buf.append(q);
488                         }
489                         response.setContentLength(0);
490                         response.sendRedirect(response.encodeRedirectURL(buf.toString()));
491                     }
492                 }
493                 // else look for a welcome file
494                 else if (null!=(welcome=getWelcomeFile(pathInContext)))
495                 {
496                     if (_redirectWelcome)
497                     {
498                         // Redirect to the index
499                         response.setContentLength(0);
500                         String q=request.getQueryString();
501                         if (q!=null&&q.length()!=0)
502                             response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths( _servletContext.getContextPath(),welcome)+"?"+q));
503                         else
504                             response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths( _servletContext.getContextPath(),welcome)));
505                     }
506                     else
507                     {
508                         // Forward to the index
509                         RequestDispatcher dispatcher=request.getRequestDispatcher(welcome);
510                         if (dispatcher!=null)
511                         {
512                             if (included.booleanValue())
513                                 dispatcher.include(request,response);
514                             else
515                             {
516                                 request.setAttribute("org.eclipse.jetty.server.welcome",welcome);
517                                 dispatcher.forward(request,response);
518                             }
519                         }
520                     }
521                 }
522                 else
523                 {
524                     content=new UnCachedContent(resource);
525                     if (included.booleanValue() || passConditionalHeaders(request,response, resource,content))
526                         sendDirectory(request,response,resource,pathInContext);
527                 }
528             }
529         }
530         catch(IllegalArgumentException e)
531         {
532             Log.warn(Log.EXCEPTION,e);
533             if(!response.isCommitted())
534                 response.sendError(500, e.getMessage());
535         }
536         finally
537         {
538             if (content!=null)
539                 content.release();
540             else if (resource!=null)
541                 resource.release();
542         }
543 
544     }
545 
546     /* ------------------------------------------------------------ */
547     private boolean hasDefinedRange(Enumeration reqRanges)
548     {
549         return (reqRanges!=null && reqRanges.hasMoreElements());
550     }
551 
552     /* ------------------------------------------------------------ */
553     @Override
554     protected void doPost(HttpServletRequest request, HttpServletResponse response)
555         throws ServletException, IOException
556     {
557         doGet(request,response);
558     }
559 
560     /* ------------------------------------------------------------ */
561     /* (non-Javadoc)
562      * @see javax.servlet.http.HttpServlet#doTrace(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
563      */
564     @Override
565     protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
566     {
567         resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
568     }
569 
570     /* ------------------------------------------------------------ */
571     @Override
572     protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
573         throws ServletException, IOException
574     {
575         resp.setHeader("Allow", "GET,HEAD,POST,OPTIONS");
576     }
577     
578     /* ------------------------------------------------------------ */
579     /**
580      * Finds a matching welcome file for the supplied {@link Resource}. This will be the first entry in the list of
581      * configured {@link #_welcomes welcome files} that existing within the directory referenced by the <code>Resource</code>.
582      * If the resource is not a directory, or no matching file is found, then it may look for a valid servlet mapping.
583      * If there is none, then <code>null</code> is returned.
584      * The list of welcome files is read from the {@link ContextHandler} for this servlet, or
585      * <code>"index.jsp" , "index.html"</code> if that is <code>null</code>.
586      * @param resource
587      * @return The path of the matching welcome file in context or null.
588      * @throws IOException
589      * @throws MalformedURLException
590      */
591     private String getWelcomeFile(String pathInContext) throws MalformedURLException, IOException
592     {
593         if (_welcomes==null)
594             return null;
595 
596         String welcome_servlet=null;
597         for (int i=0;i<_welcomes.length;i++)
598         {
599             String welcome_in_context=URIUtil.addPaths(pathInContext,_welcomes[i]);
600             Resource welcome=getResource(welcome_in_context);
601             if (welcome!=null && welcome.exists())
602                 return _welcomes[i];
603 
604             if ((_welcomeServlets || _welcomeExactServlets) && welcome_servlet==null)
605             {
606                 Map.Entry entry=_servletHandler.getHolderEntry(welcome_in_context);
607                 if (entry!=null && entry.getValue()!=_defaultHolder &&
608                         (_welcomeServlets || (_welcomeExactServlets && entry.getKey().equals(welcome_in_context))))
609                         welcome_servlet=welcome_in_context;
610 
611             }
612         }
613         return welcome_servlet;
614     }
615 
616     /* ------------------------------------------------------------ */
617     /* Check modification date headers.
618      */
619     protected boolean passConditionalHeaders(HttpServletRequest request,HttpServletResponse response, Resource resource, HttpContent content)
620     throws IOException
621     {
622         try
623         {
624             if (!request.getMethod().equals(HttpMethods.HEAD) )
625             {
626                 String ifms=request.getHeader(HttpHeaders.IF_MODIFIED_SINCE);
627                 if (ifms!=null)
628                 {
629                     if (content!=null)
630                     {
631                         Buffer mdlm=content.getLastModified();
632                         if (mdlm!=null)
633                         {
634                             if (ifms.equals(mdlm.toString()))
635                             {
636                                 response.reset();
637                                 response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
638                                 response.flushBuffer();
639                                 return false;
640                             }
641                         }
642                     }
643 
644                     long ifmsl=request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE);
645                     if (ifmsl!=-1)
646                     {
647                         if (resource.lastModified()/1000 <= ifmsl/1000)
648                         {
649                             response.reset();
650                             response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
651                             response.flushBuffer();
652                             return false;
653                         }
654                     }
655                 }
656 
657                 // Parse the if[un]modified dates and compare to resource
658                 long date=request.getDateHeader(HttpHeaders.IF_UNMODIFIED_SINCE);
659 
660                 if (date!=-1)
661                 {
662                     if (resource.lastModified()/1000 > date/1000)
663                     {
664                         response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
665                         return false;
666                     }
667                 }
668 
669             }
670         }
671         catch(IllegalArgumentException iae)
672         {
673             if(!response.isCommitted())
674                 response.sendError(400, iae.getMessage());
675             throw iae;
676         }
677         return true;
678     }
679 
680 
681     /* ------------------------------------------------------------------- */
682     protected void sendDirectory(HttpServletRequest request,
683                                  HttpServletResponse response,
684                                  Resource resource,
685                                  String pathInContext)
686     throws IOException
687     {
688         if (!_dirAllowed)
689         {
690             response.sendError(HttpServletResponse.SC_FORBIDDEN);
691             return;
692         }
693 
694         byte[] data=null;
695         String base = URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH);
696 
697         // handle ResourceCollection
698         if (_resourceBase instanceof ResourceCollection)
699             resource=_resourceBase.addPath(pathInContext);
700         else if (_contextHandler.getBaseResource() instanceof ResourceCollection)
701             resource=_contextHandler.getBaseResource().addPath(pathInContext);
702 
703         String dir = resource.getListHTML(base,pathInContext.length()>1);
704         if (dir==null)
705         {
706             response.sendError(HttpServletResponse.SC_FORBIDDEN,
707             "No directory");
708             return;
709         }
710 
711         data=dir.getBytes("UTF-8");
712         response.setContentType("text/html; charset=UTF-8");
713         response.setContentLength(data.length);
714         response.getOutputStream().write(data);
715     }
716 
717     /* ------------------------------------------------------------ */
718     protected void sendData(HttpServletRequest request,
719                             HttpServletResponse response,
720                             boolean include,
721                             Resource resource,
722                             HttpContent content,
723                             Enumeration reqRanges)
724     throws IOException
725     {
726         long content_length=content==null?resource.length():content.getContentLength();
727 
728         // Get the output stream (or writer)
729         OutputStream out =null;
730         try{out = response.getOutputStream();}
731         catch(IllegalStateException e) {out = new WriterOutputStream(response.getWriter());}
732 
733         if ( reqRanges == null || !reqRanges.hasMoreElements() || content_length<0)
734         {
735             //  if there were no ranges, send entire entity
736             if (include)
737             {
738                 resource.writeTo(out,0,content_length);
739             }
740             else
741             {
742                 // See if a direct methods can be used?
743                 if (out instanceof HttpConnection.Output)
744                 {
745                     if (response instanceof Response)
746                     {
747                         writeOptionHeaders(((Response)response).getHttpFields());
748                         ((HttpConnection.Output)out).sendContent(content);
749                     }
750                     else if (content.getBuffer()!=null)
751                     {
752                         writeHeaders(response,content,content_length);
753                         ((HttpConnection.Output)out).sendContent(content.getBuffer());
754                     }
755                     else
756                     {
757                         writeHeaders(response,content,content_length);
758                         resource.writeTo(out,0,content_length);
759                     }
760                 }
761                 else
762                 {
763                     // Write content normally
764                     writeHeaders(response,content,content_length);
765                     resource.writeTo(out,0,content_length);
766                 }
767             }
768         }
769         else
770         {
771             // Parse the satisfiable ranges
772             List ranges =InclusiveByteRange.satisfiableRanges(reqRanges,content_length);
773 
774             //  if there are no satisfiable ranges, send 416 response
775             if (ranges==null || ranges.size()==0)
776             {
777                 writeHeaders(response, content, content_length);
778                 response.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
779                 response.setHeader(HttpHeaders.CONTENT_RANGE,
780                         InclusiveByteRange.to416HeaderRangeString(content_length));
781                 resource.writeTo(out,0,content_length);
782                 return;
783             }
784 
785             //  if there is only a single valid range (must be satisfiable
786             //  since were here now), send that range with a 216 response
787             if ( ranges.size()== 1)
788             {
789                 InclusiveByteRange singleSatisfiableRange =
790                     (InclusiveByteRange)ranges.get(0);
791                 long singleLength = singleSatisfiableRange.getSize(content_length);
792                 writeHeaders(response,content,singleLength                     );
793                 response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
794                 response.setHeader(HttpHeaders.CONTENT_RANGE,
795                         singleSatisfiableRange.toHeaderRangeString(content_length));
796                 resource.writeTo(out,singleSatisfiableRange.getFirst(content_length),singleLength);
797                 return;
798             }
799 
800             //  multiple non-overlapping valid ranges cause a multipart
801             //  216 response which does not require an overall
802             //  content-length header
803             //
804             writeHeaders(response,content,-1);
805             String mimetype=content.getContentType().toString();
806             MultiPartOutputStream multi = new MultiPartOutputStream(out);
807             response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
808 
809             // If the request has a "Request-Range" header then we need to
810             // send an old style multipart/x-byteranges Content-Type. This
811             // keeps Netscape and acrobat happy. This is what Apache does.
812             String ctp;
813             if (request.getHeader(HttpHeaders.REQUEST_RANGE)!=null)
814                 ctp = "multipart/x-byteranges; boundary=";
815             else
816                 ctp = "multipart/byteranges; boundary=";
817             response.setContentType(ctp+multi.getBoundary());
818 
819             InputStream in=resource.getInputStream();
820             long pos=0;
821 
822             // calculate the content-length
823             int length=0;
824             String[] header = new String[ranges.size()];
825             for (int i=0;i<ranges.size();i++)
826             {
827                 InclusiveByteRange ibr = (InclusiveByteRange) ranges.get(i);
828                 header[i]=ibr.toHeaderRangeString(content_length);
829                 length+=
830                     ((i>0)?2:0)+
831                     2+multi.getBoundary().length()+2+
832                     HttpHeaders.CONTENT_TYPE.length()+2+mimetype.length()+2+
833                     HttpHeaders.CONTENT_RANGE.length()+2+header[i].length()+2+
834                     2+
835                     (ibr.getLast(content_length)-ibr.getFirst(content_length))+1;
836             }
837             length+=2+2+multi.getBoundary().length()+2+2;
838             response.setContentLength(length);
839 
840             for (int i=0;i<ranges.size();i++)
841             {
842                 InclusiveByteRange ibr = (InclusiveByteRange) ranges.get(i);
843                 multi.startPart(mimetype,new String[]{HttpHeaders.CONTENT_RANGE+": "+header[i]});
844 
845                 long start=ibr.getFirst(content_length);
846                 long size=ibr.getSize(content_length);
847                 if (in!=null)
848                 {
849                     // Handle non cached resource
850                     if (start<pos)
851                     {
852                         in.close();
853                         in=resource.getInputStream();
854                         pos=0;
855                     }
856                     if (pos<start)
857                     {
858                         in.skip(start-pos);
859                         pos=start;
860                     }
861                     IO.copy(in,multi,size);
862                     pos+=size;
863                 }
864                 else
865                     // Handle cached resource
866                     (resource).writeTo(multi,start,size);
867 
868             }
869             if (in!=null)
870                 in.close();
871             multi.close();
872         }
873         return;
874     }
875 
876     /* ------------------------------------------------------------ */
877     protected void writeHeaders(HttpServletResponse response,HttpContent content,long count)
878         throws IOException
879     {
880         if (content.getContentType()!=null && response.getContentType()==null)
881             response.setContentType(content.getContentType().toString());
882 
883         if (response instanceof Response)
884         {
885             Response r=(Response)response;
886             HttpFields fields = r.getHttpFields();
887 
888             if (content.getLastModified()!=null)
889                 fields.put(HttpHeaders.LAST_MODIFIED_BUFFER,content.getLastModified(),content.getResource().lastModified());
890             else if (content.getResource()!=null)
891             {
892                 long lml=content.getResource().lastModified();
893                 if (lml!=-1)
894                     fields.putDateField(HttpHeaders.LAST_MODIFIED_BUFFER,lml);
895             }
896 
897             if (count != -1)
898                 r.setLongContentLength(count);
899 
900             writeOptionHeaders(fields);
901         }
902         else
903         {
904             long lml=content.getResource().lastModified();
905             if (lml>=0)
906                 response.setDateHeader(HttpHeaders.LAST_MODIFIED,lml);
907 
908             if (count != -1)
909             {
910                 if (count<Integer.MAX_VALUE)
911                     response.setContentLength((int)count);
912                 else
913                     response.setHeader(HttpHeaders.CONTENT_LENGTH,Long.toString(count));
914             }
915 
916             writeOptionHeaders(response);
917         }
918     }
919 
920     /* ------------------------------------------------------------ */
921     protected void writeOptionHeaders(HttpFields fields) throws IOException
922     {
923         if (_acceptRanges)
924             fields.put(HttpHeaders.ACCEPT_RANGES_BUFFER,HttpHeaderValues.BYTES_BUFFER);
925 
926         if (_cacheControl!=null)
927             fields.put(HttpHeaders.CACHE_CONTROL_BUFFER,_cacheControl);
928     }
929 
930     /* ------------------------------------------------------------ */
931     protected void writeOptionHeaders(HttpServletResponse response) throws IOException
932     {
933         if (_acceptRanges)
934             response.setHeader(HttpHeaders.ACCEPT_RANGES,"bytes");
935 
936         if (_cacheControl!=null)
937             response.setHeader(HttpHeaders.CACHE_CONTROL,_cacheControl.toString());
938     }
939 
940     /* ------------------------------------------------------------ */
941     /*
942      * @see javax.servlet.Servlet#destroy()
943      */
944     @Override
945     public void destroy()
946     {
947         try
948         {
949             if (_nioCache!=null)
950                 _nioCache.stop();
951         }
952         catch(Exception e)
953         {
954             Log.warn(Log.EXCEPTION,e);
955         }
956         finally
957         {
958 	    try
959 	    {
960 		if (_bioCache!=null)
961 		    _bioCache.stop();
962 	    }
963 	    catch(Exception e)
964 	    {
965 		Log.warn(Log.EXCEPTION,e);
966 	    }
967 	    finally
968 	    {
969                 super.destroy();
970 	    }
971         }
972     }
973 
974     /* ------------------------------------------------------------ */
975     /* ------------------------------------------------------------ */
976     /* ------------------------------------------------------------ */
977     private class UnCachedContent implements HttpContent
978     {
979         Resource _resource;
980 
981         UnCachedContent(Resource resource)
982         {
983             _resource=resource;
984         }
985 
986         /* ------------------------------------------------------------ */
987         public Buffer getContentType()
988         {
989             return _mimeTypes.getMimeByExtension(_resource.toString());
990         }
991 
992         /* ------------------------------------------------------------ */
993         public Buffer getLastModified()
994         {
995             return null;
996         }
997 
998         /* ------------------------------------------------------------ */
999         public Buffer getBuffer()
1000         {
1001             return null;
1002         }
1003 
1004         /* ------------------------------------------------------------ */
1005         public long getContentLength()
1006         {
1007             return _resource.length();
1008         }
1009 
1010         /* ------------------------------------------------------------ */
1011         public InputStream getInputStream() throws IOException
1012         {
1013             return _resource.getInputStream();
1014         }
1015 
1016         /* ------------------------------------------------------------ */
1017         public Resource getResource()
1018         {
1019             return _resource;
1020         }
1021 
1022         /* ------------------------------------------------------------ */
1023         public void release()
1024         {
1025             _resource.release();
1026             _resource=null;
1027         }
1028 
1029     }
1030 }