001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.protocol.http.servlet;
018
019import java.io.IOException;
020import java.time.Instant;
021import jakarta.servlet.http.Cookie;
022import jakarta.servlet.http.HttpServletRequest;
023import jakarta.servlet.http.HttpServletResponse;
024import org.apache.wicket.WicketRuntimeException;
025import org.apache.wicket.request.Url;
026import org.apache.wicket.request.UrlRenderer;
027import org.apache.wicket.request.cycle.RequestCycle;
028import org.apache.wicket.request.http.WebResponse;
029import org.apache.wicket.util.lang.Args;
030
031/**
032 * WebResponse that wraps a {@link ServletWebResponse}.
033 * 
034 * @author Matej Knopp
035 */
036public class ServletWebResponse extends WebResponse
037{
038        private final HttpServletResponse httpServletResponse;
039        private final ServletWebRequest webRequest;
040
041        private boolean redirect = false;
042
043        /**
044         * Construct.
045         * 
046         * @param webRequest
047         * @param httpServletResponse
048         */
049        public ServletWebResponse(ServletWebRequest webRequest, HttpServletResponse httpServletResponse)
050        {
051                Args.notNull(webRequest, "webRequest");
052                Args.notNull(httpServletResponse, "httpServletResponse");
053
054                this.httpServletResponse = httpServletResponse;
055                this.webRequest = webRequest;
056        }
057
058        @Override
059        public void addCookie(Cookie cookie)
060        {
061                httpServletResponse.addCookie(cookie);
062        }
063
064        @Override
065        public void clearCookie(Cookie cookie)
066        {
067                cookie.setMaxAge(0);
068                cookie.setValue(null);
069                addCookie(cookie);
070        }
071
072        @Override
073        public void setContentLength(long length)
074        {
075                httpServletResponse.addHeader("Content-Length", Long.toString(length));
076        }
077
078        @Override
079        public void setContentType(String mimeType)
080        {
081                httpServletResponse.setContentType(mimeType);
082        }
083
084        @Override
085        public void setDateHeader(String name, Instant date)
086        {
087                Args.notNull(date, "date");
088                httpServletResponse.setDateHeader(name, date.toEpochMilli());
089        }
090
091        @Override
092        public boolean isHeaderSupported()
093        {
094                return !httpServletResponse.isCommitted();
095        }
096        
097        @Override
098        public void setHeader(String name, String value)
099        {
100                httpServletResponse.setHeader(name, value);
101        }
102
103        @Override
104        public void addHeader(String name, String value)
105        {
106                httpServletResponse.addHeader(name, value);
107        }
108
109        @Override
110        public void write(CharSequence sequence)
111        {
112                try
113                {
114                        httpServletResponse.getWriter().append(sequence);
115                }
116                catch (IOException e)
117                {
118                        throw new ResponseIOException(e);
119                }
120        }
121
122        @Override
123        public void write(byte[] array)
124        {
125                try
126                {
127                        httpServletResponse.getOutputStream().write(array);
128                }
129                catch (IOException e)
130                {
131                        throw new ResponseIOException(e);
132                }
133        }
134
135        @Override
136        public void write(byte[] array, int offset, int length)
137        {
138                try
139                {
140                        httpServletResponse.getOutputStream().write(array, offset, length);
141                }
142                catch (IOException e)
143                {
144                        throw new ResponseIOException(e);
145                }
146        }
147
148
149        @Override
150        public void setStatus(int sc)
151        {
152                httpServletResponse.setStatus(sc);
153        }
154
155        @Override
156        public void sendError(int sc, String msg)
157        {
158                try
159                {
160                        if (msg == null)
161                        {
162                                httpServletResponse.sendError(sc);
163                        }
164                        else
165                        {
166                                httpServletResponse.sendError(sc, msg);
167                        }
168                }
169                catch (IOException e)
170                {
171                        throw new WicketRuntimeException(e);
172                }
173        }
174
175        @Override
176        public String encodeURL(CharSequence url)
177        {
178                Args.notNull(url, "url");
179
180                UrlRenderer urlRenderer = getUrlRenderer();
181
182                Url originalUrl = Url.parse(url);
183
184                /*
185                  WICKET-4645 - always pass absolute url to the web container for encoding
186                  because when REDIRECT_TO_BUFFER is in use Wicket may render PageB when
187                  PageA is actually the requested one and the web container cannot resolve
188                  the base url properly
189                 */
190                String fullUrl = urlRenderer.renderFullUrl(originalUrl);
191                String encodedFullUrl = httpServletResponse.encodeURL(fullUrl);
192
193                final String encodedUrl;
194                if (originalUrl.isFull())
195                {
196                        encodedUrl = encodedFullUrl;
197                }
198                else
199                {
200                        if (fullUrl.equals(encodedFullUrl))
201                        {
202                                // no encoding happened so just reuse the original url
203                                encodedUrl = url.toString();
204                        }
205                        else
206                        {
207                                // get the relative url with the jsessionid encoded in it
208                                Url _encoded = Url.parse(encodedFullUrl);
209                                encodedUrl = urlRenderer.renderRelativeUrl(_encoded);
210                        }
211                }
212                return encodedUrl;
213        }
214
215        private UrlRenderer getUrlRenderer()
216        {
217                RequestCycle requestCycle = RequestCycle.get();
218                if (requestCycle == null)
219                {
220                        return new UrlRenderer(webRequest);
221                }
222                return requestCycle.getUrlRenderer();
223        }
224
225        @Override
226        public String encodeRedirectURL(CharSequence url)
227        {
228                Args.notNull(url, "url");
229
230                UrlRenderer urlRenderer = getUrlRenderer();
231
232                Url originalUrl = Url.parse(url);
233
234                /*
235                 * WICKET-4645 - always pass absolute url to the web container for encoding because when
236                 * REDIRECT_TO_BUFFER is in use Wicket may render PageB when PageA is actually the requested
237                 * one and the web container cannot resolve the base url properly
238                 */
239                String fullUrl = urlRenderer.renderFullUrl(originalUrl);
240                String encodedFullUrl = httpServletResponse.encodeRedirectURL(fullUrl);
241
242                final String encodedUrl;
243                if (originalUrl.isFull())
244                {
245                        encodedUrl = encodedFullUrl;
246                }
247                else
248                {
249                        if (fullUrl.equals(encodedFullUrl))
250                        {
251                                // no encoding happened so just reuse the original url
252                                encodedUrl = url.toString();
253                        }
254                        else
255                        {
256                                // get the relative url with the jsessionid encoded in it
257                                Url _encoded = Url.parse(encodedFullUrl);
258                                encodedUrl = urlRenderer.renderRelativeUrl(_encoded);
259                        }
260                }
261                return encodedUrl;
262        }
263
264        @Override
265        public void sendRedirect(String url)
266        {
267                try
268                {
269                        redirect = true;
270                        url = encodeRedirectURL(url);
271
272                        // wicket redirects should never be cached
273                        disableCaching();
274
275                        if (webRequest.isAjax())
276                        {
277                                setHeader("Ajax-Location", url);
278                                setContentType("text/xml;charset=" +
279                                        webRequest.getContainerRequest().getCharacterEncoding());
280
281                                /*
282                                 * usually the Ajax-Location header is enough and we do not need to the redirect url
283                                 * into the response, but sometimes the response is processed via an iframe (eg
284                                 * using multipart ajax handling) and the headers are not available because XHR is
285                                 * not used and that is the only way javascript has access to response headers.
286                                 */
287                                httpServletResponse.getWriter().write(
288                                        "<ajax-response><redirect><![CDATA[" + url + "]]></redirect></ajax-response>");
289                        }
290                        else
291                        {
292                                httpServletResponse.sendRedirect(url);
293                        }
294                }
295                catch (IOException e)
296                {
297                        throw new WicketRuntimeException(e);
298                }
299        }
300
301        @Override
302        public boolean isRedirect()
303        {
304                return redirect;
305        }
306
307        @Override
308        public void flush()
309        {
310                try
311                {
312                        HttpServletRequest httpServletRequest = webRequest.getContainerRequest();
313                        if (httpServletRequest.isAsyncStarted() == false)
314                        {
315                                httpServletResponse.flushBuffer();
316                        }
317                }
318                catch (IOException e)
319                {
320                        throw new ResponseIOException(e);
321                }
322        }
323
324        @Override
325        public void reset()
326        {
327                super.reset();
328                httpServletResponse.reset();
329                redirect = false;
330        }
331
332        @Override
333        public HttpServletResponse getContainerResponse()
334        {
335                return httpServletResponse;
336        }
337
338}