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