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