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