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