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