View Javadoc

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