View Javadoc

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