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