1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.catalina.connector;
18
19 import java.io.IOException;
20 import java.io.PrintWriter;
21 import java.io.UnsupportedEncodingException;
22 import java.net.MalformedURLException;
23 import java.net.URL;
24 import java.nio.charset.Charset;
25 import java.security.AccessController;
26 import java.security.PrivilegedAction;
27 import java.security.PrivilegedActionException;
28 import java.security.PrivilegedExceptionAction;
29 import java.text.SimpleDateFormat;
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.Collection;
33 import java.util.Enumeration;
34 import java.util.LinkedHashSet;
35 import java.util.List;
36 import java.util.Locale;
37 import java.util.Map;
38 import java.util.Set;
39 import java.util.function.Supplier;
40
41 import javax.servlet.ServletOutputStream;
42 import javax.servlet.ServletResponse;
43 import javax.servlet.SessionTrackingMode;
44 import javax.servlet.http.Cookie;
45 import javax.servlet.http.HttpServletResponse;
46 import javax.servlet.http.HttpServletResponseWrapper;
47
48 import org.apache.catalina.Context;
49 import org.apache.catalina.Session;
50 import org.apache.catalina.security.SecurityUtil;
51 import org.apache.catalina.util.SessionConfig;
52 import org.apache.coyote.ActionCode;
53 import org.apache.juli.logging.Log;
54 import org.apache.juli.logging.LogFactory;
55 import org.apache.tomcat.util.buf.CharChunk;
56 import org.apache.tomcat.util.buf.UEncoder;
57 import org.apache.tomcat.util.buf.UEncoder.SafeCharsSet;
58 import org.apache.tomcat.util.buf.UriUtil;
59 import org.apache.tomcat.util.http.FastHttpDateFormat;
60 import org.apache.tomcat.util.http.MimeHeaders;
61 import org.apache.tomcat.util.http.parser.MediaTypeCache;
62 import org.apache.tomcat.util.res.StringManager;
63 import org.apache.tomcat.util.security.Escape;
64
65 /**
66 * Wrapper object for the Coyote response.
67 *
68 * @author Remy Maucherat
69 * @author Craig R. McClanahan
70 */
71 public class Response implements HttpServletResponse {
72
73 private static final Log log = LogFactory.getLog(Response.class);
74 protected static final StringManager sm = StringManager.getManager(Response.class);
75
76 private static final MediaTypeCache MEDIA_TYPE_CACHE = new MediaTypeCache(100);
77
78 /**
79 * Compliance with SRV.15.2.22.1. A call to Response.getWriter() if no
80 * character encoding has been specified will result in subsequent calls to
81 * Response.getCharacterEncoding() returning ISO-8859-1 and the Content-Type
82 * response header will include a charset=ISO-8859-1 component.
83 */
84 private static final boolean ENFORCE_ENCODING_IN_GET_WRITER;
85
86 static {
87 ENFORCE_ENCODING_IN_GET_WRITER = Boolean.parseBoolean(
88 System.getProperty("org.apache.catalina.connector.Response.ENFORCE_ENCODING_IN_GET_WRITER",
89 "true"));
90 }
91
92
93 // ----------------------------------------------------- Instance Variables
94
95 /**
96 * The date format we will use for creating date headers.
97 *
98 * @deprecated Unused. This will be removed in Tomcat 10
99 */
100 @Deprecated
101 protected SimpleDateFormat format = null;
102
103
104 public Response() {
105 this(OutputBuffer.DEFAULT_BUFFER_SIZE);
106 }
107
108
109 public Response(int outputBufferSize) {
110 outputBuffer = new OutputBuffer(outputBufferSize);
111 }
112
113
114 // ------------------------------------------------------------- Properties
115
116 /**
117 * Coyote response.
118 */
119 protected org.apache.coyote.Response coyoteResponse;
120
121 /**
122 * Set the Coyote response.
123 *
124 * @param coyoteResponse The Coyote response
125 */
126 public void setCoyoteResponse(org.apache.coyote.Response coyoteResponse) {
127 this.coyoteResponse = coyoteResponse;
128 outputBuffer.setResponse(coyoteResponse);
129 }
130
131 /**
132 * @return the Coyote response.
133 */
134 public org.apache.coyote.Response getCoyoteResponse() {
135 return this.coyoteResponse;
136 }
137
138
139 /**
140 * @return the Context within which this Request is being processed.
141 */
142 public Context getContext() {
143 return request.getContext();
144 }
145
146
147 /**
148 * The associated output buffer.
149 */
150 protected final OutputBuffer outputBuffer;
151
152
153 /**
154 * The associated output stream.
155 */
156 protected CoyoteOutputStream outputStream;
157
158
159 /**
160 * The associated writer.
161 */
162 protected CoyoteWriter writer;
163
164
165 /**
166 * The application commit flag.
167 */
168 protected boolean appCommitted = false;
169
170
171 /**
172 * The included flag.
173 */
174 protected boolean included = false;
175
176
177 /**
178 * The characterEncoding flag
179 */
180 private boolean isCharacterEncodingSet = false;
181
182
183 /**
184 * Using output stream flag.
185 */
186 protected boolean usingOutputStream = false;
187
188
189 /**
190 * Using writer flag.
191 */
192 protected boolean usingWriter = false;
193
194
195 /**
196 * URL encoder.
197 */
198 protected final UEncoder urlEncoder = new UEncoder(SafeCharsSet.WITH_SLASH);
199
200
201 /**
202 * Recyclable buffer to hold the redirect URL.
203 */
204 protected final CharChunk redirectURLCC = new CharChunk();
205
206
207 /*
208 * Not strictly required but it makes generating HTTP/2 push requests a lot
209 * easier if these are retained until the response is recycled.
210 */
211 private final List<Cookie> cookies = new ArrayList<>();
212
213 private HttpServletResponse applicationResponse = null;
214
215
216 // --------------------------------------------------------- Public Methods
217
218 /**
219 * Release all object references, and initialize instance variables, in
220 * preparation for reuse of this object.
221 */
222 public void recycle() {
223
224 cookies.clear();
225 outputBuffer.recycle();
226 usingOutputStream = false;
227 usingWriter = false;
228 appCommitted = false;
229 included = false;
230 isCharacterEncodingSet = false;
231
232 applicationResponse = null;
233 if (getRequest().getDiscardFacades()) {
234 if (facade != null) {
235 facade.clear();
236 facade = null;
237 }
238 if (outputStream != null) {
239 outputStream.clear();
240 outputStream = null;
241 }
242 if (writer != null) {
243 writer.clear();
244 writer = null;
245 }
246 } else if (writer != null) {
247 writer.recycle();
248 }
249
250 }
251
252
253 public List<Cookie> getCookies() {
254 return cookies;
255 }
256
257
258 // ------------------------------------------------------- Response Methods
259
260 /**
261 * @return the number of bytes the application has actually written to the
262 * output stream. This excludes chunking, compression, etc. as well as
263 * headers.
264 */
265 public long getContentWritten() {
266 return outputBuffer.getContentWritten();
267 }
268
269
270 /**
271 * @return the number of bytes the actually written to the socket. This
272 * includes chunking, compression, etc. but excludes headers.
273 * @param flush if <code>true</code> will perform a buffer flush first
274 */
275 public long getBytesWritten(boolean flush) {
276 if (flush) {
277 try {
278 outputBuffer.flush();
279 } catch (IOException ioe) {
280 // Ignore - the client has probably closed the connection
281 }
282 }
283 return getCoyoteResponse().getBytesWritten(flush);
284 }
285
286 /**
287 * Set the application commit flag.
288 *
289 * @param appCommitted The new application committed flag value
290 */
291 public void setAppCommitted(boolean appCommitted) {
292 this.appCommitted = appCommitted;
293 }
294
295
296 /**
297 * Application commit flag accessor.
298 *
299 * @return <code>true</code> if the application has committed the response
300 */
301 public boolean isAppCommitted() {
302 return this.appCommitted || isCommitted() || isSuspended()
303 || ((getContentLength() > 0)
304 && (getContentWritten() >= getContentLength()));
305 }
306
307
308 /**
309 * The request with which this response is associated.
310 */
311 protected Request request = null;
312
313 /**
314 * @return the Request with which this Response is associated.
315 */
316 public org.apache.catalina.connector.Request getRequest() {
317 return this.request;
318 }
319
320 /**
321 * Set the Request with which this Response is associated.
322 *
323 * @param request The new associated request
324 */
325 public void setRequest(org.apache.catalina.connector.Request request) {
326 this.request = request;
327 }
328
329
330 /**
331 * The facade associated with this response.
332 */
333 protected ResponseFacade facade = null;
334
335
336 /**
337 * @return the <code>ServletResponse</code> for which this object
338 * is the facade.
339 */
340 public HttpServletResponse getResponse() {
341 if (facade == null) {
342 facade = new ResponseFacade(this);
343 }
344 if (applicationResponse == null) {
345 applicationResponse = facade;
346 }
347 return applicationResponse;
348 }
349
350
351 /**
352 * Set a wrapped HttpServletResponse to pass to the application. Components
353 * wishing to wrap the response should obtain the response via
354 * {@link #getResponse()}, wrap it and then call this method with the
355 * wrapped response.
356 *
357 * @param applicationResponse The wrapped response to pass to the
358 * application
359 */
360 public void setResponse(HttpServletResponse applicationResponse) {
361 // Check the wrapper wraps this request
362 ServletResponse r = applicationResponse;
363 while (r instanceof HttpServletResponseWrapper) {
364 r = ((HttpServletResponseWrapper) r).getResponse();
365 }
366 if (r != facade) {
367 throw new IllegalArgumentException(sm.getString("response.illegalWrap"));
368 }
369 this.applicationResponse = applicationResponse;
370 }
371
372
373 /**
374 * Set the suspended flag.
375 *
376 * @param suspended The new suspended flag value
377 */
378 public void setSuspended(boolean suspended) {
379 outputBuffer.setSuspended(suspended);
380 }
381
382
383 /**
384 * Suspended flag accessor.
385 *
386 * @return <code>true</code> if the response is suspended
387 */
388 public boolean isSuspended() {
389 return outputBuffer.isSuspended();
390 }
391
392
393 /**
394 * Closed flag accessor.
395 *
396 * @return <code>true</code> if the response has been closed
397 */
398 public boolean isClosed() {
399 return outputBuffer.isClosed();
400 }
401
402
403 /**
404 * Set the error flag.
405 *
406 * @return <code>false</code> if the error flag was already set
407 */
408 public boolean setError() {
409 return getCoyoteResponse().setError();
410 }
411
412
413 /**
414 * Error flag accessor.
415 *
416 * @return <code>true</code> if the response has encountered an error
417 */
418 public boolean isError() {
419 return getCoyoteResponse().isError();
420 }
421
422
423 public boolean isErrorReportRequired() {
424 return getCoyoteResponse().isErrorReportRequired();
425 }
426
427
428 public boolean setErrorReported() {
429 return getCoyoteResponse().setErrorReported();
430 }
431
432
433 /**
434 * Perform whatever actions are required to flush and close the output
435 * stream or writer, in a single operation.
436 *
437 * @exception IOException if an input/output error occurs
438 */
439 public void finishResponse() throws IOException {
440 // Writing leftover bytes
441 outputBuffer.close();
442 }
443
444
445 /**
446 * @return the content length that was set or calculated for this Response.
447 */
448 public int getContentLength() {
449 return getCoyoteResponse().getContentLength();
450 }
451
452
453 /**
454 * @return the content type that was set or calculated for this response,
455 * or <code>null</code> if no content type was set.
456 */
457 @Override
458 public String getContentType() {
459 return getCoyoteResponse().getContentType();
460 }
461
462
463 /**
464 * Return a PrintWriter that can be used to render error messages,
465 * regardless of whether a stream or writer has already been acquired.
466 *
467 * @return Writer which can be used for error reports. If the response is
468 * not an error report returned using sendError or triggered by an
469 * unexpected exception thrown during the servlet processing
470 * (and only in that case), null will be returned if the response stream
471 * has already been used.
472 *
473 * @exception IOException if an input/output error occurs
474 */
475 public PrintWriter getReporter() throws IOException {
476 if (outputBuffer.isNew()) {
477 outputBuffer.checkConverter();
478 if (writer == null) {
479 writer = new CoyoteWriter(outputBuffer);
480 }
481 return writer;
482 } else {
483 return null;
484 }
485 }
486
487
488 // ------------------------------------------------ ServletResponse Methods
489
490
491 /**
492 * Flush the buffer and commit this response.
493 *
494 * @exception IOException if an input/output error occurs
495 */
496 @Override
497 public void flushBuffer() throws IOException {
498 outputBuffer.flush();
499 }
500
501
502 /**
503 * @return the actual buffer size used for this Response.
504 */
505 @Override
506 public int getBufferSize() {
507 return outputBuffer.getBufferSize();
508 }
509
510
511 /**
512 * @return the character encoding used for this Response.
513 */
514 @Override
515 public String getCharacterEncoding() {
516 String charset = getCoyoteResponse().getCharacterEncoding();
517 if (charset != null) {
518 return charset;
519 }
520
521 Context context = getContext();
522 String result = null;
523 if (context != null) {
524 result = context.getResponseCharacterEncoding();
525 }
526
527 if (result == null) {
528 result = org.apache.coyote.Constants.DEFAULT_BODY_CHARSET.name();
529 }
530
531 return result;
532 }
533
534
535 /**
536 * @return the servlet output stream associated with this Response.
537 *
538 * @exception IllegalStateException if <code>getWriter</code> has
539 * already been called for this response
540 * @exception IOException if an input/output error occurs
541 */
542 @Override
543 public ServletOutputStream getOutputStream()
544 throws IOException {
545
546 if (usingWriter) {
547 throw new IllegalStateException
548 (sm.getString("coyoteResponse.getOutputStream.ise"));
549 }
550
551 usingOutputStream = true;
552 if (outputStream == null) {
553 outputStream = new CoyoteOutputStream(outputBuffer);
554 }
555 return outputStream;
556
557 }
558
559
560 /**
561 * @return the Locale assigned to this response.
562 */
563 @Override
564 public Locale getLocale() {
565 return getCoyoteResponse().getLocale();
566 }
567
568
569 /**
570 * @return the writer associated with this Response.
571 *
572 * @exception IllegalStateException if <code>getOutputStream</code> has
573 * already been called for this response
574 * @exception IOException if an input/output error occurs
575 */
576 @Override
577 public PrintWriter getWriter()
578 throws IOException {
579
580 if (usingOutputStream) {
581 throw new IllegalStateException
582 (sm.getString("coyoteResponse.getWriter.ise"));
583 }
584
585 if (ENFORCE_ENCODING_IN_GET_WRITER) {
586 /*
587 * If the response's character encoding has not been specified as
588 * described in <code>getCharacterEncoding</code> (i.e., the method
589 * just returns the default value <code>ISO-8859-1</code>),
590 * <code>getWriter</code> updates it to <code>ISO-8859-1</code>
591 * (with the effect that a subsequent call to getContentType() will
592 * include a charset=ISO-8859-1 component which will also be
593 * reflected in the Content-Type response header, thereby satisfying
594 * the Servlet spec requirement that containers must communicate the
595 * character encoding used for the servlet response's writer to the
596 * client).
597 */
598 setCharacterEncoding(getCharacterEncoding());
599 }
600
601 usingWriter = true;
602 outputBuffer.checkConverter();
603 if (writer == null) {
604 writer = new CoyoteWriter(outputBuffer);
605 }
606 return writer;
607 }
608
609
610 /**
611 * Has the output of this response already been committed?
612 *
613 * @return <code>true</code> if the response has been committed
614 */
615 @Override
616 public boolean isCommitted() {
617 return getCoyoteResponse().isCommitted();
618 }
619
620
621 /**
622 * Clear any content written to the buffer.
623 *
624 * @exception IllegalStateException if this response has already
625 * been committed
626 */
627 @Override
628 public void reset() {
629 // Ignore any call from an included servlet
630 if (included) {
631 return;
632 }
633
634 getCoyoteResponse().reset();
635 outputBuffer.reset();
636 usingOutputStream = false;
637 usingWriter = false;
638 isCharacterEncodingSet = false;
639 }
640
641
642 /**
643 * Reset the data buffer but not any status or header information.
644 *
645 * @exception IllegalStateException if the response has already
646 * been committed
647 */
648 @Override
649 public void resetBuffer() {
650 resetBuffer(false);
651 }
652
653
654 /**
655 * Reset the data buffer and the using Writer/Stream flags but not any
656 * status or header information.
657 *
658 * @param resetWriterStreamFlags <code>true</code> if the internal
659 * <code>usingWriter</code>, <code>usingOutputStream</code>,
660 * <code>isCharacterEncodingSet</code> flags should also be reset
661 *
662 * @exception IllegalStateException if the response has already
663 * been committed
664 */
665 public void resetBuffer(boolean resetWriterStreamFlags) {
666
667 if (isCommitted()) {
668 throw new IllegalStateException
669 (sm.getString("coyoteResponse.resetBuffer.ise"));
670 }
671
672 outputBuffer.reset(resetWriterStreamFlags);
673
674 if(resetWriterStreamFlags) {
675 usingOutputStream = false;
676 usingWriter = false;
677 isCharacterEncodingSet = false;
678 }
679
680 }
681
682
683 /**
684 * Set the buffer size to be used for this Response.
685 *
686 * @param size The new buffer size
687 *
688 * @exception IllegalStateException if this method is called after
689 * output has been committed for this response
690 */
691 @Override
692 public void setBufferSize(int size) {
693
694 if (isCommitted() || !outputBuffer.isNew()) {
695 throw new IllegalStateException
696 (sm.getString("coyoteResponse.setBufferSize.ise"));
697 }
698
699 outputBuffer.setBufferSize(size);
700
701 }
702
703
704 /**
705 * Set the content length (in bytes) for this Response.
706 *
707 * @param length The new content length
708 */
709 @Override
710 public void setContentLength(int length) {
711
712 setContentLengthLong(length);
713 }
714
715
716 @Override
717 public void setContentLengthLong(long length) {
718 if (isCommitted()) {
719 return;
720 }
721
722 // Ignore any call from an included servlet
723 if (included) {
724 return;
725 }
726
727 getCoyoteResponse().setContentLength(length);
728 }
729
730
731 /**
732 * Set the content type for this Response.
733 *
734 * @param type The new content type
735 */
736 @Override
737 public void setContentType(String type) {
738
739 if (isCommitted()) {
740 return;
741 }
742
743 // Ignore any call from an included servlet
744 if (included) {
745 return;
746 }
747
748 if (type == null) {
749 getCoyoteResponse().setContentType(null);
750 return;
751 }
752
753 String[] m = MEDIA_TYPE_CACHE.parse(type);
754 if (m == null) {
755 // Invalid - Assume no charset and just pass through whatever
756 // the user provided.
757 getCoyoteResponse().setContentTypeNoCharset(type);
758 return;
759 }
760
761 getCoyoteResponse().setContentTypeNoCharset(m[0]);
762
763 if (m[1] != null) {
764 // Ignore charset if getWriter() has already been called
765 if (!usingWriter) {
766 try {
767 getCoyoteResponse().setCharacterEncoding(m[1]);
768 } catch (UnsupportedEncodingException e) {
769 log.warn(sm.getString("coyoteResponse.encoding.invalid", m[1]), e);
770 }
771
772 isCharacterEncodingSet = true;
773 }
774 }
775 }
776
777
778 /**
779 * Overrides the name of the character encoding used in the body
780 * of the request. This method must be called prior to reading
781 * request parameters or reading input using getReader().
782 *
783 * @param charset String containing the name of the character encoding.
784 */
785 @Override
786 public void setCharacterEncoding(String charset) {
787
788 if (isCommitted()) {
789 return;
790 }
791
792 // Ignore any call from an included servlet
793 if (included) {
794 return;
795 }
796
797 // Ignore any call made after the getWriter has been invoked
798 // The default should be used
799 if (usingWriter) {
800 return;
801 }
802
803 try {
804 getCoyoteResponse().setCharacterEncoding(charset);
805 } catch (UnsupportedEncodingException e) {
806 log.warn(sm.getString("coyoteResponse.encoding.invalid", charset), e);
807 return;
808 }
809 isCharacterEncodingSet = true;
810 }
811
812
813 /**
814 * Set the Locale that is appropriate for this response, including
815 * setting the appropriate character encoding.
816 *
817 * @param locale The new locale
818 */
819 @Override
820 public void setLocale(Locale locale) {
821
822 if (isCommitted()) {
823 return;
824 }
825
826 // Ignore any call from an included servlet
827 if (included) {
828 return;
829 }
830
831 getCoyoteResponse().setLocale(locale);
832
833 // Ignore any call made after the getWriter has been invoked.
834 // The default should be used
835 if (usingWriter) {
836 return;
837 }
838
839 if (isCharacterEncodingSet) {
840 return;
841 }
842
843 // In some error handling scenarios, the context is unknown
844 // (e.g. a 404 when a ROOT context is not present)
845 Context context = getContext();
846 if (context != null) {
847 String charset = context.getCharset(locale);
848 if (charset != null) {
849 try {
850 getCoyoteResponse().setCharacterEncoding(charset);
851 } catch (UnsupportedEncodingException e) {
852 log.warn(sm.getString("coyoteResponse.encoding.invalid", charset), e);
853 }
854 }
855 }
856 }
857
858
859 // --------------------------------------------------- HttpResponse Methods
860
861
862 @Override
863 public String getHeader(String name) {
864 return getCoyoteResponse().getMimeHeaders().getHeader(name);
865 }
866
867
868 @Override
869 public Collection<String> getHeaderNames() {
870 MimeHeaders headers = getCoyoteResponse().getMimeHeaders();
871 int n = headers.size();
872 List<String> result = new ArrayList<>(n);
873 for (int i = 0; i < n; i++) {
874 result.add(headers.getName(i).toString());
875 }
876 return result;
877
878 }
879
880
881 @Override
882 public Collection<String> getHeaders(String name) {
883 Enumeration<String> enumeration = getCoyoteResponse().getMimeHeaders().values(name);
884 Set<String> result = new LinkedHashSet<>();
885 while (enumeration.hasMoreElements()) {
886 result.add(enumeration.nextElement());
887 }
888 return result;
889 }
890
891
892 /**
893 * @return the error message that was set with <code>sendError()</code>
894 * for this Response.
895 */
896 public String getMessage() {
897 return getCoyoteResponse().getMessage();
898 }
899
900
901 @Override
902 public int getStatus() {
903 return getCoyoteResponse().getStatus();
904 }
905
906
907 // -------------------------------------------- HttpServletResponse Methods
908
909 /**
910 * Add the specified Cookie to those that will be included with
911 * this Response.
912 *
913 * @param cookie Cookie to be added
914 */
915 @Override
916 public void addCookie(final Cookie cookie) {
917
918 // Ignore any call from an included servlet
919 if (included || isCommitted()) {
920 return;
921 }
922
923 cookies.add(cookie);
924
925 String header = generateCookieString(cookie);
926 //if we reached here, no exception, cookie is valid
927 // the header name is Set-Cookie for both "old" and v.1 ( RFC2109 )
928 // RFC2965 is not supported by browsers and the Servlet spec
929 // asks for 2109.
930 addHeader("Set-Cookie", header, getContext().getCookieProcessor().getCharset());
931 }
932
933 /**
934 * Special method for adding a session cookie as we should be overriding
935 * any previous.
936 *
937 * @param cookie The new session cookie to add the response
938 */
939 public void addSessionCookieInternal(final Cookie cookie) {
940 if (isCommitted()) {
941 return;
942 }
943
944 String name = cookie.getName();
945 final String headername = "Set-Cookie";
946 final String startsWith = name + "=";
947 String header = generateCookieString(cookie);
948 boolean set = false;
949 MimeHeaders headers = getCoyoteResponse().getMimeHeaders();
950 int n = headers.size();
951 for (int i = 0; i < n; i++) {
952 if (headers.getName(i).toString().equals(headername)) {
953 if (headers.getValue(i).toString().startsWith(startsWith)) {
954 headers.getValue(i).setString(header);
955 set = true;
956 }
957 }
958 }
959 if (!set) {
960 addHeader(headername, header);
961 }
962
963
964 }
965
966 public String generateCookieString(final Cookie cookie) {
967 // Web application code can receive a IllegalArgumentException
968 // from the generateHeader() invocation
969 if (SecurityUtil.isPackageProtectionEnabled()) {
970 return AccessController.doPrivileged(
971 new PrivilegedGenerateCookieString(getContext(), cookie));
972 } else {
973 return getContext().getCookieProcessor().generateHeader(cookie);
974 }
975 }
976
977
978 /**
979 * Add the specified date header to the specified value.
980 *
981 * @param name Name of the header to set
982 * @param value Date value to be set
983 */
984 @Override
985 public void addDateHeader(String name, long value) {
986
987 if (name == null || name.length() == 0) {
988 return;
989 }
990
991 if (isCommitted()) {
992 return;
993 }
994
995 // Ignore any call from an included servlet
996 if (included) {
997 return;
998 }
999
1000 addHeader(name, FastHttpDateFormat.formatDate(value));
1001 }
1002
1003
1004 /**
1005 * Add the specified header to the specified value.
1006 *
1007 * @param name Name of the header to set
1008 * @param value Value to be set
1009 */
1010 @Override
1011 public void addHeader(String name, String value) {
1012 addHeader(name, value, null);
1013 }
1014
1015
1016 private void addHeader(String name, String value, Charset charset) {
1017
1018 if (name == null || name.length() == 0 || value == null) {
1019 return;
1020 }
1021
1022 if (isCommitted()) {
1023 return;
1024 }
1025
1026 // Ignore any call from an included servlet
1027 if (included) {
1028 return;
1029 }
1030
1031 char cc=name.charAt(0);
1032 if (cc=='C' || cc=='c') {
1033 if (checkSpecialHeader(name, value))
1034 return;
1035 }
1036
1037 getCoyoteResponse().addHeader(name, value, charset);
1038 }
1039
1040
1041 /**
1042 * An extended version of this exists in {@link org.apache.coyote.Response}.
1043 * This check is required here to ensure that the usingWriter check in
1044 * {@link #setContentType(String)} is applied since usingWriter is not
1045 * visible to {@link org.apache.coyote.Response}
1046 *
1047 * Called from set/addHeader.
1048 * @return <code>true</code> if the header is special, no need to set the header.
1049 */
1050 private boolean checkSpecialHeader(String name, String value) {
1051 if (name.equalsIgnoreCase("Content-Type")) {
1052 setContentType(value);
1053 return true;
1054 }
1055 return false;
1056 }
1057
1058
1059 /**
1060 * Add the specified integer header to the specified value.
1061 *
1062 * @param name Name of the header to set
1063 * @param value Integer value to be set
1064 */
1065 @Override
1066 public void addIntHeader(String name, int value) {
1067
1068 if (name == null || name.length() == 0) {
1069 return;
1070 }
1071
1072 if (isCommitted()) {
1073 return;
1074 }
1075
1076 // Ignore any call from an included servlet
1077 if (included) {
1078 return;
1079 }
1080
1081 addHeader(name, "" + value);
1082
1083 }
1084
1085
1086 /**
1087 * Has the specified header been set already in this response?
1088 *
1089 * @param name Name of the header to check
1090 * @return <code>true</code> if the header has been set
1091 */
1092 @Override
1093 public boolean containsHeader(String name) {
1094 // Need special handling for Content-Type and Content-Length due to
1095 // special handling of these in coyoteResponse
1096 char cc=name.charAt(0);
1097 if(cc=='C' || cc=='c') {
1098 if(name.equalsIgnoreCase("Content-Type")) {
1099 // Will return null if this has not been set
1100 return (getCoyoteResponse().getContentType() != null);
1101 }
1102 if(name.equalsIgnoreCase("Content-Length")) {
1103 // -1 means not known and is not sent to client
1104 return (getCoyoteResponse().getContentLengthLong() != -1);
1105 }
1106 }
1107
1108 return getCoyoteResponse().containsHeader(name);
1109 }
1110
1111
1112 @Override
1113 public void setTrailerFields(Supplier<Map<String, String>> supplier) {
1114 getCoyoteResponse().setTrailerFields(supplier);
1115 }
1116
1117
1118 @Override
1119 public Supplier<Map<String, String>> getTrailerFields() {
1120 return getCoyoteResponse().getTrailerFields();
1121 }
1122
1123
1124 /**
1125 * Encode the session identifier associated with this response
1126 * into the specified redirect URL, if necessary.
1127 *
1128 * @param url URL to be encoded
1129 * @return <code>true</code> if the URL was encoded
1130 */
1131 @Override
1132 public String encodeRedirectURL(String url) {
1133 if (isEncodeable(toAbsolute(url))) {
1134 return toEncoded(url, request.getSessionInternal().getIdInternal());
1135 } else {
1136 return url;
1137 }
1138 }
1139
1140
1141 /**
1142 * Encode the session identifier associated with this response
1143 * into the specified redirect URL, if necessary.
1144 *
1145 * @param url URL to be encoded
1146 * @return <code>true</code> if the URL was encoded
1147 *
1148 * @deprecated As of Version 2.1 of the Java Servlet API, use
1149 * <code>encodeRedirectURL()</code> instead.
1150 */
1151 @Override
1152 @Deprecated
1153 public String encodeRedirectUrl(String url) {
1154 return encodeRedirectURL(url);
1155 }
1156
1157
1158 /**
1159 * Encode the session identifier associated with this response
1160 * into the specified URL, if necessary.
1161 *
1162 * @param url URL to be encoded
1163 * @return <code>true</code> if the URL was encoded
1164 */
1165 @Override
1166 public String encodeURL(String url) {
1167
1168 String absolute;
1169 try {
1170 absolute = toAbsolute(url);
1171 } catch (IllegalArgumentException iae) {
1172 // Relative URL
1173 return url;
1174 }
1175
1176 if (isEncodeable(absolute)) {
1177 // W3c spec clearly said
1178 if (url.equalsIgnoreCase("")) {
1179 url = absolute;
1180 } else if (url.equals(absolute) && !hasPath(url)) {
1181 url += '/';
1182 }
1183 return toEncoded(url, request.getSessionInternal().getIdInternal());
1184 } else {
1185 return url;
1186 }
1187
1188 }
1189
1190
1191 /**
1192 * Encode the session identifier associated with this response
1193 * into the specified URL, if necessary.
1194 *
1195 * @param url URL to be encoded
1196 * @return <code>true</code> if the URL was encoded
1197 *
1198 * @deprecated As of Version 2.1 of the Java Servlet API, use
1199 * <code>encodeURL()</code> instead.
1200 */
1201 @Override
1202 @Deprecated
1203 public String encodeUrl(String url) {
1204 return encodeURL(url);
1205 }
1206
1207
1208 /**
1209 * Send an acknowledgement of a request.
1210 *
1211 * @exception IOException if an input/output error occurs
1212 */
1213 public void sendAcknowledgement()
1214 throws IOException {
1215
1216 if (isCommitted()) {
1217 return;
1218 }
1219
1220 // Ignore any call from an included servlet
1221 if (included) {
1222 return;
1223 }
1224
1225 getCoyoteResponse().action(ActionCode.ACK, null);
1226 }
1227
1228
1229 /**
1230 * Send an error response with the specified status and a
1231 * default message.
1232 *
1233 * @param status HTTP status code to send
1234 *
1235 * @exception IllegalStateException if this response has
1236 * already been committed
1237 * @exception IOException if an input/output error occurs
1238 */
1239 @Override
1240 public void sendError(int status) throws IOException {
1241 sendError(status, null);
1242 }
1243
1244
1245 /**
1246 * Send an error response with the specified status and message.
1247 *
1248 * @param status HTTP status code to send
1249 * @param message Corresponding message to send
1250 *
1251 * @exception IllegalStateException if this response has
1252 * already been committed
1253 * @exception IOException if an input/output error occurs
1254 */
1255 @Override
1256 public void sendError(int status, String message) throws IOException {
1257
1258 if (isCommitted()) {
1259 throw new IllegalStateException
1260 (sm.getString("coyoteResponse.sendError.ise"));
1261 }
1262
1263 // Ignore any call from an included servlet
1264 if (included) {
1265 return;
1266 }
1267
1268 setError();
1269
1270 getCoyoteResponse().setStatus(status);
1271 getCoyoteResponse().setMessage(message);
1272
1273 // Clear any data content that has been buffered
1274 resetBuffer();
1275
1276 // Cause the response to be finished (from the application perspective)
1277 setSuspended(true);
1278 }
1279
1280
1281 /**
1282 * Send a temporary redirect to the specified redirect location URL.
1283 *
1284 * @param location Location URL to redirect to
1285 *
1286 * @exception IllegalStateException if this response has
1287 * already been committed
1288 * @exception IOException if an input/output error occurs
1289 */
1290 @Override
1291 public void sendRedirect(String location) throws IOException {
1292 sendRedirect(location, SC_FOUND);
1293 }
1294
1295
1296 /**
1297 * Internal method that allows a redirect to be sent with a status other
1298 * than {@link HttpServletResponse#SC_FOUND} (302). No attempt is made to
1299 * validate the status code.
1300 *
1301 * @param location Location URL to redirect to
1302 * @param status HTTP status code that will be sent
1303 * @throws IOException an IO exception occurred
1304 */
1305 public void sendRedirect(String location, int status) throws IOException {
1306 if (isCommitted()) {
1307 throw new IllegalStateException(sm.getString("coyoteResponse.sendRedirect.ise"));
1308 }
1309
1310 // Ignore any call from an included servlet
1311 if (included) {
1312 return;
1313 }
1314
1315 // Clear any data content that has been buffered
1316 resetBuffer(true);
1317
1318 // Generate a temporary redirect to the specified location
1319 try {
1320 String locationUri;
1321 // Relative redirects require HTTP/1.1
1322 if (getRequest().getCoyoteRequest().getSupportsRelativeRedirects() &&
1323 getContext().getUseRelativeRedirects()) {
1324 locationUri = location;
1325 } else {
1326 locationUri = toAbsolute(location);
1327 }
1328 setStatus(status);
1329 setHeader("Location", locationUri);
1330 if (getContext().getSendRedirectBody()) {
1331 PrintWriter writer = getWriter();
1332 writer.print(sm.getString("coyoteResponse.sendRedirect.note",
1333 Escape.htmlElementContent(locationUri)));
1334 flushBuffer();
1335 }
1336 } catch (IllegalArgumentException e) {
1337 log.warn(sm.getString("response.sendRedirectFail", location), e);
1338 setStatus(SC_NOT_FOUND);
1339 }
1340
1341 // Cause the response to be finished (from the application perspective)
1342 setSuspended(true);
1343 }
1344
1345
1346 /**
1347 * Set the specified date header to the specified value.
1348 *
1349 * @param name Name of the header to set
1350 * @param value Date value to be set
1351 */
1352 @Override
1353 public void setDateHeader(String name, long value) {
1354
1355 if (name == null || name.length() == 0) {
1356 return;
1357 }
1358
1359 if (isCommitted()) {
1360 return;
1361 }
1362
1363 // Ignore any call from an included servlet
1364 if (included) {
1365 return;
1366 }
1367
1368 setHeader(name, FastHttpDateFormat.formatDate(value));
1369 }
1370
1371
1372 /**
1373 * Set the specified header to the specified value.
1374 *
1375 * @param name Name of the header to set
1376 * @param value Value to be set
1377 */
1378 @Override
1379 public void setHeader(String name, String value) {
1380
1381 if (name == null || name.length() == 0 || value == null) {
1382 return;
1383 }
1384
1385 if (isCommitted()) {
1386 return;
1387 }
1388
1389 // Ignore any call from an included servlet
1390 if (included) {
1391 return;
1392 }
1393
1394 char cc=name.charAt(0);
1395 if (cc=='C' || cc=='c') {
1396 if (checkSpecialHeader(name, value)) {
1397 return;
1398 }
1399 }
1400
1401 getCoyoteResponse().setHeader(name, value);
1402 }
1403
1404
1405 /**
1406 * Set the specified integer header to the specified value.
1407 *
1408 * @param name Name of the header to set
1409 * @param value Integer value to be set
1410 */
1411 @Override
1412 public void setIntHeader(String name, int value) {
1413
1414 if (name == null || name.length() == 0) {
1415 return;
1416 }
1417
1418 if (isCommitted()) {
1419 return;
1420 }
1421
1422 // Ignore any call from an included servlet
1423 if (included) {
1424 return;
1425 }
1426
1427 setHeader(name, "" + value);
1428
1429 }
1430
1431
1432 /**
1433 * Set the HTTP status to be returned with this response.
1434 *
1435 * @param status The new HTTP status
1436 */
1437 @Override
1438 public void setStatus(int status) {
1439 setStatus(status, null);
1440 }
1441
1442
1443 /**
1444 * Set the HTTP status and message to be returned with this response.
1445 *
1446 * @param status The new HTTP status
1447 * @param message The associated text message
1448 *
1449 * @deprecated As of Version 2.1 of the Java Servlet API, this method
1450 * has been deprecated due to the ambiguous meaning of the message
1451 * parameter.
1452 */
1453 @Override
1454 @Deprecated
1455 public void setStatus(int status, String message) {
1456
1457 if (isCommitted()) {
1458 return;
1459 }
1460
1461 // Ignore any call from an included servlet
1462 if (included) {
1463 return;
1464 }
1465
1466 getCoyoteResponse().setStatus(status);
1467 getCoyoteResponse().setMessage(message);
1468
1469 }
1470
1471
1472 // ------------------------------------------------------ Protected Methods
1473
1474 /**
1475 * Return <code>true</code> if the specified URL should be encoded with
1476 * a session identifier. This will be true if all of the following
1477 * conditions are met:
1478 * <ul>
1479 * <li>The request we are responding to asked for a valid session
1480 * <li>The requested session ID was not received via a cookie
1481 * <li>The specified URL points back to somewhere within the web
1482 * application that is responding to this request
1483 * </ul>
1484 *
1485 * @param location Absolute URL to be validated
1486 * @return <code>true</code> if the URL should be encoded
1487 */
1488 protected boolean isEncodeable(final String location) {
1489
1490 if (location == null) {
1491 return false;
1492 }
1493
1494 // Is this an intra-document reference?
1495 if (location.startsWith("#")) {
1496 return false;
1497 }
1498
1499 // Are we in a valid session that is not using cookies?
1500 final Request hreq = request;
1501 final Session session = hreq.getSessionInternal(false);
1502 if (session == null) {
1503 return false;
1504 }
1505 if (hreq.isRequestedSessionIdFromCookie()) {
1506 return false;
1507 }
1508
1509 // Is URL encoding permitted
1510 if (!hreq.getServletContext().getEffectiveSessionTrackingModes().
1511 contains(SessionTrackingMode.URL)) {
1512 return false;
1513 }
1514
1515 if (SecurityUtil.isPackageProtectionEnabled()) {
1516 Boolean result = AccessController.doPrivileged(
1517 new PrivilegedDoIsEncodable(getContext(), hreq, session, location));
1518 return result.booleanValue();
1519 } else {
1520 return doIsEncodeable(getContext(), hreq, session, location);
1521 }
1522 }
1523
1524
1525 private static boolean doIsEncodeable(Context context, Request hreq, Session session,
1526 String location) {
1527 // Is this a valid absolute URL?
1528 URL url = null;
1529 try {
1530 url = new URL(location);
1531 } catch (MalformedURLException e) {
1532 return false;
1533 }
1534
1535 // Does this URL match down to (and including) the context path?
1536 if (!hreq.getScheme().equalsIgnoreCase(url.getProtocol())) {
1537 return false;
1538 }
1539 if (!hreq.getServerName().equalsIgnoreCase(url.getHost())) {
1540 return false;
1541 }
1542 int serverPort = hreq.getServerPort();
1543 if (serverPort == -1) {
1544 if ("https".equals(hreq.getScheme())) {
1545 serverPort = 443;
1546 } else {
1547 serverPort = 80;
1548 }
1549 }
1550 int urlPort = url.getPort();
1551 if (urlPort == -1) {
1552 if ("https".equals(url.getProtocol())) {
1553 urlPort = 443;
1554 } else {
1555 urlPort = 80;
1556 }
1557 }
1558 if (serverPort != urlPort) {
1559 return false;
1560 }
1561
1562 String contextPath = context.getPath();
1563 if (contextPath != null) {
1564 String file = url.getFile();
1565 if (!file.startsWith(contextPath)) {
1566 return false;
1567 }
1568 String tok = ";" + SessionConfig.getSessionUriParamName(context) + "=" +
1569 session.getIdInternal();
1570 if( file.indexOf(tok, contextPath.length()) >= 0 ) {
1571 return false;
1572 }
1573 }
1574
1575 // This URL belongs to our web application, so it is encodeable
1576 return true;
1577
1578 }
1579
1580
1581 /**
1582 * Convert (if necessary) and return the absolute URL that represents the
1583 * resource referenced by this possibly relative URL. If this URL is
1584 * already absolute, return it unchanged.
1585 *
1586 * @param location URL to be (possibly) converted and then returned
1587 * @return the encoded URL
1588 *
1589 * @exception IllegalArgumentException if a MalformedURLException is
1590 * thrown when converting the relative URL to an absolute one
1591 */
1592 protected String toAbsolute(String location) {
1593
1594 if (location == null) {
1595 return location;
1596 }
1597
1598 boolean leadingSlash = location.startsWith("/");
1599
1600 if (location.startsWith("//")) {
1601 // Scheme relative
1602 redirectURLCC.recycle();
1603 // Add the scheme
1604 String scheme = request.getScheme();
1605 try {
1606 redirectURLCC.append(scheme, 0, scheme.length());
1607 redirectURLCC.append(':');
1608 redirectURLCC.append(location, 0, location.length());
1609 return redirectURLCC.toString();
1610 } catch (IOException e) {
1611 IllegalArgumentException iae =
1612 new IllegalArgumentException(location);
1613 iae.initCause(e);
1614 throw iae;
1615 }
1616
1617 } else if (leadingSlash || !UriUtil.hasScheme(location)) {
1618
1619 redirectURLCC.recycle();
1620
1621 String scheme = request.getScheme();
1622 String name = request.getServerName();
1623 int port = request.getServerPort();
1624
1625 try {
1626 redirectURLCC.append(scheme, 0, scheme.length());
1627 redirectURLCC.append("://", 0, 3);
1628 redirectURLCC.append(name, 0, name.length());
1629 if ((scheme.equals("http") && port != 80)
1630 || (scheme.equals("https") && port != 443)) {
1631 redirectURLCC.append(':');
1632 String portS = port + "";
1633 redirectURLCC.append(portS, 0, portS.length());
1634 }
1635 if (!leadingSlash) {
1636 String relativePath = request.getDecodedRequestURI();
1637 int pos = relativePath.lastIndexOf('/');
1638 CharChunk encodedURI = null;
1639 if (SecurityUtil.isPackageProtectionEnabled() ){
1640 try{
1641 encodedURI = AccessController.doPrivileged(
1642 new PrivilgedEncodeUrl(urlEncoder, relativePath, pos));
1643 } catch (PrivilegedActionException pae){
1644 IllegalArgumentException iae =
1645 new IllegalArgumentException(location);
1646 iae.initCause(pae.getException());
1647 throw iae;
1648 }
1649 } else {
1650 encodedURI = urlEncoder.encodeURL(relativePath, 0, pos);
1651 }
1652 redirectURLCC.append(encodedURI);
1653 encodedURI.recycle();
1654 redirectURLCC.append('/');
1655 }
1656 redirectURLCC.append(location, 0, location.length());
1657
1658 normalize(redirectURLCC);
1659 } catch (IOException e) {
1660 IllegalArgumentException iae =
1661 new IllegalArgumentException(location);
1662 iae.initCause(e);
1663 throw iae;
1664 }
1665
1666 return redirectURLCC.toString();
1667
1668 } else {
1669
1670 return location;
1671
1672 }
1673
1674 }
1675
1676 /**
1677 * Removes /./ and /../ sequences from absolute URLs.
1678 * Code borrowed heavily from CoyoteAdapter.normalize()
1679 *
1680 * @param cc the char chunk containing the chars to normalize
1681 */
1682 private void normalize(CharChunk cc) {
1683 // Strip query string and/or fragment first as doing it this way makes
1684 // the normalization logic a lot simpler
1685 int truncate = cc.indexOf('?');
1686 if (truncate == -1) {
1687 truncate = cc.indexOf('#');
1688 }
1689 char[] truncateCC = null;
1690 if (truncate > -1) {
1691 truncateCC = Arrays.copyOfRange(cc.getBuffer(),
1692 cc.getStart() + truncate, cc.getEnd());
1693 cc.setEnd(cc.getStart() + truncate);
1694 }
1695
1696 if (cc.endsWith("/.") || cc.endsWith("/..")) {
1697 try {
1698 cc.append('/');
1699 } catch (IOException e) {
1700 throw new IllegalArgumentException(cc.toString(), e);
1701 }
1702 }
1703
1704 char[] c = cc.getChars();
1705 int start = cc.getStart();
1706 int end = cc.getEnd();
1707 int index = 0;
1708 int startIndex = 0;
1709
1710 // Advance past the first three / characters (should place index just
1711 // scheme://host[:port]
1712
1713 for (int i = 0; i < 3; i++) {
1714 startIndex = cc.indexOf('/', startIndex + 1);
1715 }
1716
1717 // Remove /./
1718 index = startIndex;
1719 while (true) {
1720 index = cc.indexOf("/./", 0, 3, index);
1721 if (index < 0) {
1722 break;
1723 }
1724 copyChars(c, start + index, start + index + 2,
1725 end - start - index - 2);
1726 end = end - 2;
1727 cc.setEnd(end);
1728 }
1729
1730 // Remove /../
1731 index = startIndex;
1732 int pos;
1733 while (true) {
1734 index = cc.indexOf("/../", 0, 4, index);
1735 if (index < 0) {
1736 break;
1737 }
1738 // Can't go above the server root
1739 if (index == startIndex) {
1740 throw new IllegalArgumentException();
1741 }
1742 int index2 = -1;
1743 for (pos = start + index - 1; (pos >= 0) && (index2 < 0); pos --) {
1744 if (c[pos] == (byte) '/') {
1745 index2 = pos;
1746 }
1747 }
1748 copyChars(c, start + index2, start + index + 3,
1749 end - start - index - 3);
1750 end = end + index2 - index - 3;
1751 cc.setEnd(end);
1752 index = index2;
1753 }
1754
1755 // Add the query string and/or fragment (if present) back in
1756 if (truncateCC != null) {
1757 try {
1758 cc.append(truncateCC, 0, truncateCC.length);
1759 } catch (IOException ioe) {
1760 throw new IllegalArgumentException(ioe);
1761 }
1762 }
1763 }
1764
1765 private void copyChars(char[] c, int dest, int src, int len) {
1766 System.arraycopy(c, src, c, dest, len);
1767 }
1768
1769
1770 /**
1771 * Determine if an absolute URL has a path component.
1772 *
1773 * @param uri the URL that will be checked
1774 * @return <code>true</code> if the URL has a path
1775 */
1776 private boolean hasPath(String uri) {
1777 int pos = uri.indexOf("://");
1778 if (pos < 0) {
1779 return false;
1780 }
1781 pos = uri.indexOf('/', pos + 3);
1782 if (pos < 0) {
1783 return false;
1784 }
1785 return true;
1786 }
1787
1788 /**
1789 * Return the specified URL with the specified session identifier
1790 * suitably encoded.
1791 *
1792 * @param url URL to be encoded with the session id
1793 * @param sessionId Session id to be included in the encoded URL
1794 * @return the encoded URL
1795 */
1796 protected String toEncoded(String url, String sessionId) {
1797 if ((url == null) || (sessionId == null)) {
1798 return url;
1799 }
1800
1801 String path = url;
1802 String query = "";
1803 String anchor = "";
1804 int question = url.indexOf('?');
1805 if (question >= 0) {
1806 path = url.substring(0, question);
1807 query = url.substring(question);
1808 }
1809 int pound = path.indexOf('#');
1810 if (pound >= 0) {
1811 anchor = path.substring(pound);
1812 path = path.substring(0, pound);
1813 }
1814 StringBuilder sb = new StringBuilder(path);
1815 if( sb.length() > 0 ) { // jsessionid can't be first.
1816 sb.append(";");
1817 sb.append(SessionConfig.getSessionUriParamName(
1818 request.getContext()));
1819 sb.append("=");
1820 sb.append(sessionId);
1821 }
1822 sb.append(anchor);
1823 sb.append(query);
1824 return sb.toString();
1825 }
1826
1827
1828 private static class PrivilegedGenerateCookieString implements PrivilegedAction<String> {
1829
1830 private final Context context;
1831 private final Cookie cookie;
1832
1833 public PrivilegedGenerateCookieString(Context context, Cookie cookie) {
1834 this.context = context;
1835 this.cookie = cookie;
1836 }
1837
1838 @Override
1839 public String run(){
1840 return context.getCookieProcessor().generateHeader(cookie);
1841 }
1842 }
1843
1844
1845 private static class PrivilegedDoIsEncodable implements PrivilegedAction<Boolean> {
1846
1847 private final Context context;
1848 private final Request hreq;
1849 private final Session session;
1850 private final String location;
1851
1852 public PrivilegedDoIsEncodable(Context context, Request hreq, Session session,
1853 String location) {
1854 this.context = context;
1855 this.hreq = hreq;
1856 this.session = session;
1857 this.location = location;
1858 }
1859
1860 @Override
1861 public Boolean run(){
1862 return Boolean.valueOf(doIsEncodeable(context, hreq, session, location));
1863 }
1864 }
1865
1866
1867 private static class PrivilgedEncodeUrl implements PrivilegedExceptionAction<CharChunk> {
1868
1869 private final UEncoder urlEncoder;
1870 private final String relativePath;
1871 private final int end;
1872
1873 public PrivilgedEncodeUrl(UEncoder urlEncoder, String relativePath, int end) {
1874 this.urlEncoder = urlEncoder;
1875 this.relativePath = relativePath;
1876 this.end = end;
1877 }
1878
1879 @Override
1880 public CharChunk run() throws IOException{
1881 return urlEncoder.encodeURL(relativePath, 0, end);
1882 }
1883 }
1884 }
1885