View Javadoc
1   /*
2    * Copyright (c) 2002-2025 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit;
16  
17  import static java.nio.charset.StandardCharsets.UTF_8;
18  
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.io.Writer;
22  import java.net.URL;
23  import java.util.ArrayList;
24  import java.util.HashMap;
25  import java.util.List;
26  import java.util.Map;
27  
28  import javax.servlet.Servlet;
29  import javax.servlet.http.HttpServlet;
30  import javax.servlet.http.HttpServletRequest;
31  import javax.servlet.http.HttpServletResponse;
32  
33  import org.apache.commons.io.IOUtils;
34  import org.apache.commons.lang3.StringUtils;
35  import org.htmlunit.html.HtmlPage;
36  import org.htmlunit.http.HttpStatus;
37  import org.htmlunit.util.MimeType;
38  import org.htmlunit.util.NameValuePair;
39  import org.junit.jupiter.api.Test;
40  
41  /**
42   * Tests for {@link WebResponseData}.
43   *
44   * @author Daniel Gredler
45   * @author Ahmed Ashour
46   * @author Ronald Brill
47   */
48  public class WebResponseDataTest extends WebServerTestCase {
49  
50      private static final String GZIPPED_FILE = "testfiles/test.html.gz";
51      private static final String BROTLI_FILE = "testfiles/test.html.brotli";
52  
53      /**
54       * Tests that gzipped content is handled correctly.
55       * @throws Exception if the test fails
56       */
57      @Test
58      public void gZippedContent() throws Exception {
59          final InputStream stream = getClass().getClassLoader().getResourceAsStream(GZIPPED_FILE);
60          final byte[] zippedContent = IOUtils.toByteArray(stream);
61  
62          final List<NameValuePair> headers = new ArrayList<>();
63          headers.add(new NameValuePair("Content-Encoding", "gzip"));
64  
65          final WebResponseData data = new WebResponseData(zippedContent,
66                  HttpStatus.OK_200, HttpStatus.OK_200_MSG, headers);
67          final String body = new String(data.getBody(), UTF_8);
68          assertTrue(StringUtils.contains(body, "Test"));
69  
70          final WebResponse response = new WebResponse(data, new URL("http://test.com"), HttpMethod.GET, 1000);
71          assertEquals(body, response.getContentAsString(UTF_8));
72      }
73  
74      /**
75       * Tests that empty gzipped content is handled correctly (bug 3566999).
76       * @throws Exception if the test fails
77       */
78      @Test
79      public void emptyGZippedContent() throws Exception {
80          testEmptyGZippedContent(HttpStatus.OK_200, 0, null);
81      }
82  
83      /**
84       * Tests that empty gzipped content is handled correctly (bug #1510).
85       * @throws Exception if the test fails
86       */
87      @Test
88      public void contentLengthIsZero() throws Exception {
89          testEmptyGZippedContent(HttpStatus.OK_200, 0, MimeType.TEXT_HTML);
90      }
91  
92      /**
93       * Tests that empty gzipped content is handled correctly (bug #1510).
94       * @throws Exception if the test fails
95       */
96      @Test
97      public void contentLengthIsMissing() throws Exception {
98          testEmptyGZippedContent(HttpStatus.NO_CONTENT_204, -1, null);
99      }
100 
101     /**
102      * Tests that gzipped content is handled correctly.
103      * @throws Exception if the test fails
104      */
105     @Test
106     public void htmlEncodingGzipOnlyTextHtml() throws Exception {
107         final InputStream stream = getClass().getClassLoader().getResourceAsStream(GZIPPED_FILE);
108         final byte[] zippedContent = IOUtils.toByteArray(stream);
109 
110         final List<NameValuePair> headers = new ArrayList<>();
111         headers.add(new NameValuePair("Content-Encoding", "gzip-only-text/html"));
112         headers.add(new NameValuePair("content-type", MimeType.TEXT_HTML));
113 
114         final WebResponseData data = new WebResponseData(zippedContent,
115                 HttpStatus.OK_200, HttpStatus.OK_200_MSG, headers);
116         final String body = new String(data.getBody(), UTF_8);
117         assertTrue(StringUtils.contains(body, "Test"));
118 
119         final WebResponse response = new WebResponse(data, new URL("http://test.com"), HttpMethod.GET, 1000);
120         assertEquals(body, response.getContentAsString(UTF_8));
121     }
122 
123     /**
124      * Tests that gzipped content is handled correctly.
125      * @throws Exception if the test fails
126      */
127     @Test
128     public void binaryEncodingGzipOnlyTextHtml() throws Exception {
129         final InputStream stream = getClass().getClassLoader().getResourceAsStream("testfiles/tiny-png.img");
130         final byte[] zippedContent = IOUtils.toByteArray(stream);
131 
132         final List<NameValuePair> headers = new ArrayList<>();
133         headers.add(new NameValuePair("Content-Encoding", "gzip-only-text/html"));
134         headers.add(new NameValuePair("content-type", MimeType.IMAGE_PNG));
135 
136         final WebResponseData data = new WebResponseData(zippedContent,
137                HttpStatus.OK_200, HttpStatus.OK_200_MSG, headers);
138         final byte[] bytes = IOUtils.toByteArray(data.getInputStream());
139         assertEquals(128, bytes.length);
140         assertEquals(137 - 256, bytes[0]);
141         assertEquals(80, bytes[1]);
142         assertEquals(78, bytes[2]);
143 
144         final WebResponse response = new WebResponse(data, new URL("http://test.com"), HttpMethod.GET, 1000);
145         assertEquals(new String(bytes, UTF_8), response.getContentAsString(UTF_8));
146     }
147 
148     /**
149      * Tests that gzipped content is handled correctly.
150      * @throws Exception if the test fails
151      */
152     @Test
153     public void binaryEncodingNoGzip() throws Exception {
154         final InputStream stream = getClass().getClassLoader().getResourceAsStream("testfiles/xhtml.html");
155         final byte[] zippedContent = IOUtils.toByteArray(stream);
156 
157         final List<NameValuePair> headers = new ArrayList<>();
158         headers.add(new NameValuePair("Content-Encoding", "no-gzip"));
159         headers.add(new NameValuePair("content-type", MimeType.TEXT_HTML));
160 
161         final WebResponseData data = new WebResponseData(zippedContent,
162                 HttpStatus.OK_200, HttpStatus.OK_200_MSG, headers);
163         final String body = new String(data.getBody(), UTF_8);
164         assertTrue(StringUtils.contains(body, "Test"));
165 
166         final WebResponse response = new WebResponse(data, new URL("http://test.com"), HttpMethod.GET, 1000);
167         assertEquals(body, response.getContentAsString(UTF_8));
168     }
169 
170     private static void testEmptyGZippedContent(final int statusCode, final int contentLength,
171                 final String contentType) throws Exception {
172         final List<NameValuePair> headers = new ArrayList<>();
173         headers.add(new NameValuePair("Content-Encoding", "gzip"));
174 
175         if (contentLength != -1) {
176             headers.add(new NameValuePair(HttpHeader.CONTENT_LENGTH, String.valueOf(contentLength)));
177         }
178 
179         if (contentType != null) {
180             headers.add(new NameValuePair(HttpHeader.CONTENT_TYPE, contentType));
181         }
182 
183         final WebResponseData data = new WebResponseData("".getBytes(),
184                 HttpStatus.OK_200, HttpStatus.OK_200_MSG, headers);
185         final String body = new String(data.getBody(), UTF_8);
186 
187         final WebResponse response = new WebResponse(data, new URL("http://test.com"), HttpMethod.GET, 1000);
188         assertEquals(body, response.getContentAsString(UTF_8));
189     }
190 
191     /**
192      * Tests that broken gzipped content is handled correctly.
193      * @throws Exception if the test fails
194      */
195     @Test
196     public void brokenGZippedContent() throws Exception {
197         final List<NameValuePair> headers = new ArrayList<>();
198         headers.add(new NameValuePair("Content-Encoding", "gzip"));
199 
200         final WebResponseData data = new WebResponseData("Plain Content".getBytes(),
201                 HttpStatus.OK_200, HttpStatus.OK_200_MSG, headers);
202         try {
203             data.getBody();
204         }
205         catch (final RuntimeException e) {
206             assertTrue(e.getMessage(), StringUtils.contains(e.getMessage(), "Not in GZIP format"));
207         }
208     }
209 
210     /**
211      * Tests that brotli encoded content is handled correctly.
212      * @throws Exception if the test fails
213      */
214     @Test
215     public void brotliContent() throws Exception {
216         final InputStream stream = getClass().getClassLoader().getResourceAsStream(BROTLI_FILE);
217         final byte[] zippedContent = IOUtils.toByteArray(stream);
218 
219         final List<NameValuePair> headers = new ArrayList<>();
220         headers.add(new NameValuePair("Content-Encoding", "br"));
221 
222         final WebResponseData data = new WebResponseData(zippedContent,
223                 HttpStatus.OK_200, HttpStatus.OK_200_MSG, headers);
224         final String body = new String(data.getBody(), UTF_8);
225         assertTrue(StringUtils.contains(body, "Test"));
226 
227         final WebResponse response = new WebResponse(data, new URL("http://test.com"), HttpMethod.GET, 1000);
228         assertEquals(body, response.getContentAsString(UTF_8));
229     }
230 
231     /**
232      * Tests that broken gzipped content is handled correctly.
233      * @throws Exception if the test fails
234      */
235     @Test
236     public void brokenBrotliContent() throws Exception {
237         final List<NameValuePair> headers = new ArrayList<>();
238         headers.add(new NameValuePair("Content-Encoding", "br"));
239 
240         final WebResponseData data = new WebResponseData("Plain Content".getBytes(),
241                 HttpStatus.OK_200, HttpStatus.OK_200_MSG, headers);
242         try {
243             data.getBody();
244         }
245         catch (final RuntimeException e) {
246             assertTrue(e.getMessage(), StringUtils.contains(e.getMessage(), "Brotli stream decoding failed"));
247         }
248     }
249 
250     /**
251      * Verifies that a null body input stream is handled correctly. A null body may be sent, for
252      * example, when a 304 (Not Modified) response is sent to the client. See bug 1706505.
253      * @throws Exception if the test fails
254      */
255     @Test
256     public void nullBody() throws Exception {
257         final DownloadedContent downloadedContent = new DownloadedContent.InMemory(new byte[] {});
258         final List<NameValuePair> headers = new ArrayList<>();
259         final WebResponseData data = new WebResponseData(downloadedContent, 304, "NOT_MODIFIED", headers);
260         assertEquals(0, data.getBody().length);
261 
262         final WebResponse response = new WebResponse(data, new URL("http://test.com"), HttpMethod.GET, 1000);
263         assertEquals(0, response.getContentAsString(UTF_8).length());
264     }
265 
266     /**
267      * @throws Exception if the test fails
268      */
269     @Test
270     public void deflateCompression() throws Exception {
271         startWebServer("src/test/resources/pjl-comp-filter", null);
272         final WebRequest request = new WebRequest(new URL("http://localhost:"
273             + PORT + "/index.html"));
274         request.setAdditionalHeader(HttpHeader.ACCEPT_ENCODING, "deflate");
275         final WebClient webClient = getWebClient();
276         final HtmlPage page = webClient.getPage(request);
277         assertEquals("Hello Compressed World!", page.asNormalizedText());
278     }
279 
280     /**
281      * @throws Exception if the test fails
282      */
283     @Test
284     public void redirection() throws Exception {
285         final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
286         servlets.put("/folder1/page1", RedirectionServlet.class);
287         servlets.put("/folder2/page2", RedirectionServlet.class);
288         startWebServer("./", null, servlets);
289 
290         final WebClient client = getWebClient();
291 
292         final HtmlPage page = client.getPage(URL_FIRST + "folder1/page1");
293         assertEquals("Hello Redirected!", page.asNormalizedText());
294     }
295 
296     /**
297      * Servlet for {@link #redirection()}.
298      */
299     public static class RedirectionServlet extends HttpServlet {
300         /**
301          * {@inheritDoc}
302          */
303         @Override
304         protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
305             if ("/folder1/page1".equals(request.getRequestURI())) {
306                 response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
307                 final String location = request.getRequestURL().toString().replace("page1", "../folder2/page2");
308                 response.setHeader("Location", location);
309                 return;
310             }
311             response.setContentType(MimeType.TEXT_HTML);
312             final Writer writer = response.getWriter();
313             writer.write("Hello Redirected!");
314         }
315     }
316 
317 }