]> WPIA git - gigi.git/blobdiff - lib/jetty/org/eclipse/jetty/servlet/DefaultServlet.java
Importing upstream Jetty jetty-9.2.1.v20140609
[gigi.git] / lib / jetty / org / eclipse / jetty / servlet / DefaultServlet.java
diff --git a/lib/jetty/org/eclipse/jetty/servlet/DefaultServlet.java b/lib/jetty/org/eclipse/jetty/servlet/DefaultServlet.java
new file mode 100644 (file)
index 0000000..c378739
--- /dev/null
@@ -0,0 +1,1100 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.ByteBuffer;
+import java.util.Enumeration;
+import java.util.List;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.UnavailableException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpContent;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpGenerator.CachedHttpField;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.http.PathMap.MappedEntry;
+import org.eclipse.jetty.io.WriterOutputStream;
+import org.eclipse.jetty.server.HttpOutput;
+import org.eclipse.jetty.server.InclusiveByteRange;
+import org.eclipse.jetty.server.ResourceCache;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.MultiPartOutputStream;
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.resource.ResourceCollection;
+import org.eclipse.jetty.util.resource.ResourceFactory;
+
+
+
+/* ------------------------------------------------------------ */
+/** The default servlet.
+ * This servlet, normally mapped to /, provides the handling for static
+ * content, OPTION and TRACE methods for the context.
+ * The following initParameters are supported, these can be set either
+ * on the servlet itself or as ServletContext initParameters with a prefix
+ * of org.eclipse.jetty.servlet.Default. :
+ * <PRE>
+ *  acceptRanges      If true, range requests and responses are
+ *                    supported
+ *
+ *  dirAllowed        If true, directory listings are returned if no
+ *                    welcome file is found. Else 403 Forbidden.
+ *
+ *  welcomeServlets   If true, attempt to dispatch to welcome files
+ *                    that are servlets, but only after no matching static
+ *                    resources could be found. If false, then a welcome
+ *                    file must exist on disk. If "exact", then exact
+ *                    servlet matches are supported without an existing file.
+ *                    Default is true.
+ *
+ *                    This must be false if you want directory listings,
+ *                    but have index.jsp in your welcome file list.
+ *
+ *  redirectWelcome   If true, welcome files are redirected rather than
+ *                    forwarded to.
+ *
+ *  gzip              If set to true, then static content will be served as
+ *                    gzip content encoded if a matching resource is
+ *                    found ending with ".gz"
+ *
+ *  resourceBase      Set to replace the context resource base
+ *
+ *  resourceCache     If set, this is a context attribute name, which the servlet
+ *                    will use to look for a shared ResourceCache instance.
+ *
+ *  relativeResourceBase
+ *                    Set with a pathname relative to the base of the
+ *                    servlet context root. Useful for only serving static content out
+ *                    of only specific subdirectories.
+ *
+ *  pathInfoOnly      If true, only the path info will be applied to the resourceBase
+ *
+ *  stylesheet       Set with the location of an optional stylesheet that will be used
+ *                    to decorate the directory listing html.
+ *
+ *  etags             If True, weak etags will be generated and handled.
+ *
+ *  maxCacheSize      The maximum total size of the cache or 0 for no cache.
+ *  maxCachedFileSize The maximum size of a file to cache
+ *  maxCachedFiles    The maximum number of files to cache
+ *
+ *  useFileMappedBuffer
+ *                    If set to true, it will use mapped file buffer to serve static content
+ *                    when using NIO connector. Setting this value to false means that
+ *                    a direct buffer will be used instead of a mapped file buffer.
+ *                    By default, this is set to true.
+ *
+ *  cacheControl      If set, all static content will have this value set as the cache-control
+ *                    header.
+ *
+ *
+ * </PRE>
+ *
+ *
+ *
+ *
+ */
+public class DefaultServlet extends HttpServlet implements ResourceFactory
+{
+    private static final Logger LOG = Log.getLogger(DefaultServlet.class);
+
+    private static final long serialVersionUID = 4930458713846881193L;
+    
+    private static final CachedHttpField ACCEPT_RANGES = new CachedHttpField(HttpHeader.ACCEPT_RANGES, "bytes");
+    
+    private ServletContext _servletContext;
+    private ContextHandler _contextHandler;
+
+    private boolean _acceptRanges=true;
+    private boolean _dirAllowed=true;
+    private boolean _welcomeServlets=false;
+    private boolean _welcomeExactServlets=false;
+    private boolean _redirectWelcome=false;
+    private boolean _gzip=false;
+    private boolean _pathInfoOnly=false;
+    private boolean _etags=false;
+
+    private Resource _resourceBase;
+    private ResourceCache _cache;
+
+    private MimeTypes _mimeTypes;
+    private String[] _welcomes;
+    private Resource _stylesheet;
+    private boolean _useFileMappedBuffer=false;
+    private HttpField _cacheControl;
+    private String _relativeResourceBase;
+    private ServletHandler _servletHandler;
+    private ServletHolder _defaultHolder;
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void init()
+    throws UnavailableException
+    {
+        _servletContext=getServletContext();
+        _contextHandler = initContextHandler(_servletContext);
+
+        _mimeTypes = _contextHandler.getMimeTypes();
+
+        _welcomes = _contextHandler.getWelcomeFiles();
+        if (_welcomes==null)
+            _welcomes=new String[] {"index.html","index.jsp"};
+
+        _acceptRanges=getInitBoolean("acceptRanges",_acceptRanges);
+        _dirAllowed=getInitBoolean("dirAllowed",_dirAllowed);
+        _redirectWelcome=getInitBoolean("redirectWelcome",_redirectWelcome);
+        _gzip=getInitBoolean("gzip",_gzip);
+        _pathInfoOnly=getInitBoolean("pathInfoOnly",_pathInfoOnly);
+
+        if ("exact".equals(getInitParameter("welcomeServlets")))
+        {
+            _welcomeExactServlets=true;
+            _welcomeServlets=false;
+        }
+        else
+            _welcomeServlets=getInitBoolean("welcomeServlets", _welcomeServlets);
+
+        _useFileMappedBuffer=getInitBoolean("useFileMappedBuffer",_useFileMappedBuffer);
+
+        _relativeResourceBase = getInitParameter("relativeResourceBase");
+
+        String rb=getInitParameter("resourceBase");
+        if (rb!=null)
+        {
+            if (_relativeResourceBase!=null)
+                throw new  UnavailableException("resourceBase & relativeResourceBase");
+            try{_resourceBase=_contextHandler.newResource(rb);}
+            catch (Exception e)
+            {
+                LOG.warn(Log.EXCEPTION,e);
+                throw new UnavailableException(e.toString());
+            }
+        }
+
+        String css=getInitParameter("stylesheet");
+        try
+        {
+            if(css!=null)
+            {
+                _stylesheet = Resource.newResource(css);
+                if(!_stylesheet.exists())
+                {
+                    LOG.warn("!" + css);
+                    _stylesheet = null;
+                }
+            }
+            if(_stylesheet == null)
+            {
+                _stylesheet = Resource.newResource(this.getClass().getResource("/jetty-dir.css"));
+            }
+        }
+        catch(Exception e)
+        {
+            LOG.warn(e.toString());
+            LOG.debug(e);
+        }
+
+        String cc=getInitParameter("cacheControl");
+        if (cc!=null)
+            _cacheControl=new CachedHttpField(HttpHeader.CACHE_CONTROL, cc);
+        
+        String resourceCache = getInitParameter("resourceCache");
+        int max_cache_size=getInitInt("maxCacheSize", -2);
+        int max_cached_file_size=getInitInt("maxCachedFileSize", -2);
+        int max_cached_files=getInitInt("maxCachedFiles", -2);
+        if (resourceCache!=null)
+        {
+            if (max_cache_size!=-1 || max_cached_file_size!= -2 || max_cached_files!=-2)
+                LOG.debug("ignoring resource cache configuration, using resourceCache attribute");
+            if (_relativeResourceBase!=null || _resourceBase!=null)
+                throw new UnavailableException("resourceCache specified with resource bases");
+            _cache=(ResourceCache)_servletContext.getAttribute(resourceCache);
+
+            LOG.debug("Cache {}={}",resourceCache,_cache);
+        }
+
+        _etags = getInitBoolean("etags",_etags);
+        
+        try
+        {
+            if (_cache==null && max_cached_files>0)
+            {
+                _cache= new ResourceCache(null,this,_mimeTypes,_useFileMappedBuffer,_etags);
+
+                if (max_cache_size>0)
+                    _cache.setMaxCacheSize(max_cache_size);
+                if (max_cached_file_size>=-1)
+                    _cache.setMaxCachedFileSize(max_cached_file_size);
+                if (max_cached_files>=-1)
+                    _cache.setMaxCachedFiles(max_cached_files);
+            }
+        }
+        catch (Exception e)
+        {
+            LOG.warn(Log.EXCEPTION,e);
+            throw new UnavailableException(e.toString());
+        }
+
+        _servletHandler= _contextHandler.getChildHandlerByClass(ServletHandler.class);
+        for (ServletHolder h :_servletHandler.getServlets())
+            if (h.getServletInstance()==this)
+                _defaultHolder=h;
+
+        
+        if (LOG.isDebugEnabled())
+            LOG.debug("resource base = "+_resourceBase);
+    }
+
+    /**
+     * Compute the field _contextHandler.<br/>
+     * In the case where the DefaultServlet is deployed on the HttpService it is likely that
+     * this method needs to be overwritten to unwrap the ServletContext facade until we reach
+     * the original jetty's ContextHandler.
+     * @param servletContext The servletContext of this servlet.
+     * @return the jetty's ContextHandler for this servletContext.
+     */
+    protected ContextHandler initContextHandler(ServletContext servletContext)
+    {
+        ContextHandler.Context scontext=ContextHandler.getCurrentContext();
+        if (scontext==null)
+        {
+            if (servletContext instanceof ContextHandler.Context)
+                return ((ContextHandler.Context)servletContext).getContextHandler();
+            else
+                throw new IllegalArgumentException("The servletContext " + servletContext + " " +
+                    servletContext.getClass().getName() + " is not " + ContextHandler.Context.class.getName());
+        }
+        else
+            return ContextHandler.getCurrentContext().getContextHandler();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String getInitParameter(String name)
+    {
+        String value=getServletContext().getInitParameter("org.eclipse.jetty.servlet.Default."+name);
+        if (value==null)
+            value=super.getInitParameter(name);
+        return value;
+    }
+
+    /* ------------------------------------------------------------ */
+    private boolean getInitBoolean(String name, boolean dft)
+    {
+        String value=getInitParameter(name);
+        if (value==null || value.length()==0)
+            return dft;
+        return (value.startsWith("t")||
+                value.startsWith("T")||
+                value.startsWith("y")||
+                value.startsWith("Y")||
+                value.startsWith("1"));
+    }
+
+    /* ------------------------------------------------------------ */
+    private int getInitInt(String name, int dft)
+    {
+        String value=getInitParameter(name);
+        if (value==null)
+            value=getInitParameter(name);
+        if (value!=null && value.length()>0)
+            return Integer.parseInt(value);
+        return dft;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** get Resource to serve.
+     * Map a path to a resource. The default implementation calls
+     * HttpContext.getResource but derived servlets may provide
+     * their own mapping.
+     * @param pathInContext The path to find a resource for.
+     * @return The resource to serve.
+     */
+    @Override
+    public Resource getResource(String pathInContext)
+    {
+        Resource r=null;
+        if (_relativeResourceBase!=null)
+            pathInContext=URIUtil.addPaths(_relativeResourceBase,pathInContext);
+
+        try
+        {
+            if (_resourceBase!=null)
+            {
+                r = _resourceBase.addPath(pathInContext);
+                if (!_contextHandler.checkAlias(pathInContext,r))
+                    r=null;
+            }
+            else if (_servletContext instanceof ContextHandler.Context)
+            {
+                r = _contextHandler.getResource(pathInContext);
+            }
+            else
+            {
+                URL u = _servletContext.getResource(pathInContext);
+                r = _contextHandler.newResource(u);
+            }
+
+            if (LOG.isDebugEnabled())
+                LOG.debug("Resource "+pathInContext+"="+r);
+        }
+        catch (IOException e)
+        {
+            LOG.ignore(e);
+        }
+
+        if((r==null || !r.exists()) && pathInContext.endsWith("/jetty-dir.css"))
+            r=_stylesheet;
+
+        return r;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doGet(HttpServletRequest request, HttpServletResponse response)
+    throws ServletException, IOException
+    {
+        String servletPath=null;
+        String pathInfo=null;
+        Enumeration<String> reqRanges = null;
+        Boolean included =request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI)!=null;
+        if (included!=null && included.booleanValue())
+        {
+            servletPath=(String)request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
+            pathInfo=(String)request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);
+            if (servletPath==null)
+            {
+                servletPath=request.getServletPath();
+                pathInfo=request.getPathInfo();
+            }
+        }
+        else
+        {
+            included = Boolean.FALSE;
+            servletPath = _pathInfoOnly?"/":request.getServletPath();
+            pathInfo = request.getPathInfo();
+
+            // Is this a Range request?
+            reqRanges = request.getHeaders(HttpHeader.RANGE.asString());
+            if (!hasDefinedRange(reqRanges))
+                reqRanges = null;
+        }
+
+        String pathInContext=URIUtil.addPaths(servletPath,pathInfo);
+        boolean endsWithSlash=(pathInfo==null?request.getServletPath():pathInfo).endsWith(URIUtil.SLASH);
+
+
+        // Find the resource and content
+        Resource resource=null;
+        HttpContent content=null;
+        try
+        {
+            // is gzip enabled?
+            String pathInContextGz=null;
+            boolean gzip=false;
+            if (!included.booleanValue() && _gzip && reqRanges==null && !endsWithSlash )
+            {
+                // Look for a gzip resource
+                pathInContextGz=pathInContext+".gz";
+                if (_cache==null)
+                    resource=getResource(pathInContextGz);
+                else
+                {
+                    content=_cache.lookup(pathInContextGz);
+                    resource=(content==null)?null:content.getResource();
+                }
+
+                // Does a gzip resource exist?
+                if (resource!=null && resource.exists() && !resource.isDirectory())
+                {
+                    // Tell caches that response may vary by accept-encoding
+                    response.addHeader(HttpHeader.VARY.asString(),HttpHeader.ACCEPT_ENCODING.asString());
+                    
+                    // Does the client accept gzip?
+                    String accept=request.getHeader(HttpHeader.ACCEPT_ENCODING.asString());
+                    if (accept!=null && accept.indexOf("gzip")>=0)
+                        gzip=true;
+                }
+            }
+
+            // find resource
+            if (!gzip)
+            {
+                if (_cache==null)
+                    resource=getResource(pathInContext);
+                else
+                {
+                    content=_cache.lookup(pathInContext);
+                    resource=content==null?null:content.getResource();
+                }
+            }
+
+            if (LOG.isDebugEnabled())
+                LOG.debug("uri="+request.getRequestURI()+" resource="+resource+(content!=null?" content":""));
+
+            // Handle resource
+            if (resource==null || !resource.exists())
+            {
+                if (included)
+                    throw new FileNotFoundException("!" + pathInContext);
+                response.sendError(HttpServletResponse.SC_NOT_FOUND);
+            }
+            else if (!resource.isDirectory())
+            {
+                if (endsWithSlash && pathInContext.length()>1)
+                {
+                    String q=request.getQueryString();
+                    pathInContext=pathInContext.substring(0,pathInContext.length()-1);
+                    if (q!=null&&q.length()!=0)
+                        pathInContext+="?"+q;
+                    response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(_servletContext.getContextPath(),pathInContext)));
+                }
+                else
+                {
+                    // ensure we have content
+                    if (content==null)
+                        content=new HttpContent.ResourceAsHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),response.getBufferSize(),_etags);
+
+                    if (included.booleanValue() || passConditionalHeaders(request,response, resource,content))
+                    {
+                        if (gzip)
+                        {
+                            response.setHeader(HttpHeader.CONTENT_ENCODING.asString(),"gzip");
+                            String mt=_servletContext.getMimeType(pathInContext);
+                            if (mt!=null)
+                                response.setContentType(mt);
+                        }
+                        sendData(request,response,included.booleanValue(),resource,content,reqRanges);
+                    }
+                }
+            }
+            else
+            {
+                String welcome=null;
+
+                if (!endsWithSlash || (pathInContext.length()==1 && request.getAttribute("org.eclipse.jetty.server.nullPathInfo")!=null))
+                {
+                    StringBuffer buf=request.getRequestURL();
+                    synchronized(buf)
+                    {
+                        int param=buf.lastIndexOf(";");
+                        if (param<0)
+                            buf.append('/');
+                        else
+                            buf.insert(param,'/');
+                        String q=request.getQueryString();
+                        if (q!=null&&q.length()!=0)
+                        {
+                            buf.append('?');
+                            buf.append(q);
+                        }
+                        response.setContentLength(0);
+                        response.sendRedirect(response.encodeRedirectURL(buf.toString()));
+                    }
+                }
+                // else look for a welcome file
+                else if (null!=(welcome=getWelcomeFile(pathInContext)))
+                {
+                    LOG.debug("welcome={}",welcome);
+                    if (_redirectWelcome)
+                    {
+                        // Redirect to the index
+                        response.setContentLength(0);
+                        String q=request.getQueryString();
+                        if (q!=null&&q.length()!=0)
+                            response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths( _servletContext.getContextPath(),welcome)+"?"+q));
+                        else
+                            response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths( _servletContext.getContextPath(),welcome)));
+                    }
+                    else
+                    {
+                        // Forward to the index
+                        RequestDispatcher dispatcher=request.getRequestDispatcher(welcome);
+                        if (dispatcher!=null)
+                        {
+                            if (included.booleanValue())
+                                dispatcher.include(request,response);
+                            else
+                            {
+                                request.setAttribute("org.eclipse.jetty.server.welcome",welcome);
+                                dispatcher.forward(request,response);
+                            }
+                        }
+                    }
+                }
+                else
+                {
+                    content=new HttpContent.ResourceAsHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),_etags);
+                    if (included.booleanValue() || passConditionalHeaders(request,response, resource,content))
+                        sendDirectory(request,response,resource,pathInContext);
+                }
+            }
+        }
+        catch(IllegalArgumentException e)
+        {
+            LOG.warn(Log.EXCEPTION,e);
+            if(!response.isCommitted())
+                response.sendError(500, e.getMessage());
+        }
+        finally
+        {
+            if (content!=null)
+                content.release();
+            else if (resource!=null)
+                resource.close();
+        }
+
+    }
+
+    /* ------------------------------------------------------------ */
+    private boolean hasDefinedRange(Enumeration<String> reqRanges)
+    {
+        return (reqRanges!=null && reqRanges.hasMoreElements());
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doPost(HttpServletRequest request, HttpServletResponse response)
+    throws ServletException, IOException
+    {
+        doGet(request,response);
+    }
+
+    /* ------------------------------------------------------------ */
+    /* (non-Javadoc)
+     * @see javax.servlet.http.HttpServlet#doTrace(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
+    @Override
+    protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+    {
+        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
+    throws ServletException, IOException
+    {
+        resp.setHeader("Allow", "GET,HEAD,POST,OPTIONS");
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Finds a matching welcome file for the supplied {@link Resource}. This will be the first entry in the list of
+     * configured {@link #_welcomes welcome files} that existing within the directory referenced by the <code>Resource</code>.
+     * If the resource is not a directory, or no matching file is found, then it may look for a valid servlet mapping.
+     * If there is none, then <code>null</code> is returned.
+     * The list of welcome files is read from the {@link ContextHandler} for this servlet, or
+     * <code>"index.jsp" , "index.html"</code> if that is <code>null</code>.
+     * @param resource
+     * @return The path of the matching welcome file in context or null.
+     * @throws IOException
+     * @throws MalformedURLException
+     */
+    private String getWelcomeFile(String pathInContext) throws MalformedURLException, IOException
+    {
+        if (_welcomes==null)
+            return null;
+
+        String welcome_servlet=null;
+        for (int i=0;i<_welcomes.length;i++)
+        {
+            String welcome_in_context=URIUtil.addPaths(pathInContext,_welcomes[i]);
+            Resource welcome=getResource(welcome_in_context);
+            if (welcome!=null && welcome.exists())
+                return _welcomes[i];
+
+            if ((_welcomeServlets || _welcomeExactServlets) && welcome_servlet==null)
+            {
+                MappedEntry<?> entry=_servletHandler.getHolderEntry(welcome_in_context);
+                if (entry!=null && entry.getValue()!=_defaultHolder &&
+                        (_welcomeServlets || (_welcomeExactServlets && entry.getKey().equals(welcome_in_context))))
+                    welcome_servlet=welcome_in_context;
+
+            }
+        }
+        return welcome_servlet;
+    }
+
+    /* ------------------------------------------------------------ */
+    /* Check modification date headers.
+     */
+    protected boolean passConditionalHeaders(HttpServletRequest request,HttpServletResponse response, Resource resource, HttpContent content)
+    throws IOException
+    {
+        try
+        {
+            if (!HttpMethod.HEAD.is(request.getMethod()))
+            {
+                if (_etags)
+                {
+                    String ifm=request.getHeader(HttpHeader.IF_MATCH.asString());
+                    if (ifm!=null)
+                    {
+                        boolean match=false;
+                        if (content.getETag()!=null)
+                        {
+                            QuotedStringTokenizer quoted = new QuotedStringTokenizer(ifm,", ",false,true);
+                            while (!match && quoted.hasMoreTokens())
+                            {
+                                String tag = quoted.nextToken();
+                                if (content.getETag().equals(tag))
+                                    match=true;
+                            }
+                        }
+
+                        if (!match)
+                        {
+                            response.setStatus(HttpServletResponse.SC_PRECONDITION_FAILED);
+                            return false;
+                        }
+                    }
+                    
+                    String if_non_match_etag=request.getHeader(HttpHeader.IF_NONE_MATCH.asString());
+                    if (if_non_match_etag!=null && content.getETag()!=null)
+                    {
+                        // Look for GzipFiltered version of etag
+                        if (content.getETag().equals(request.getAttribute("o.e.j.s.GzipFilter.ETag")))
+                        {
+                            response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                            response.setHeader(HttpHeader.ETAG.asString(),if_non_match_etag);
+                            return false;
+                        }
+                        
+                        // Handle special case of exact match.
+                        if (content.getETag().equals(if_non_match_etag))
+                        {
+                            response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                            response.setHeader(HttpHeader.ETAG.asString(),content.getETag());
+                            return false;
+                        }
+
+                        // Handle list of tags
+                        QuotedStringTokenizer quoted = new QuotedStringTokenizer(if_non_match_etag,", ",false,true);
+                        while (quoted.hasMoreTokens())
+                        {
+                            String tag = quoted.nextToken();
+                            if (content.getETag().equals(tag))
+                            {
+                                response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                                response.setHeader(HttpHeader.ETAG.asString(),content.getETag());
+                                return false;
+                            }
+                        }
+                        
+                        // If etag requires content to be served, then do not check if-modified-since
+                        return true;
+                    }
+                }
+                
+                // Handle if modified since
+                String ifms=request.getHeader(HttpHeader.IF_MODIFIED_SINCE.asString());
+                if (ifms!=null)
+                {
+                    //Get jetty's Response impl
+                    String mdlm=content.getLastModified();
+                    if (mdlm!=null && ifms.equals(mdlm))
+                    {
+                        response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                        if (_etags)
+                            response.setHeader(HttpHeader.ETAG.asString(),content.getETag());
+                        response.flushBuffer();
+                        return false;
+                    }
+
+                    long ifmsl=request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.asString());
+                    if (ifmsl!=-1 && resource.lastModified()/1000 <= ifmsl/1000)
+                    { 
+                        response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                        if (_etags)
+                            response.setHeader(HttpHeader.ETAG.asString(),content.getETag());
+                        response.flushBuffer();
+                        return false;
+                    }
+                }
+
+                // Parse the if[un]modified dates and compare to resource
+                long date=request.getDateHeader(HttpHeader.IF_UNMODIFIED_SINCE.asString());
+                if (date!=-1 && resource.lastModified()/1000 > date/1000)
+                {
+                    response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
+                    return false;
+                }
+
+            }
+        }
+        catch(IllegalArgumentException iae)
+        {
+            if(!response.isCommitted())
+                response.sendError(400, iae.getMessage());
+            throw iae;
+        }
+        return true;
+    }
+
+
+    /* ------------------------------------------------------------------- */
+    protected void sendDirectory(HttpServletRequest request,
+            HttpServletResponse response,
+            Resource resource,
+            String pathInContext)
+    throws IOException
+    {
+        if (!_dirAllowed)
+        {
+            response.sendError(HttpServletResponse.SC_FORBIDDEN);
+            return;
+        }
+
+        byte[] data=null;
+        String base = URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH);
+
+        //If the DefaultServlet has a resource base set, use it
+        if (_resourceBase != null)
+        {
+            // handle ResourceCollection
+            if (_resourceBase instanceof ResourceCollection)
+                resource=_resourceBase.addPath(pathInContext);
+        }
+        //Otherwise, try using the resource base of its enclosing context handler
+        else if (_contextHandler.getBaseResource() instanceof ResourceCollection)
+            resource=_contextHandler.getBaseResource().addPath(pathInContext);
+
+        String dir = resource.getListHTML(base,pathInContext.length()>1);
+        if (dir==null)
+        {
+            response.sendError(HttpServletResponse.SC_FORBIDDEN,
+            "No directory");
+            return;
+        }
+
+        data=dir.getBytes("UTF-8");
+        response.setContentType("text/html; charset=UTF-8");
+        response.setContentLength(data.length);
+        response.getOutputStream().write(data);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void sendData(HttpServletRequest request,
+            HttpServletResponse response,
+            boolean include,
+            Resource resource,
+            HttpContent content,
+            Enumeration<String> reqRanges)
+    throws IOException
+    {
+        final long content_length = (content==null)?resource.length():content.getContentLength();
+
+        // Get the output stream (or writer)
+        OutputStream out =null;
+        boolean written;
+        try
+        {
+            out = response.getOutputStream();
+
+            // has a filter already written to the response?
+            written = out instanceof HttpOutput
+                ? ((HttpOutput)out).isWritten()
+                : true;
+        }
+        catch(IllegalStateException e)
+        {
+            out = new WriterOutputStream(response.getWriter());
+            written=true; // there may be data in writer buffer, so assume written
+        }
+
+        if ( reqRanges == null || !reqRanges.hasMoreElements() || content_length<0)
+        {
+            //  if there were no ranges, send entire entity
+            if (include)
+            {
+                resource.writeTo(out,0,content_length);
+            }
+            // else if we can't do a bypass write because of wrapping
+            else if (content==null || written || !(out instanceof HttpOutput))
+            {
+                // write normally
+                writeHeaders(response,content,written?-1:content_length);
+                ByteBuffer buffer = (content==null)?null:content.getIndirectBuffer();
+                if (buffer!=null)
+                    BufferUtil.writeTo(buffer,out);
+                else
+                    resource.writeTo(out,0,content_length);
+            }
+            // else do a bypass write
+            else
+            {
+                // write the headers
+                if (response instanceof Response)
+                {
+                    Response r = (Response)response;
+                    writeOptionHeaders(r.getHttpFields());
+                    r.setHeaders(content);
+                }
+                else
+                    writeHeaders(response,content,content_length);
+
+                // write the content asynchronously if supported
+                if (request.isAsyncSupported())
+                {
+                    final AsyncContext context = request.startAsync();
+
+                    ((HttpOutput)out).sendContent(content,new Callback()
+                    {
+                        @Override
+                        public void succeeded()
+                        {   
+                            context.complete();
+                        }
+
+                        @Override
+                        public void failed(Throwable x)
+                        {
+                            if (x instanceof IOException)
+                                LOG.debug(x);
+                            else
+                                LOG.warn(x);
+                            context.complete();
+                        }
+                    });
+                }
+                // otherwise write content blocking
+                else
+                {
+                    ((HttpOutput)out).sendContent(content);
+                }
+            }
+        }
+        else
+        {
+            // Parse the satisfiable ranges
+            List<InclusiveByteRange> ranges =InclusiveByteRange.satisfiableRanges(reqRanges,content_length);
+
+            //  if there are no satisfiable ranges, send 416 response
+            if (ranges==null || ranges.size()==0)
+            {
+                writeHeaders(response, content, content_length);
+                response.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
+                response.setHeader(HttpHeader.CONTENT_RANGE.asString(),
+                        InclusiveByteRange.to416HeaderRangeString(content_length));
+                resource.writeTo(out,0,content_length);
+                return;
+            }
+
+            //  if there is only a single valid range (must be satisfiable
+            //  since were here now), send that range with a 216 response
+            if ( ranges.size()== 1)
+            {
+                InclusiveByteRange singleSatisfiableRange = ranges.get(0);
+                long singleLength = singleSatisfiableRange.getSize(content_length);
+                writeHeaders(response,content,singleLength                     );
+                response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
+                if (!response.containsHeader(HttpHeader.DATE.asString()))
+                    response.addDateHeader(HttpHeader.DATE.asString(),System.currentTimeMillis());
+                response.setHeader(HttpHeader.CONTENT_RANGE.asString(),
+                        singleSatisfiableRange.toHeaderRangeString(content_length));
+                resource.writeTo(out,singleSatisfiableRange.getFirst(content_length),singleLength);
+                return;
+            }
+
+            //  multiple non-overlapping valid ranges cause a multipart
+            //  216 response which does not require an overall
+            //  content-length header
+            //
+            writeHeaders(response,content,-1);
+            String mimetype=(content==null?null:content.getContentType());
+            if (mimetype==null)
+                LOG.warn("Unknown mimetype for "+request.getRequestURI());
+            MultiPartOutputStream multi = new MultiPartOutputStream(out);
+            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
+            if (!response.containsHeader(HttpHeader.DATE.asString()))
+                response.addDateHeader(HttpHeader.DATE.asString(),System.currentTimeMillis());
+
+            // If the request has a "Request-Range" header then we need to
+            // send an old style multipart/x-byteranges Content-Type. This
+            // keeps Netscape and acrobat happy. This is what Apache does.
+            String ctp;
+            if (request.getHeader(HttpHeader.REQUEST_RANGE.asString())!=null)
+                ctp = "multipart/x-byteranges; boundary=";
+            else
+                ctp = "multipart/byteranges; boundary=";
+            response.setContentType(ctp+multi.getBoundary());
+
+            InputStream in=resource.getInputStream();
+            long pos=0;
+
+            // calculate the content-length
+            int length=0;
+            String[] header = new String[ranges.size()];
+            for (int i=0;i<ranges.size();i++)
+            {
+                InclusiveByteRange ibr = ranges.get(i);
+                header[i]=ibr.toHeaderRangeString(content_length);
+                length+=
+                    ((i>0)?2:0)+
+                    2+multi.getBoundary().length()+2+
+                    (mimetype==null?0:HttpHeader.CONTENT_TYPE.asString().length()+2+mimetype.length())+2+
+                    HttpHeader.CONTENT_RANGE.asString().length()+2+header[i].length()+2+
+                    2+
+                    (ibr.getLast(content_length)-ibr.getFirst(content_length))+1;
+            }
+            length+=2+2+multi.getBoundary().length()+2+2;
+            response.setContentLength(length);
+
+            for (int i=0;i<ranges.size();i++)
+            {
+                InclusiveByteRange ibr =  ranges.get(i);
+                multi.startPart(mimetype,new String[]{HttpHeader.CONTENT_RANGE+": "+header[i]});
+
+                long start=ibr.getFirst(content_length);
+                long size=ibr.getSize(content_length);
+                if (in!=null)
+                {
+                    // Handle non cached resource
+                    if (start<pos)
+                    {
+                        in.close();
+                        in=resource.getInputStream();
+                        pos=0;
+                    }
+                    if (pos<start)
+                    {
+                        in.skip(start-pos);
+                        pos=start;
+                    }
+                    
+                    IO.copy(in,multi,size);
+                    pos+=size;
+                }
+                else
+                    // Handle cached resource
+                    (resource).writeTo(multi,start,size);
+            }
+            if (in!=null)
+                in.close();
+            multi.close();
+        }
+        return;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void writeHeaders(HttpServletResponse response,HttpContent content,long count)
+    {        
+        if (content.getContentType()!=null && response.getContentType()==null)
+            response.setContentType(content.getContentType().toString());
+
+        if (response instanceof Response)
+        {
+            Response r=(Response)response;
+            HttpFields fields = r.getHttpFields();
+
+            if (content.getLastModified()!=null)
+                fields.put(HttpHeader.LAST_MODIFIED,content.getLastModified());
+            else if (content.getResource()!=null)
+            {
+                long lml=content.getResource().lastModified();
+                if (lml!=-1)
+                    fields.putDateField(HttpHeader.LAST_MODIFIED,lml);
+            }
+
+            if (count != -1)
+                r.setLongContentLength(count);
+
+            writeOptionHeaders(fields);
+            
+            if (_etags)
+                fields.put(HttpHeader.ETAG,content.getETag());
+        }
+        else
+        {
+            long lml=content.getResource().lastModified();
+            if (lml>=0)
+                response.setDateHeader(HttpHeader.LAST_MODIFIED.asString(),lml);
+
+            if (count != -1)
+            {
+                if (count<Integer.MAX_VALUE)
+                    response.setContentLength((int)count);
+                else
+                    response.setHeader(HttpHeader.CONTENT_LENGTH.asString(),Long.toString(count));
+            }
+
+            writeOptionHeaders(response);
+
+            if (_etags)
+                response.setHeader(HttpHeader.ETAG.asString(),content.getETag());
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void writeOptionHeaders(HttpFields fields)
+    {
+        if (_acceptRanges)
+            fields.put(ACCEPT_RANGES);
+
+        if (_cacheControl!=null)
+            fields.put(_cacheControl);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void writeOptionHeaders(HttpServletResponse response)
+    {
+        if (_acceptRanges)
+            response.setHeader(HttpHeader.ACCEPT_RANGES.asString(),"bytes");
+
+        if (_cacheControl!=null)
+            response.setHeader(HttpHeader.CACHE_CONTROL.asString(),_cacheControl.getValue());
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.Servlet#destroy()
+     */
+    @Override
+    public void destroy()
+    {
+        if (_cache!=null)
+            _cache.flushCache();
+        super.destroy();
+    }
+
+}