1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.server;
20
21 import java.io.IOException;
22 import java.io.PrintWriter;
23 import java.nio.channels.IllegalSelectorException;
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.Enumeration;
28 import java.util.Iterator;
29 import java.util.Locale;
30 import java.util.concurrent.atomic.AtomicInteger;
31
32 import javax.servlet.RequestDispatcher;
33 import javax.servlet.ServletOutputStream;
34 import javax.servlet.http.Cookie;
35 import javax.servlet.http.HttpServletResponse;
36 import javax.servlet.http.HttpSession;
37
38 import org.eclipse.jetty.http.DateGenerator;
39 import org.eclipse.jetty.http.HttpContent;
40 import org.eclipse.jetty.http.HttpCookie;
41 import org.eclipse.jetty.http.HttpField;
42 import org.eclipse.jetty.http.HttpFields;
43 import org.eclipse.jetty.http.HttpGenerator;
44 import org.eclipse.jetty.http.HttpHeader;
45 import org.eclipse.jetty.http.HttpHeaderValue;
46 import org.eclipse.jetty.http.HttpScheme;
47 import org.eclipse.jetty.http.HttpStatus;
48 import org.eclipse.jetty.http.HttpURI;
49 import org.eclipse.jetty.http.HttpVersion;
50 import org.eclipse.jetty.http.MetaData;
51 import org.eclipse.jetty.http.MimeTypes;
52 import org.eclipse.jetty.http.PreEncodedHttpField;
53 import org.eclipse.jetty.io.RuntimeIOException;
54 import org.eclipse.jetty.server.handler.ErrorHandler;
55 import org.eclipse.jetty.util.ByteArrayISO8859Writer;
56 import org.eclipse.jetty.util.Jetty;
57 import org.eclipse.jetty.util.QuotedStringTokenizer;
58 import org.eclipse.jetty.util.StringUtil;
59 import org.eclipse.jetty.util.URIUtil;
60 import org.eclipse.jetty.util.log.Log;
61 import org.eclipse.jetty.util.log.Logger;
62
63
64
65
66 public class Response implements HttpServletResponse
67 {
68 private static final Logger LOG = Log.getLogger(Response.class);
69 private static final String __COOKIE_DELIM="\",;\\ \t";
70 private final static String __01Jan1970_COOKIE = DateGenerator.formatCookieDate(0).trim();
71 private final static int __MIN_BUFFER_SIZE = 1;
72 private final static HttpField __EXPIRES_01JAN1970 = new PreEncodedHttpField(HttpHeader.EXPIRES,DateGenerator.__01Jan1970);
73
74
75
76 private static final ThreadLocal<StringBuilder> __cookieBuilder = new ThreadLocal<StringBuilder>()
77 {
78 @Override
79 protected StringBuilder initialValue()
80 {
81 return new StringBuilder(128);
82 }
83 };
84
85 public enum OutputType
86 {
87 NONE, STREAM, WRITER
88 }
89
90
91
92
93
94
95 public final static String SET_INCLUDE_HEADER_PREFIX = "org.eclipse.jetty.server.include.";
96
97
98
99
100
101 public final static String HTTP_ONLY_COMMENT = "__HTTP_ONLY__";
102
103 private final HttpChannel _channel;
104 private final HttpFields _fields = new HttpFields();
105 private final AtomicInteger _include = new AtomicInteger();
106 private final HttpOutput _out;
107 private int _status = HttpStatus.OK_200;
108 private String _reason;
109 private Locale _locale;
110 private MimeTypes.Type _mimeType;
111 private String _characterEncoding;
112 private boolean _explicitEncoding;
113 private String _contentType;
114 private OutputType _outputType = OutputType.NONE;
115 private ResponseWriter _writer;
116 private long _contentLength = -1;
117
118
119 public Response(HttpChannel channel, HttpOutput out)
120 {
121 _channel = channel;
122 _out = out;
123 }
124
125 public HttpChannel getHttpChannel()
126 {
127 return _channel;
128 }
129
130 protected void recycle()
131 {
132 _status = HttpStatus.OK_200;
133 _reason = null;
134 _locale = null;
135 _mimeType = null;
136 _characterEncoding = null;
137 _contentType = null;
138 _outputType = OutputType.NONE;
139 _contentLength = -1;
140 _out.recycle();
141 _fields.clear();
142 _explicitEncoding=false;
143 }
144
145 public HttpOutput getHttpOutput()
146 {
147 return _out;
148 }
149
150 public boolean isIncluding()
151 {
152 return _include.get() > 0;
153 }
154
155 public void include()
156 {
157 _include.incrementAndGet();
158 }
159
160 public void included()
161 {
162 _include.decrementAndGet();
163 if (_outputType == OutputType.WRITER)
164 {
165 _writer.reopen();
166 }
167 _out.reopen();
168 }
169
170 public void addCookie(HttpCookie cookie)
171 {
172 addSetCookie(
173 cookie.getName(),
174 cookie.getValue(),
175 cookie.getDomain(),
176 cookie.getPath(),
177 cookie.getMaxAge(),
178 cookie.getComment(),
179 cookie.isSecure(),
180 cookie.isHttpOnly(),
181 cookie.getVersion());;
182 }
183
184 @Override
185 public void addCookie(Cookie cookie)
186 {
187 String comment = cookie.getComment();
188 boolean httpOnly = false;
189
190 if (comment != null)
191 {
192 int i = comment.indexOf(HTTP_ONLY_COMMENT);
193 if (i >= 0)
194 {
195 httpOnly = true;
196 comment = comment.replace(HTTP_ONLY_COMMENT, "").trim();
197 if (comment.length() == 0)
198 comment = null;
199 }
200 }
201 addSetCookie(cookie.getName(),
202 cookie.getValue(),
203 cookie.getDomain(),
204 cookie.getPath(),
205 cookie.getMaxAge(),
206 comment,
207 cookie.getSecure(),
208 httpOnly || cookie.isHttpOnly(),
209 cookie.getVersion());
210 }
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226 public void addSetCookie(
227 final String name,
228 final String value,
229 final String domain,
230 final String path,
231 final long maxAge,
232 final String comment,
233 final boolean isSecure,
234 final boolean isHttpOnly,
235 int version)
236 {
237
238 if (name == null || name.length() == 0)
239 throw new IllegalArgumentException("Bad cookie name");
240
241
242 StringBuilder buf = __cookieBuilder.get();
243 buf.setLength(0);
244
245
246 boolean quote_name=isQuoteNeededForCookie(name);
247 quoteOnlyOrAppend(buf,name,quote_name);
248
249 buf.append('=');
250
251
252 String name_equals=buf.toString();
253
254
255 boolean quote_value=isQuoteNeededForCookie(value);
256 quoteOnlyOrAppend(buf,value,quote_value);
257
258
259 boolean has_domain = domain!=null && domain.length()>0;
260 boolean quote_domain = has_domain && isQuoteNeededForCookie(domain);
261 boolean has_path = path!=null && path.length()>0;
262 boolean quote_path = has_path && isQuoteNeededForCookie(path);
263
264
265 if (version==0 && ( comment!=null || quote_name || quote_value || quote_domain || quote_path ||
266 QuotedStringTokenizer.isQuoted(name) || QuotedStringTokenizer.isQuoted(value) ||
267 QuotedStringTokenizer.isQuoted(path) || QuotedStringTokenizer.isQuoted(domain)))
268 version=1;
269
270
271 if (version==1)
272 buf.append (";Version=1");
273 else if (version>1)
274 buf.append (";Version=").append(version);
275
276
277 if (has_path)
278 {
279 buf.append(";Path=");
280 quoteOnlyOrAppend(buf,path,quote_path);
281 }
282
283
284 if (has_domain)
285 {
286 buf.append(";Domain=");
287 quoteOnlyOrAppend(buf,domain,quote_domain);
288 }
289
290
291 if (maxAge >= 0)
292 {
293
294
295 buf.append(";Expires=");
296 if (maxAge == 0)
297 buf.append(__01Jan1970_COOKIE);
298 else
299 DateGenerator.formatCookieDate(buf, System.currentTimeMillis() + 1000L * maxAge);
300
301
302 if (version>=1)
303 {
304 buf.append(";Max-Age=");
305 buf.append(maxAge);
306 }
307 }
308
309
310 if (isSecure)
311 buf.append(";Secure");
312 if (isHttpOnly)
313 buf.append(";HttpOnly");
314 if (comment != null)
315 {
316 buf.append(";Comment=");
317 quoteOnlyOrAppend(buf,comment,isQuoteNeededForCookie(comment));
318 }
319
320
321 Iterator<HttpField> i=_fields.iterator();
322 while (i.hasNext())
323 {
324 HttpField field=i.next();
325 if (field.getHeader()==HttpHeader.SET_COOKIE)
326 {
327 String val = field.getValue();
328 if (val!=null && val.startsWith(name_equals))
329 {
330
331 if (((!has_domain && !val.contains("Domain")) || (has_domain && val.contains(domain))) &&
332 ((!has_path && !val.contains("Path")) || (has_path && val.contains(path))))
333 {
334 i.remove();
335 }
336 }
337 }
338 }
339
340
341 _fields.add(HttpHeader.SET_COOKIE.toString(), buf.toString());
342
343
344 _fields.put(__EXPIRES_01JAN1970);
345 }
346
347
348
349
350
351
352
353
354 private static boolean isQuoteNeededForCookie(String s)
355 {
356 if (s==null || s.length()==0)
357 return true;
358
359 if (QuotedStringTokenizer.isQuoted(s))
360 return false;
361
362 for (int i=0;i<s.length();i++)
363 {
364 char c = s.charAt(i);
365 if (__COOKIE_DELIM.indexOf(c)>=0)
366 return true;
367
368 if (c<0x20 || c>=0x7f)
369 throw new IllegalArgumentException("Illegal character in cookie value");
370 }
371
372 return false;
373 }
374
375
376 private static void quoteOnlyOrAppend(StringBuilder buf, String s, boolean quote)
377 {
378 if (quote)
379 QuotedStringTokenizer.quoteOnly(buf,s);
380 else
381 buf.append(s);
382 }
383
384 @Override
385 public boolean containsHeader(String name)
386 {
387 return _fields.containsKey(name);
388 }
389
390 @Override
391 public String encodeURL(String url)
392 {
393 final Request request = _channel.getRequest();
394 SessionManager sessionManager = request.getSessionManager();
395 if (sessionManager == null)
396 return url;
397
398 HttpURI uri = null;
399 if (sessionManager.isCheckingRemoteSessionIdEncoding() && URIUtil.hasScheme(url))
400 {
401 uri = new HttpURI(url);
402 String path = uri.getPath();
403 path = (path == null ? "" : path);
404 int port = uri.getPort();
405 if (port < 0)
406 port = HttpScheme.HTTPS.asString().equalsIgnoreCase(uri.getScheme()) ? 443 : 80;
407
408
409 if (!request.getServerName().equalsIgnoreCase(uri.getHost()))
410 return url;
411 if (request.getServerPort() != port)
412 return url;
413 if (!path.startsWith(request.getContextPath()))
414 return url;
415 }
416
417 String sessionURLPrefix = sessionManager.getSessionIdPathParameterNamePrefix();
418 if (sessionURLPrefix == null)
419 return url;
420
421 if (url == null)
422 return null;
423
424
425 if ((sessionManager.isUsingCookies() && request.isRequestedSessionIdFromCookie()) || !sessionManager.isUsingURLs())
426 {
427 int prefix = url.indexOf(sessionURLPrefix);
428 if (prefix != -1)
429 {
430 int suffix = url.indexOf("?", prefix);
431 if (suffix < 0)
432 suffix = url.indexOf("#", prefix);
433
434 if (suffix <= prefix)
435 return url.substring(0, prefix);
436 return url.substring(0, prefix) + url.substring(suffix);
437 }
438 return url;
439 }
440
441
442 HttpSession session = request.getSession(false);
443
444
445 if (session == null)
446 return url;
447
448
449 if (!sessionManager.isValid(session))
450 return url;
451
452 String id = sessionManager.getNodeId(session);
453
454 if (uri == null)
455 uri = new HttpURI(url);
456
457
458
459 int prefix = url.indexOf(sessionURLPrefix);
460 if (prefix != -1)
461 {
462 int suffix = url.indexOf("?", prefix);
463 if (suffix < 0)
464 suffix = url.indexOf("#", prefix);
465
466 if (suffix <= prefix)
467 return url.substring(0, prefix + sessionURLPrefix.length()) + id;
468 return url.substring(0, prefix + sessionURLPrefix.length()) + id +
469 url.substring(suffix);
470 }
471
472
473 int suffix = url.indexOf('?');
474 if (suffix < 0)
475 suffix = url.indexOf('#');
476 if (suffix < 0)
477 {
478 return url +
479 ((HttpScheme.HTTPS.is(uri.getScheme()) || HttpScheme.HTTP.is(uri.getScheme())) && uri.getPath() == null ? "/" : "") +
480 sessionURLPrefix + id;
481 }
482
483
484 return url.substring(0, suffix) +
485 ((HttpScheme.HTTPS.is(uri.getScheme()) || HttpScheme.HTTP.is(uri.getScheme())) && uri.getPath() == null ? "/" : "") +
486 sessionURLPrefix + id + url.substring(suffix);
487 }
488
489 @Override
490 public String encodeRedirectURL(String url)
491 {
492 return encodeURL(url);
493 }
494
495 @Override
496 @Deprecated
497 public String encodeUrl(String url)
498 {
499 return encodeURL(url);
500 }
501
502 @Override
503 @Deprecated
504 public String encodeRedirectUrl(String url)
505 {
506 return encodeRedirectURL(url);
507 }
508
509 @Override
510 public void sendError(int sc) throws IOException
511 {
512 sendError(sc, null);
513 }
514
515 @Override
516 public void sendError(int code, String message) throws IOException
517 {
518 if (isIncluding())
519 return;
520
521 if (isCommitted())
522 {
523 if (LOG.isDebugEnabled())
524 LOG.debug("Aborting on sendError on committed response {} {}",code,message);
525 code=-1;
526 }
527
528 switch(code)
529 {
530 case -1:
531 _channel.abort(new IOException());
532 return;
533 case 102:
534 sendProcessing();
535 return;
536 default:
537 }
538
539 if (isCommitted())
540 LOG.warn("Committed before "+code+" "+message);
541
542 resetBuffer();
543 _characterEncoding=null;
544 setHeader(HttpHeader.EXPIRES,null);
545 setHeader(HttpHeader.LAST_MODIFIED,null);
546 setHeader(HttpHeader.CACHE_CONTROL,null);
547 setHeader(HttpHeader.CONTENT_TYPE,null);
548 setHeader(HttpHeader.CONTENT_LENGTH,null);
549
550 _outputType = OutputType.NONE;
551 setStatus(code);
552 _reason=message;
553
554 Request request = _channel.getRequest();
555 Throwable cause = (Throwable)request.getAttribute(Dispatcher.ERROR_EXCEPTION);
556 if (message==null)
557 message=cause==null?HttpStatus.getMessage(code):cause.toString();
558
559
560 if (code!=SC_NO_CONTENT &&
561 code!=SC_NOT_MODIFIED &&
562 code!=SC_PARTIAL_CONTENT &&
563 code>=SC_OK)
564 {
565 ErrorHandler error_handler = ErrorHandler.getErrorHandler(_channel.getServer(),request.getContext()==null?null:request.getContext().getContextHandler());
566 if (error_handler!=null)
567 {
568 request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE,new Integer(code));
569 request.setAttribute(RequestDispatcher.ERROR_MESSAGE, message);
570 request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI, request.getRequestURI());
571 request.setAttribute(RequestDispatcher.ERROR_SERVLET_NAME,request.getServletName());
572 error_handler.handle(null,_channel.getRequest(),_channel.getRequest(),this );
573 }
574 else
575 {
576 setHeader(HttpHeader.CACHE_CONTROL, "must-revalidate,no-cache,no-store");
577 setContentType(MimeTypes.Type.TEXT_HTML_8859_1.toString());
578 try (ByteArrayISO8859Writer writer= new ByteArrayISO8859Writer(2048);)
579 {
580 message=StringUtil.sanitizeXmlString(message);
581 String uri= request.getRequestURI();
582 uri=StringUtil.sanitizeXmlString(uri);
583
584 writer.write("<html>\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html;charset=ISO-8859-1\"/>\n");
585 writer.write("<title>Error ");
586 writer.write(Integer.toString(code));
587 writer.write(' ');
588 if (message==null)
589 writer.write(message);
590 writer.write("</title>\n</head>\n<body>\n<h2>HTTP ERROR: ");
591 writer.write(Integer.toString(code));
592 writer.write("</h2>\n<p>Problem accessing ");
593 writer.write(uri);
594 writer.write(". Reason:\n<pre> ");
595 writer.write(message);
596 writer.write("</pre>");
597 writer.write("</p>\n<hr />");
598
599 getHttpChannel().getHttpConfiguration().writePoweredBy(writer,null,"<hr/>");
600 writer.write("\n</body>\n</html>\n");
601
602 writer.flush();
603 setContentLength(writer.size());
604 try (ServletOutputStream outputStream = getOutputStream())
605 {
606 writer.writeTo(outputStream);
607 writer.destroy();
608 }
609 }
610 }
611 }
612 else if (code!=SC_PARTIAL_CONTENT)
613 {
614
615 _channel.getRequest().getHttpFields().remove(HttpHeader.CONTENT_TYPE);
616 _channel.getRequest().getHttpFields().remove(HttpHeader.CONTENT_LENGTH);
617 _characterEncoding=null;
618 _mimeType=null;
619 }
620
621 closeOutput();
622 }
623
624
625
626
627
628
629
630
631
632
633 public void sendProcessing() throws IOException
634 {
635 if (_channel.isExpecting102Processing() && !isCommitted())
636 {
637 _channel.sendResponse(HttpGenerator.PROGRESS_102_INFO, null, true);
638 }
639 }
640
641
642
643
644
645
646
647 public void sendRedirect(int code, String location) throws IOException
648 {
649 if ((code < HttpServletResponse.SC_MULTIPLE_CHOICES) || (code >= HttpServletResponse.SC_BAD_REQUEST))
650 throw new IllegalArgumentException("Not a 3xx redirect code");
651
652 if (isIncluding())
653 return;
654
655 if (location == null)
656 throw new IllegalArgumentException();
657
658 if (!URIUtil.hasScheme(location))
659 {
660 StringBuilder buf = _channel.getRequest().getRootURL();
661 if (location.startsWith("/"))
662 {
663
664 location=URIUtil.canonicalPath(location);
665 }
666 else
667 {
668
669 String path=_channel.getRequest().getRequestURI();
670 String parent=(path.endsWith("/"))?path:URIUtil.parentPath(path);
671 location=URIUtil.canonicalPath(URIUtil.addPaths(parent,location));
672 if (!location.startsWith("/"))
673 buf.append('/');
674 }
675
676 if(location==null)
677 throw new IllegalStateException("path cannot be above root");
678 buf.append(location);
679
680 location=buf.toString();
681 }
682
683 resetBuffer();
684 setHeader(HttpHeader.LOCATION, location);
685 setStatus(code);
686 closeOutput();
687 }
688
689 @Override
690 public void sendRedirect(String location) throws IOException
691 {
692 sendRedirect(HttpServletResponse.SC_MOVED_TEMPORARILY, location);
693 }
694
695 @Override
696 public void setDateHeader(String name, long date)
697 {
698 if (!isIncluding())
699 _fields.putDateField(name, date);
700 }
701
702 @Override
703 public void addDateHeader(String name, long date)
704 {
705 if (!isIncluding())
706 _fields.addDateField(name, date);
707 }
708
709 public void setHeader(HttpHeader name, String value)
710 {
711 if (HttpHeader.CONTENT_TYPE == name)
712 setContentType(value);
713 else
714 {
715 if (isIncluding())
716 return;
717
718 _fields.put(name, value);
719
720 if (HttpHeader.CONTENT_LENGTH == name)
721 {
722 if (value == null)
723 _contentLength = -1l;
724 else
725 _contentLength = Long.parseLong(value);
726 }
727 }
728 }
729
730 @Override
731 public void setHeader(String name, String value)
732 {
733 if (HttpHeader.CONTENT_TYPE.is(name))
734 setContentType(value);
735 else
736 {
737 if (isIncluding())
738 {
739 if (name.startsWith(SET_INCLUDE_HEADER_PREFIX))
740 name = name.substring(SET_INCLUDE_HEADER_PREFIX.length());
741 else
742 return;
743 }
744 _fields.put(name, value);
745 if (HttpHeader.CONTENT_LENGTH.is(name))
746 {
747 if (value == null)
748 _contentLength = -1l;
749 else
750 _contentLength = Long.parseLong(value);
751 }
752 }
753 }
754
755 @Override
756 public Collection<String> getHeaderNames()
757 {
758 final HttpFields fields = _fields;
759 return fields.getFieldNamesCollection();
760 }
761
762 @Override
763 public String getHeader(String name)
764 {
765 return _fields.get(name);
766 }
767
768 @Override
769 public Collection<String> getHeaders(String name)
770 {
771 final HttpFields fields = _fields;
772 Collection<String> i = fields.getValuesList(name);
773 if (i == null)
774 return Collections.emptyList();
775 return i;
776 }
777
778 @Override
779 public void addHeader(String name, String value)
780 {
781 if (isIncluding())
782 {
783 if (name.startsWith(SET_INCLUDE_HEADER_PREFIX))
784 name = name.substring(SET_INCLUDE_HEADER_PREFIX.length());
785 else
786 return;
787 }
788
789 if (HttpHeader.CONTENT_TYPE.is(name))
790 {
791 setContentType(value);
792 return;
793 }
794
795 if (HttpHeader.CONTENT_LENGTH.is(name))
796 {
797 setHeader(name,value);
798 return;
799 }
800
801 _fields.add(name, value);
802 }
803
804 @Override
805 public void setIntHeader(String name, int value)
806 {
807 if (!isIncluding())
808 {
809 _fields.putLongField(name, value);
810 if (HttpHeader.CONTENT_LENGTH.is(name))
811 _contentLength = value;
812 }
813 }
814
815 @Override
816 public void addIntHeader(String name, int value)
817 {
818 if (!isIncluding())
819 {
820 _fields.add(name, Integer.toString(value));
821 if (HttpHeader.CONTENT_LENGTH.is(name))
822 _contentLength = value;
823 }
824 }
825
826 @Override
827 public void setStatus(int sc)
828 {
829 if (sc <= 0)
830 throw new IllegalArgumentException();
831 if (!isIncluding())
832 {
833 _status = sc;
834 _reason = null;
835 }
836 }
837
838 @Override
839 @Deprecated
840 public void setStatus(int sc, String sm)
841 {
842 setStatusWithReason(sc,sm);
843 }
844
845 public void setStatusWithReason(int sc, String sm)
846 {
847 if (sc <= 0)
848 throw new IllegalArgumentException();
849 if (!isIncluding())
850 {
851 _status = sc;
852 _reason = sm;
853 }
854 }
855
856 @Override
857 public String getCharacterEncoding()
858 {
859 if (_characterEncoding == null)
860 _characterEncoding = StringUtil.__ISO_8859_1;
861 return _characterEncoding;
862 }
863
864 @Override
865 public String getContentType()
866 {
867 return _contentType;
868 }
869
870 @Override
871 public ServletOutputStream getOutputStream() throws IOException
872 {
873 if (_outputType == OutputType.WRITER)
874 throw new IllegalStateException("WRITER");
875 _outputType = OutputType.STREAM;
876 return _out;
877 }
878
879 public boolean isWriting()
880 {
881 return _outputType == OutputType.WRITER;
882 }
883
884 @Override
885 public PrintWriter getWriter() throws IOException
886 {
887 if (_outputType == OutputType.STREAM)
888 throw new IllegalStateException("STREAM");
889
890 if (_outputType == OutputType.NONE)
891 {
892
893 String encoding = _characterEncoding;
894 if (encoding == null)
895 {
896 if (_mimeType!=null && _mimeType.isCharsetAssumed())
897 encoding=_mimeType.getCharsetString();
898 else
899 {
900 encoding = MimeTypes.inferCharsetFromContentType(_contentType);
901 if (encoding == null)
902 encoding = StringUtil.__ISO_8859_1;
903 setCharacterEncoding(encoding,false);
904 }
905 }
906
907 Locale locale = getLocale();
908
909 if (_writer != null && _writer.isFor(locale,encoding))
910 _writer.reopen();
911 else
912 {
913 if (StringUtil.__ISO_8859_1.equalsIgnoreCase(encoding))
914 _writer = new ResponseWriter(new Iso88591HttpWriter(_out),locale,encoding);
915 else if (StringUtil.__UTF8.equalsIgnoreCase(encoding))
916 _writer = new ResponseWriter(new Utf8HttpWriter(_out),locale,encoding);
917 else
918 _writer = new ResponseWriter(new EncodingHttpWriter(_out, encoding),locale,encoding);
919 }
920
921
922 _outputType = OutputType.WRITER;
923 }
924 return _writer;
925 }
926
927 @Override
928 public void setContentLength(int len)
929 {
930
931
932
933 if (isCommitted() || isIncluding())
934 return;
935
936 _contentLength = len;
937 if (_contentLength > 0)
938 {
939 long written = _out.getWritten();
940 if (written > len)
941 throw new IllegalArgumentException("setContentLength(" + len + ") when already written " + written);
942
943 _fields.putLongField(HttpHeader.CONTENT_LENGTH, len);
944 if (isAllContentWritten(written))
945 {
946 try
947 {
948 closeOutput();
949 }
950 catch(IOException e)
951 {
952 throw new RuntimeIOException(e);
953 }
954 }
955 }
956 else if (_contentLength==0)
957 {
958 long written = _out.getWritten();
959 if (written > 0)
960 throw new IllegalArgumentException("setContentLength(0) when already written " + written);
961 _fields.put(HttpHeader.CONTENT_LENGTH, "0");
962 }
963 else
964 _fields.remove(HttpHeader.CONTENT_LENGTH);
965 }
966
967 public long getContentLength()
968 {
969 return _contentLength;
970 }
971
972 public boolean isAllContentWritten(long written)
973 {
974 return (_contentLength >= 0 && written >= _contentLength);
975 }
976
977 public void closeOutput() throws IOException
978 {
979 switch (_outputType)
980 {
981 case WRITER:
982 _writer.close();
983 if (!_out.isClosed())
984 _out.close();
985 break;
986 case STREAM:
987 getOutputStream().close();
988 break;
989 default:
990 _out.close();
991 }
992 }
993
994 public long getLongContentLength()
995 {
996 return _contentLength;
997 }
998
999 public void setLongContentLength(long len)
1000 {
1001
1002
1003
1004 if (isCommitted() || isIncluding())
1005 return;
1006 _contentLength = len;
1007 _fields.putLongField(HttpHeader.CONTENT_LENGTH.toString(), len);
1008 }
1009
1010 @Override
1011 public void setContentLengthLong(long length)
1012 {
1013 setLongContentLength(length);
1014 }
1015
1016 @Override
1017 public void setCharacterEncoding(String encoding)
1018 {
1019 setCharacterEncoding(encoding,true);
1020 }
1021
1022 private void setCharacterEncoding(String encoding, boolean explicit)
1023 {
1024 if (isIncluding() || isWriting())
1025 return;
1026
1027 if (_outputType == OutputType.NONE && !isCommitted())
1028 {
1029 if (encoding == null)
1030 {
1031 _explicitEncoding=false;
1032
1033
1034 if (_characterEncoding != null)
1035 {
1036 _characterEncoding = null;
1037
1038 if (_mimeType!=null)
1039 {
1040 _mimeType=_mimeType.getBaseType();
1041 _contentType=_mimeType.asString();
1042 _fields.put(_mimeType.getContentTypeField());
1043 }
1044 else if (_contentType != null)
1045 {
1046 _contentType = MimeTypes.getContentTypeWithoutCharset(_contentType);
1047 _fields.put(HttpHeader.CONTENT_TYPE, _contentType);
1048 }
1049 }
1050 }
1051 else
1052 {
1053
1054 _explicitEncoding = explicit;
1055 _characterEncoding = HttpGenerator.__STRICT?encoding:StringUtil.normalizeCharset(encoding);
1056 if (_mimeType!=null)
1057 {
1058 _contentType=_mimeType.getBaseType().asString()+ ";charset=" + _characterEncoding;
1059 _mimeType = MimeTypes.CACHE.get(_contentType);
1060 if (_mimeType==null || HttpGenerator.__STRICT)
1061 _fields.put(HttpHeader.CONTENT_TYPE, _contentType);
1062 else
1063 _fields.put(_mimeType.getContentTypeField());
1064 }
1065 else if (_contentType != null)
1066 {
1067 _contentType = MimeTypes.getContentTypeWithoutCharset(_contentType) + ";charset=" + _characterEncoding;
1068 _fields.put(HttpHeader.CONTENT_TYPE, _contentType);
1069 }
1070 }
1071 }
1072 }
1073
1074 @Override
1075 public void setContentType(String contentType)
1076 {
1077 if (isCommitted() || isIncluding())
1078 return;
1079
1080 if (contentType == null)
1081 {
1082 if (isWriting() && _characterEncoding != null)
1083 throw new IllegalSelectorException();
1084
1085 if (_locale == null)
1086 _characterEncoding = null;
1087 _mimeType = null;
1088 _contentType = null;
1089 _fields.remove(HttpHeader.CONTENT_TYPE);
1090 }
1091 else
1092 {
1093 _contentType = contentType;
1094 _mimeType = MimeTypes.CACHE.get(contentType);
1095
1096 String charset;
1097 if (_mimeType!=null && _mimeType.getCharset()!=null && !_mimeType.isCharsetAssumed())
1098 charset=_mimeType.getCharsetString();
1099 else
1100 charset = MimeTypes.getCharsetFromContentType(contentType);
1101
1102 if (charset == null)
1103 {
1104 if (_characterEncoding != null)
1105 {
1106 _contentType = contentType + ";charset=" + _characterEncoding;
1107 _mimeType = null;
1108 }
1109 }
1110 else if (isWriting() && !charset.equalsIgnoreCase(_characterEncoding))
1111 {
1112
1113 _mimeType = null;
1114 _contentType = MimeTypes.getContentTypeWithoutCharset(_contentType);
1115 if (_characterEncoding != null)
1116 _contentType = _contentType + ";charset=" + _characterEncoding;
1117 }
1118 else
1119 {
1120 _characterEncoding = charset;
1121 _explicitEncoding = true;
1122 }
1123
1124 if (HttpGenerator.__STRICT || _mimeType==null)
1125 _fields.put(HttpHeader.CONTENT_TYPE, _contentType);
1126 else
1127 {
1128 _contentType=_mimeType.asString();
1129 _fields.put(_mimeType.getContentTypeField());
1130 }
1131 }
1132
1133 }
1134
1135 @Override
1136 public void setBufferSize(int size)
1137 {
1138 if (isCommitted() || getContentCount() > 0)
1139 throw new IllegalStateException("Committed or content written");
1140 if (size <= 0)
1141 size = __MIN_BUFFER_SIZE;
1142 _out.setBufferSize(size);
1143 }
1144
1145 @Override
1146 public int getBufferSize()
1147 {
1148 return _out.getBufferSize();
1149 }
1150
1151 @Override
1152 public void flushBuffer() throws IOException
1153 {
1154 if (!_out.isClosed())
1155 _out.flush();
1156 }
1157
1158 @Override
1159 public void reset()
1160 {
1161 resetForForward();
1162 _status = 200;
1163 _reason = null;
1164 _contentLength = -1;
1165 _fields.clear();
1166
1167 String connection = _channel.getRequest().getHeader(HttpHeader.CONNECTION.asString());
1168
1169 if (connection != null)
1170 {
1171 for (String value: StringUtil.csvSplit(null,connection,0,connection.length()))
1172 {
1173 HttpHeaderValue cb = HttpHeaderValue.CACHE.get(value);
1174
1175 if (cb != null)
1176 {
1177 switch (cb)
1178 {
1179 case CLOSE:
1180 _fields.put(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.toString());
1181 break;
1182
1183 case KEEP_ALIVE:
1184 if (HttpVersion.HTTP_1_0.is(_channel.getRequest().getProtocol()))
1185 _fields.put(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.toString());
1186 break;
1187 case TE:
1188 _fields.put(HttpHeader.CONNECTION, HttpHeaderValue.TE.toString());
1189 break;
1190 default:
1191 }
1192 }
1193 }
1194 }
1195 }
1196
1197 public void reset(boolean preserveCookies)
1198 {
1199 if (!preserveCookies)
1200 reset();
1201 else
1202 {
1203 ArrayList<String> cookieValues = new ArrayList<String>(5);
1204 Enumeration<String> vals = _fields.getValues(HttpHeader.SET_COOKIE.asString());
1205 while (vals.hasMoreElements())
1206 cookieValues.add(vals.nextElement());
1207 reset();
1208 for (String v:cookieValues)
1209 _fields.add(HttpHeader.SET_COOKIE, v);
1210 }
1211 }
1212
1213 public void resetForForward()
1214 {
1215 resetBuffer();
1216 _outputType = OutputType.NONE;
1217 }
1218
1219 @Override
1220 public void resetBuffer()
1221 {
1222 if (isCommitted())
1223 throw new IllegalStateException("Committed");
1224
1225 _out.resetBuffer();
1226 }
1227
1228 protected MetaData.Response newResponseMetaData()
1229 {
1230 return new MetaData.Response(_channel.getRequest().getHttpVersion(), getStatus(), getReason(), _fields, getLongContentLength());
1231 }
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241 public MetaData.Response getCommittedMetaData()
1242 {
1243 MetaData.Response meta = _channel.getCommittedMetaData();
1244 if (meta==null)
1245 return newResponseMetaData();
1246 return meta;
1247 }
1248
1249 @Override
1250 public boolean isCommitted()
1251 {
1252 return _channel.isCommitted();
1253 }
1254
1255 @Override
1256 public void setLocale(Locale locale)
1257 {
1258 if (locale == null || isCommitted() || isIncluding())
1259 return;
1260
1261 _locale = locale;
1262 _fields.put(HttpHeader.CONTENT_LANGUAGE, locale.toString().replace('_', '-'));
1263
1264 if (_outputType != OutputType.NONE)
1265 return;
1266
1267 if (_channel.getRequest().getContext() == null)
1268 return;
1269
1270 String charset = _channel.getRequest().getContext().getContextHandler().getLocaleEncoding(locale);
1271
1272 if (charset != null && charset.length() > 0 && !_explicitEncoding)
1273 setCharacterEncoding(charset,false);
1274 }
1275
1276 @Override
1277 public Locale getLocale()
1278 {
1279 if (_locale == null)
1280 return Locale.getDefault();
1281 return _locale;
1282 }
1283
1284 @Override
1285 public int getStatus()
1286 {
1287 return _status;
1288 }
1289
1290 public String getReason()
1291 {
1292 return _reason;
1293 }
1294
1295 public HttpFields getHttpFields()
1296 {
1297 return _fields;
1298 }
1299
1300 public long getContentCount()
1301 {
1302 return _out.getWritten();
1303 }
1304
1305 @Override
1306 public String toString()
1307 {
1308 return String.format("%s %d %s%n%s", _channel.getRequest().getHttpVersion(), _status, _reason == null ? "" : _reason, _fields);
1309 }
1310
1311
1312 public void putHeaders(HttpContent content,long contentLength, boolean etag)
1313 {
1314 HttpField lm = content.getLastModified();
1315 if (lm!=null)
1316 _fields.put(lm);
1317
1318 if (contentLength==0)
1319 {
1320 _fields.put(content.getContentLength());
1321 _contentLength=content.getContentLengthValue();
1322 }
1323 else if (contentLength>0)
1324 {
1325 _fields.putLongField(HttpHeader.CONTENT_LENGTH,contentLength);
1326 _contentLength=contentLength;
1327 }
1328
1329 HttpField ct=content.getContentType();
1330 if (ct!=null)
1331 {
1332 _fields.put(ct);
1333 _contentType=ct.getValue();
1334 _characterEncoding=content.getCharacterEncoding();
1335 _mimeType=content.getMimeType();
1336 }
1337
1338 HttpField ce=content.getContentEncoding();
1339 if (ce!=null)
1340 _fields.put(ce);
1341
1342 if (etag)
1343 {
1344 HttpField et = content.getETag();
1345 if (et!=null)
1346 _fields.put(et);
1347 }
1348 }
1349
1350 public static void putHeaders(HttpServletResponse response, HttpContent content, long contentLength, boolean etag)
1351 {
1352 long lml=content.getResource().lastModified();
1353 if (lml>=0)
1354 response.setDateHeader(HttpHeader.LAST_MODIFIED.asString(),lml);
1355
1356 if (contentLength==0)
1357 contentLength=content.getContentLengthValue();
1358 if (contentLength >=0)
1359 {
1360 if (contentLength<Integer.MAX_VALUE)
1361 response.setContentLength((int)contentLength);
1362 else
1363 response.setHeader(HttpHeader.CONTENT_LENGTH.asString(),Long.toString(contentLength));
1364 }
1365
1366 String ct=content.getContentTypeValue();
1367 if (ct!=null && response.getContentType()==null)
1368 response.setContentType(ct);
1369
1370 String ce=content.getContentEncodingValue();
1371 if (ce!=null)
1372 response.setHeader(HttpHeader.CONTENT_ENCODING.asString(),ce);
1373
1374 if (etag)
1375 {
1376 String et=content.getETagValue();
1377 if (et!=null)
1378 response.setHeader(HttpHeader.ETAG.asString(),et);
1379 }
1380 }
1381 }