1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.servlet;
20
21 import static org.eclipse.jetty.http.GzipHttpContent.ETAG_GZIP_QUOTE;
22 import static org.eclipse.jetty.http.GzipHttpContent.removeGzipFromETag;
23
24 import java.io.FileNotFoundException;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.OutputStream;
28 import java.net.MalformedURLException;
29 import java.net.URL;
30 import java.nio.ByteBuffer;
31 import java.util.ArrayList;
32 import java.util.Enumeration;
33 import java.util.List;
34 import java.util.StringTokenizer;
35
36 import javax.servlet.AsyncContext;
37 import javax.servlet.RequestDispatcher;
38 import javax.servlet.ServletContext;
39 import javax.servlet.ServletException;
40 import javax.servlet.UnavailableException;
41 import javax.servlet.http.HttpServlet;
42 import javax.servlet.http.HttpServletRequest;
43 import javax.servlet.http.HttpServletResponse;
44
45 import org.eclipse.jetty.http.DateParser;
46 import org.eclipse.jetty.http.GzipHttpContent;
47 import org.eclipse.jetty.http.HttpContent;
48 import org.eclipse.jetty.http.HttpField;
49 import org.eclipse.jetty.http.HttpFields;
50 import org.eclipse.jetty.http.HttpHeader;
51 import org.eclipse.jetty.http.HttpMethod;
52 import org.eclipse.jetty.http.MimeTypes;
53 import org.eclipse.jetty.http.PathMap.MappedEntry;
54 import org.eclipse.jetty.http.PreEncodedHttpField;
55 import org.eclipse.jetty.http.ResourceHttpContent;
56 import org.eclipse.jetty.io.WriterOutputStream;
57 import org.eclipse.jetty.server.HttpOutput;
58 import org.eclipse.jetty.server.InclusiveByteRange;
59 import org.eclipse.jetty.server.Request;
60 import org.eclipse.jetty.server.ResourceCache;
61 import org.eclipse.jetty.server.ResourceContentFactory;
62 import org.eclipse.jetty.server.Response;
63 import org.eclipse.jetty.server.handler.ContextHandler;
64 import org.eclipse.jetty.util.BufferUtil;
65 import org.eclipse.jetty.util.Callback;
66 import org.eclipse.jetty.util.IO;
67 import org.eclipse.jetty.util.MultiPartOutputStream;
68 import org.eclipse.jetty.util.QuotedStringTokenizer;
69 import org.eclipse.jetty.util.URIUtil;
70 import org.eclipse.jetty.util.log.Log;
71 import org.eclipse.jetty.util.log.Logger;
72 import org.eclipse.jetty.util.resource.Resource;
73 import org.eclipse.jetty.util.resource.ResourceCollection;
74 import org.eclipse.jetty.util.resource.ResourceFactory;
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147 public class DefaultServlet extends HttpServlet implements ResourceFactory
148 {
149 private static final Logger LOG = Log.getLogger(DefaultServlet.class);
150
151 private static final long serialVersionUID = 4930458713846881193L;
152
153 private static final PreEncodedHttpField ACCEPT_RANGES = new PreEncodedHttpField(HttpHeader.ACCEPT_RANGES, "bytes");
154
155 private ServletContext _servletContext;
156 private ContextHandler _contextHandler;
157
158 private boolean _acceptRanges=true;
159 private boolean _dirAllowed=true;
160 private boolean _welcomeServlets=false;
161 private boolean _welcomeExactServlets=false;
162 private boolean _redirectWelcome=false;
163 private boolean _gzip=false;
164 private boolean _pathInfoOnly=false;
165 private boolean _etags=false;
166
167 private Resource _resourceBase;
168 private ResourceCache _cache;
169 private HttpContent.Factory _contentFactory;
170
171 private MimeTypes _mimeTypes;
172 private String[] _welcomes;
173 private Resource _stylesheet;
174 private boolean _useFileMappedBuffer=false;
175 private HttpField _cacheControl;
176 private String _relativeResourceBase;
177 private ServletHandler _servletHandler;
178 private ServletHolder _defaultHolder;
179 private List<String> _gzipEquivalentFileExtensions;
180
181
182 @Override
183 public void init()
184 throws UnavailableException
185 {
186 _servletContext=getServletContext();
187 _contextHandler = initContextHandler(_servletContext);
188
189 _mimeTypes = _contextHandler.getMimeTypes();
190
191 _welcomes = _contextHandler.getWelcomeFiles();
192 if (_welcomes==null)
193 _welcomes=new String[] {"index.html","index.jsp"};
194
195 _acceptRanges=getInitBoolean("acceptRanges",_acceptRanges);
196 _dirAllowed=getInitBoolean("dirAllowed",_dirAllowed);
197 _redirectWelcome=getInitBoolean("redirectWelcome",_redirectWelcome);
198 _gzip=getInitBoolean("gzip",_gzip);
199 _pathInfoOnly=getInitBoolean("pathInfoOnly",_pathInfoOnly);
200
201 if ("exact".equals(getInitParameter("welcomeServlets")))
202 {
203 _welcomeExactServlets=true;
204 _welcomeServlets=false;
205 }
206 else
207 _welcomeServlets=getInitBoolean("welcomeServlets", _welcomeServlets);
208
209 _useFileMappedBuffer=getInitBoolean("useFileMappedBuffer",_useFileMappedBuffer);
210
211 _relativeResourceBase = getInitParameter("relativeResourceBase");
212
213 String rb=getInitParameter("resourceBase");
214 if (rb!=null)
215 {
216 if (_relativeResourceBase!=null)
217 throw new UnavailableException("resourceBase & relativeResourceBase");
218 try{_resourceBase=_contextHandler.newResource(rb);}
219 catch (Exception e)
220 {
221 LOG.warn(Log.EXCEPTION,e);
222 throw new UnavailableException(e.toString());
223 }
224 }
225
226 String css=getInitParameter("stylesheet");
227 try
228 {
229 if(css!=null)
230 {
231 _stylesheet = Resource.newResource(css);
232 if(!_stylesheet.exists())
233 {
234 LOG.warn("!" + css);
235 _stylesheet = null;
236 }
237 }
238 if(_stylesheet == null)
239 {
240 _stylesheet = Resource.newResource(this.getClass().getResource("/jetty-dir.css"));
241 }
242 }
243 catch(Exception e)
244 {
245 LOG.warn(e.toString());
246 LOG.debug(e);
247 }
248
249 String cc=getInitParameter("cacheControl");
250 if (cc!=null)
251 _cacheControl=new PreEncodedHttpField(HttpHeader.CACHE_CONTROL, cc);
252
253 String resourceCache = getInitParameter("resourceCache");
254 int max_cache_size=getInitInt("maxCacheSize", -2);
255 int max_cached_file_size=getInitInt("maxCachedFileSize", -2);
256 int max_cached_files=getInitInt("maxCachedFiles", -2);
257 if (resourceCache!=null)
258 {
259 if (max_cache_size!=-1 || max_cached_file_size!= -2 || max_cached_files!=-2)
260 LOG.debug("ignoring resource cache configuration, using resourceCache attribute");
261 if (_relativeResourceBase!=null || _resourceBase!=null)
262 throw new UnavailableException("resourceCache specified with resource bases");
263 _cache=(ResourceCache)_servletContext.getAttribute(resourceCache);
264
265 if (LOG.isDebugEnabled())
266 LOG.debug("Cache {}={}",resourceCache,_contentFactory);
267 }
268
269 _etags = getInitBoolean("etags",_etags);
270
271 try
272 {
273 if (_cache==null && (max_cached_files!=-2 || max_cache_size!=-2 || max_cached_file_size!=-2))
274 {
275 _cache = new ResourceCache(null,this,_mimeTypes,_useFileMappedBuffer,_etags,_gzip);
276 if (max_cache_size>=0)
277 _cache.setMaxCacheSize(max_cache_size);
278 if (max_cached_file_size>=-1)
279 _cache.setMaxCachedFileSize(max_cached_file_size);
280 if (max_cached_files>=-1)
281 _cache.setMaxCachedFiles(max_cached_files);
282 _servletContext.setAttribute(resourceCache==null?"resourceCache":resourceCache,_cache);
283 }
284 }
285 catch (Exception e)
286 {
287 LOG.warn(Log.EXCEPTION,e);
288 throw new UnavailableException(e.toString());
289 }
290
291 _contentFactory=_cache==null?new ResourceContentFactory(this,_mimeTypes,-1,_gzip):_cache;
292
293 _gzipEquivalentFileExtensions = new ArrayList<String>();
294 String otherGzipExtensions = getInitParameter("otherGzipFileExtensions");
295 if (otherGzipExtensions != null)
296 {
297
298 StringTokenizer tok = new StringTokenizer(otherGzipExtensions,",",false);
299 while (tok.hasMoreTokens())
300 {
301 String s = tok.nextToken().trim();
302 _gzipEquivalentFileExtensions.add((s.charAt(0)=='.'?s:"."+s));
303 }
304 }
305 else
306 {
307
308 _gzipEquivalentFileExtensions.add(".svgz");
309 }
310
311 _servletHandler= _contextHandler.getChildHandlerByClass(ServletHandler.class);
312 for (ServletHolder h :_servletHandler.getServlets())
313 if (h.getServletInstance()==this)
314 _defaultHolder=h;
315
316 if (LOG.isDebugEnabled())
317 LOG.debug("resource base = "+_resourceBase);
318 }
319
320
321
322
323
324
325
326
327
328 protected ContextHandler initContextHandler(ServletContext servletContext)
329 {
330 ContextHandler.Context scontext=ContextHandler.getCurrentContext();
331 if (scontext==null)
332 {
333 if (servletContext instanceof ContextHandler.Context)
334 return ((ContextHandler.Context)servletContext).getContextHandler();
335 else
336 throw new IllegalArgumentException("The servletContext " + servletContext + " " +
337 servletContext.getClass().getName() + " is not " + ContextHandler.Context.class.getName());
338 }
339 else
340 return ContextHandler.getCurrentContext().getContextHandler();
341 }
342
343
344 @Override
345 public String getInitParameter(String name)
346 {
347 String value=getServletContext().getInitParameter("org.eclipse.jetty.servlet.Default."+name);
348 if (value==null)
349 value=super.getInitParameter(name);
350 return value;
351 }
352
353
354 private boolean getInitBoolean(String name, boolean dft)
355 {
356 String value=getInitParameter(name);
357 if (value==null || value.length()==0)
358 return dft;
359 return (value.startsWith("t")||
360 value.startsWith("T")||
361 value.startsWith("y")||
362 value.startsWith("Y")||
363 value.startsWith("1"));
364 }
365
366
367 private int getInitInt(String name, int dft)
368 {
369 String value=getInitParameter(name);
370 if (value==null)
371 value=getInitParameter(name);
372 if (value!=null && value.length()>0)
373 return Integer.parseInt(value);
374 return dft;
375 }
376
377
378
379
380
381
382
383
384
385 @Override
386 public Resource getResource(String pathInContext)
387 {
388 Resource r=null;
389 if (_relativeResourceBase!=null)
390 pathInContext=URIUtil.addPaths(_relativeResourceBase,pathInContext);
391
392 try
393 {
394 if (_resourceBase!=null)
395 {
396 r = _resourceBase.addPath(pathInContext);
397 if (!_contextHandler.checkAlias(pathInContext,r))
398 r=null;
399 }
400 else if (_servletContext instanceof ContextHandler.Context)
401 {
402 r = _contextHandler.getResource(pathInContext);
403 }
404 else
405 {
406 URL u = _servletContext.getResource(pathInContext);
407 r = _contextHandler.newResource(u);
408 }
409
410 if (LOG.isDebugEnabled())
411 LOG.debug("Resource "+pathInContext+"="+r);
412 }
413 catch (IOException e)
414 {
415 LOG.ignore(e);
416 }
417
418 if((r==null || !r.exists()) && pathInContext.endsWith("/jetty-dir.css"))
419 r=_stylesheet;
420
421 return r;
422 }
423
424
425 @Override
426 protected void doGet(HttpServletRequest request, HttpServletResponse response)
427 throws ServletException, IOException
428 {
429 String servletPath=null;
430 String pathInfo=null;
431 Enumeration<String> reqRanges = null;
432 boolean included =request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI)!=null;
433 if (included)
434 {
435 servletPath=(String)request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
436 pathInfo=(String)request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);
437 if (servletPath==null)
438 {
439 servletPath=request.getServletPath();
440 pathInfo=request.getPathInfo();
441 }
442 }
443 else
444 {
445 servletPath = _pathInfoOnly?"/":request.getServletPath();
446 pathInfo = request.getPathInfo();
447
448
449 reqRanges = request.getHeaders(HttpHeader.RANGE.asString());
450 if (!hasDefinedRange(reqRanges))
451 reqRanges = null;
452 }
453
454 String pathInContext=URIUtil.addPaths(servletPath,pathInfo);
455 boolean endsWithSlash=(pathInfo==null?request.getServletPath():pathInfo).endsWith(URIUtil.SLASH);
456 boolean gzippable=_gzip && !endsWithSlash && !included && reqRanges==null;
457
458 HttpContent content=null;
459 boolean release_content=true;
460 try
461 {
462
463 content=_contentFactory.getContent(pathInContext);
464 if (LOG.isDebugEnabled())
465 LOG.info("content={}",content);
466
467
468 if (content==null || !content.getResource().exists())
469 {
470 if (included)
471 throw new FileNotFoundException("!" + pathInContext);
472 response.sendError(HttpServletResponse.SC_NOT_FOUND);
473 return;
474 }
475
476
477 if (content.getResource().isDirectory())
478 {
479 sendWelcome(content,pathInContext,endsWithSlash,included,request,response);
480 return;
481 }
482
483
484 if (endsWithSlash && pathInContext.length()>1)
485 {
486 String q=request.getQueryString();
487 pathInContext=pathInContext.substring(0,pathInContext.length()-1);
488 if (q!=null&&q.length()!=0)
489 pathInContext+="?"+q;
490 response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(_servletContext.getContextPath(),pathInContext)));
491 return;
492 }
493
494
495 if (!included && !passConditionalHeaders(request,response,content))
496 return;
497
498
499 HttpContent gzip_content = gzippable?content.getGzipContent():null;
500 if (gzip_content!=null)
501 {
502
503 response.addHeader(HttpHeader.VARY.asString(),HttpHeader.ACCEPT_ENCODING.asString());
504
505
506 String accept=request.getHeader(HttpHeader.ACCEPT_ENCODING.asString());
507 if (accept!=null && accept.indexOf("gzip")>=0)
508 {
509 if (LOG.isDebugEnabled())
510 LOG.debug("gzip={}",gzip_content);
511 content=gzip_content;
512 }
513 }
514
515
516 if (isGzippedContent(pathInContext))
517 response.setHeader(HttpHeader.CONTENT_ENCODING.asString(),"gzip");
518
519
520 release_content=sendData(request,response,included,content,reqRanges);
521
522 }
523 catch(IllegalArgumentException e)
524 {
525 LOG.warn(Log.EXCEPTION,e);
526 if(!response.isCommitted())
527 response.sendError(500, e.getMessage());
528 }
529 finally
530 {
531 if (release_content)
532 {
533 if (content!=null)
534 content.release();
535 }
536 }
537
538 }
539
540 protected void sendWelcome(HttpContent content, String pathInContext, boolean endsWithSlash, boolean included, HttpServletRequest request, HttpServletResponse response)
541 throws ServletException, IOException
542 {
543
544 if (!endsWithSlash || (pathInContext.length()==1 && request.getAttribute("org.eclipse.jetty.server.nullPathInfo")!=null))
545 {
546 StringBuffer buf=request.getRequestURL();
547 synchronized(buf)
548 {
549 int param=buf.lastIndexOf(";");
550 if (param<0)
551 buf.append('/');
552 else
553 buf.insert(param,'/');
554 String q=request.getQueryString();
555 if (q!=null&&q.length()!=0)
556 {
557 buf.append('?');
558 buf.append(q);
559 }
560 response.setContentLength(0);
561 response.sendRedirect(response.encodeRedirectURL(buf.toString()));
562 }
563 return;
564 }
565
566
567 String welcome=getWelcomeFile(pathInContext);
568 if (welcome!=null)
569 {
570 if (LOG.isDebugEnabled())
571 LOG.debug("welcome={}",welcome);
572 if (_redirectWelcome)
573 {
574
575 response.setContentLength(0);
576 String q=request.getQueryString();
577 if (q!=null&&q.length()!=0)
578 response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths( _servletContext.getContextPath(),welcome)+"?"+q));
579 else
580 response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths( _servletContext.getContextPath(),welcome)));
581 }
582 else
583 {
584
585 RequestDispatcher dispatcher=request.getRequestDispatcher(welcome);
586 if (dispatcher!=null)
587 {
588 if (included)
589 dispatcher.include(request,response);
590 else
591 {
592 request.setAttribute("org.eclipse.jetty.server.welcome",welcome);
593 dispatcher.forward(request,response);
594 }
595 }
596 }
597 return;
598 }
599
600 if (included || passConditionalHeaders(request,response, content))
601 sendDirectory(request,response,content.getResource(),pathInContext);
602 }
603
604
605 protected boolean isGzippedContent(String path)
606 {
607 if (path == null) return false;
608
609 for (String suffix:_gzipEquivalentFileExtensions)
610 if (path.endsWith(suffix))
611 return true;
612 return false;
613 }
614
615
616 private boolean hasDefinedRange(Enumeration<String> reqRanges)
617 {
618 return (reqRanges!=null && reqRanges.hasMoreElements());
619 }
620
621
622 @Override
623 protected void doPost(HttpServletRequest request, HttpServletResponse response)
624 throws ServletException, IOException
625 {
626 doGet(request,response);
627 }
628
629
630
631
632
633 @Override
634 protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
635 {
636 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
637 }
638
639
640 @Override
641 protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
642 throws ServletException, IOException
643 {
644 resp.setHeader("Allow", "GET,HEAD,POST,OPTIONS");
645 }
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660 private String getWelcomeFile(String pathInContext) throws MalformedURLException, IOException
661 {
662 if (_welcomes==null)
663 return null;
664
665 String welcome_servlet=null;
666 for (int i=0;i<_welcomes.length;i++)
667 {
668 String welcome_in_context=URIUtil.addPaths(pathInContext,_welcomes[i]);
669 Resource welcome=getResource(welcome_in_context);
670 if (welcome!=null && welcome.exists())
671 return _welcomes[i];
672
673 if ((_welcomeServlets || _welcomeExactServlets) && welcome_servlet==null)
674 {
675 MappedEntry<?> entry=_servletHandler.getHolderEntry(welcome_in_context);
676 if (entry!=null && entry.getValue()!=_defaultHolder &&
677 (_welcomeServlets || (_welcomeExactServlets && entry.getKey().equals(welcome_in_context))))
678 welcome_servlet=welcome_in_context;
679
680 }
681 }
682 return welcome_servlet;
683 }
684
685
686
687
688 protected boolean passConditionalHeaders(HttpServletRequest request,HttpServletResponse response, HttpContent content)
689 throws IOException
690 {
691 try
692 {
693 String ifm=null;
694 String ifnm=null;
695 String ifms=null;
696 long ifums=-1;
697
698 if (request instanceof Request)
699 {
700
701 HttpFields fields = ((Request)request).getHttpFields();
702 for (int i=fields.size();i-->0;)
703 {
704 HttpField field=fields.getField(i);
705 if (field.getHeader() != null)
706 {
707 switch (field.getHeader())
708 {
709 case IF_MATCH:
710 ifm=field.getValue();
711 break;
712 case IF_NONE_MATCH:
713 ifnm=field.getValue();
714 break;
715 case IF_MODIFIED_SINCE:
716 ifms=field.getValue();
717 break;
718 case IF_UNMODIFIED_SINCE:
719 ifums=DateParser.parseDate(field.getValue());
720 break;
721 default:
722 }
723 }
724 }
725 }
726 else
727 {
728 ifm=request.getHeader(HttpHeader.IF_MATCH.asString());
729 ifnm=request.getHeader(HttpHeader.IF_NONE_MATCH.asString());
730 ifms=request.getHeader(HttpHeader.IF_MODIFIED_SINCE.asString());
731 ifums=request.getDateHeader(HttpHeader.IF_UNMODIFIED_SINCE.asString());
732 }
733
734 if (!HttpMethod.HEAD.is(request.getMethod()))
735 {
736 if (_etags)
737 {
738 String etag=content.getETagValue();
739 if (ifm!=null)
740 {
741 boolean match=false;
742 if (etag!=null)
743 {
744 QuotedStringTokenizer quoted = new QuotedStringTokenizer(ifm,", ",false,true);
745 while (!match && quoted.hasMoreTokens())
746 {
747 String tag = quoted.nextToken();
748 if (etag.equals(tag) || tag.endsWith(ETAG_GZIP_QUOTE) && etag.equals(removeGzipFromETag(tag)))
749 match=true;
750 }
751 }
752
753 if (!match)
754 {
755 response.setStatus(HttpServletResponse.SC_PRECONDITION_FAILED);
756 return false;
757 }
758 }
759
760 if (ifnm!=null && etag!=null)
761 {
762
763 if (etag.equals(ifnm) || ifnm.endsWith(ETAG_GZIP_QUOTE) && ifnm.indexOf(',')<0 && etag.equals(removeGzipFromETag(etag)))
764 {
765 response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
766 response.setHeader(HttpHeader.ETAG.asString(),ifnm);
767 return false;
768 }
769
770
771 QuotedStringTokenizer quoted = new QuotedStringTokenizer(ifnm,", ",false,true);
772 while (quoted.hasMoreTokens())
773 {
774 String tag = quoted.nextToken();
775 if (etag.equals(tag) || tag.endsWith(ETAG_GZIP_QUOTE) && etag.equals(removeGzipFromETag(tag)))
776 {
777 response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
778 response.setHeader(HttpHeader.ETAG.asString(),tag);
779 return false;
780 }
781 }
782
783
784 return true;
785 }
786 }
787
788
789 if (ifms!=null)
790 {
791
792 String mdlm=content.getLastModifiedValue();
793 if (mdlm!=null && ifms.equals(mdlm))
794 {
795 response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
796 if (_etags)
797 response.setHeader(HttpHeader.ETAG.asString(),content.getETagValue());
798 response.flushBuffer();
799 return false;
800 }
801
802 long ifmsl=request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.asString());
803 if (ifmsl!=-1 && content.getResource().lastModified()/1000 <= ifmsl/1000)
804 {
805 response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
806 if (_etags)
807 response.setHeader(HttpHeader.ETAG.asString(),content.getETagValue());
808 response.flushBuffer();
809 return false;
810 }
811 }
812
813
814 if (ifums!=-1 && content.getResource().lastModified()/1000 > ifums/1000)
815 {
816 response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
817 return false;
818 }
819
820 }
821 }
822 catch(IllegalArgumentException iae)
823 {
824 if(!response.isCommitted())
825 response.sendError(400, iae.getMessage());
826 throw iae;
827 }
828 return true;
829 }
830
831
832
833 protected void sendDirectory(HttpServletRequest request,
834 HttpServletResponse response,
835 Resource resource,
836 String pathInContext)
837 throws IOException
838 {
839 if (!_dirAllowed)
840 {
841 response.sendError(HttpServletResponse.SC_FORBIDDEN);
842 return;
843 }
844
845 byte[] data=null;
846 String base = URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH);
847
848
849 if (_resourceBase != null)
850 {
851
852 if (_resourceBase instanceof ResourceCollection)
853 resource=_resourceBase.addPath(pathInContext);
854 }
855
856 else if (_contextHandler.getBaseResource() instanceof ResourceCollection)
857 resource=_contextHandler.getBaseResource().addPath(pathInContext);
858
859 String dir = resource.getListHTML(base,pathInContext.length()>1);
860 if (dir==null)
861 {
862 response.sendError(HttpServletResponse.SC_FORBIDDEN,
863 "No directory");
864 return;
865 }
866
867 data=dir.getBytes("utf-8");
868 response.setContentType("text/html;charset=utf-8");
869 response.setContentLength(data.length);
870 response.getOutputStream().write(data);
871 }
872
873
874 protected boolean sendData(HttpServletRequest request,
875 HttpServletResponse response,
876 boolean include,
877 final HttpContent content,
878 Enumeration<String> reqRanges)
879 throws IOException
880 {
881 final long content_length = content.getContentLengthValue();
882
883
884 OutputStream out =null;
885 boolean written;
886 try
887 {
888 out = response.getOutputStream();
889
890
891 written = out instanceof HttpOutput
892 ? ((HttpOutput)out).isWritten()
893 : true;
894 }
895 catch(IllegalStateException e)
896 {
897 out = new WriterOutputStream(response.getWriter());
898 written=true;
899 }
900
901 if (LOG.isDebugEnabled())
902 LOG.debug(String.format("sendData content=%s out=%s async=%b",content,out,request.isAsyncSupported()));
903
904 if ( reqRanges == null || !reqRanges.hasMoreElements() || content_length<0)
905 {
906
907 if (include)
908 {
909
910 content.getResource().writeTo(out,0,content_length);
911 }
912
913 else if (written || !(out instanceof HttpOutput))
914 {
915
916 putHeaders(response,content,written?-1:0);
917 ByteBuffer buffer = content.getIndirectBuffer();
918 if (buffer!=null)
919 BufferUtil.writeTo(buffer,out);
920 else
921 content.getResource().writeTo(out,0,content_length);
922 }
923
924 else
925 {
926
927 putHeaders(response,content,0);
928
929
930 if (request.isAsyncSupported())
931 {
932 final AsyncContext context = request.startAsync();
933 context.setTimeout(0);
934
935 ((HttpOutput)out).sendContent(content,new Callback()
936 {
937 @Override
938 public void succeeded()
939 {
940 context.complete();
941 content.release();
942 }
943
944 @Override
945 public void failed(Throwable x)
946 {
947 if (x instanceof IOException)
948 LOG.debug(x);
949 else
950 LOG.warn(x);
951 context.complete();
952 content.release();
953 }
954
955 @Override
956 public String toString()
957 {
958 return String.format("DefaultServlet@%x$CB", DefaultServlet.this.hashCode());
959 }
960 });
961 return false;
962 }
963
964 ((HttpOutput)out).sendContent(content);
965 }
966 }
967 else
968 {
969
970 List<InclusiveByteRange> ranges =InclusiveByteRange.satisfiableRanges(reqRanges,content_length);
971
972
973 if (ranges==null || ranges.size()==0)
974 {
975 putHeaders(response,content,0);
976 response.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
977 response.setHeader(HttpHeader.CONTENT_RANGE.asString(),
978 InclusiveByteRange.to416HeaderRangeString(content_length));
979 content.getResource().writeTo(out,0,content_length);
980 return true;
981 }
982
983
984
985 if ( ranges.size()== 1)
986 {
987 InclusiveByteRange singleSatisfiableRange = ranges.get(0);
988 long singleLength = singleSatisfiableRange.getSize(content_length);
989 putHeaders(response,content,singleLength);
990 response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
991 if (!response.containsHeader(HttpHeader.DATE.asString()))
992 response.addDateHeader(HttpHeader.DATE.asString(),System.currentTimeMillis());
993 response.setHeader(HttpHeader.CONTENT_RANGE.asString(),
994 singleSatisfiableRange.toHeaderRangeString(content_length));
995 content.getResource().writeTo(out,singleSatisfiableRange.getFirst(content_length),singleLength);
996 return true;
997 }
998
999
1000
1001
1002
1003 putHeaders(response,content,-1);
1004 String mimetype=(content==null?null:content.getContentTypeValue());
1005 if (mimetype==null)
1006 LOG.warn("Unknown mimetype for "+request.getRequestURI());
1007 MultiPartOutputStream multi = new MultiPartOutputStream(out);
1008 response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
1009 if (!response.containsHeader(HttpHeader.DATE.asString()))
1010 response.addDateHeader(HttpHeader.DATE.asString(),System.currentTimeMillis());
1011
1012
1013
1014
1015 String ctp;
1016 if (request.getHeader(HttpHeader.REQUEST_RANGE.asString())!=null)
1017 ctp = "multipart/x-byteranges; boundary=";
1018 else
1019 ctp = "multipart/byteranges; boundary=";
1020 response.setContentType(ctp+multi.getBoundary());
1021
1022 InputStream in=content.getResource().getInputStream();
1023 long pos=0;
1024
1025
1026 int length=0;
1027 String[] header = new String[ranges.size()];
1028 for (int i=0;i<ranges.size();i++)
1029 {
1030 InclusiveByteRange ibr = ranges.get(i);
1031 header[i]=ibr.toHeaderRangeString(content_length);
1032 length+=
1033 ((i>0)?2:0)+
1034 2+multi.getBoundary().length()+2+
1035 (mimetype==null?0:HttpHeader.CONTENT_TYPE.asString().length()+2+mimetype.length())+2+
1036 HttpHeader.CONTENT_RANGE.asString().length()+2+header[i].length()+2+
1037 2+
1038 (ibr.getLast(content_length)-ibr.getFirst(content_length))+1;
1039 }
1040 length+=2+2+multi.getBoundary().length()+2+2;
1041 response.setContentLength(length);
1042
1043 for (int i=0;i<ranges.size();i++)
1044 {
1045 InclusiveByteRange ibr = ranges.get(i);
1046 multi.startPart(mimetype,new String[]{HttpHeader.CONTENT_RANGE+": "+header[i]});
1047
1048 long start=ibr.getFirst(content_length);
1049 long size=ibr.getSize(content_length);
1050 if (in!=null)
1051 {
1052
1053 if (start<pos)
1054 {
1055 in.close();
1056 in=content.getResource().getInputStream();
1057 pos=0;
1058 }
1059 if (pos<start)
1060 {
1061 in.skip(start-pos);
1062 pos=start;
1063 }
1064
1065 IO.copy(in,multi,size);
1066 pos+=size;
1067 }
1068 else
1069
1070 content.getResource().writeTo(multi,start,size);
1071 }
1072 if (in!=null)
1073 in.close();
1074 multi.close();
1075 }
1076 return true;
1077 }
1078
1079
1080 protected void putHeaders(HttpServletResponse response,HttpContent content, long contentLength)
1081 {
1082 if (response instanceof Response)
1083 {
1084 Response r = (Response)response;
1085 r.putHeaders(content,contentLength,_etags);
1086 HttpFields f = r.getHttpFields();
1087 if (_acceptRanges)
1088 f.put(ACCEPT_RANGES);
1089
1090 if (_cacheControl!=null)
1091 f.put(_cacheControl);
1092 }
1093 else
1094 {
1095 Response.putHeaders(response,content,contentLength,_etags);
1096 if (_acceptRanges)
1097 response.setHeader(ACCEPT_RANGES.getName(),ACCEPT_RANGES.getValue());
1098
1099 if (_cacheControl!=null)
1100 response.setHeader(_cacheControl.getName(),_cacheControl.getValue());
1101 }
1102 }
1103
1104
1105
1106
1107
1108 @Override
1109 public void destroy()
1110 {
1111 if (_cache!=null)
1112 _cache.flushCache();
1113 super.destroy();
1114 }
1115
1116 }