View Javadoc

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