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