View Javadoc

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