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