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