View Javadoc

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