1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit;
16
17 import static java.nio.charset.StandardCharsets.ISO_8859_1;
18
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.io.Serializable;
22 import java.util.Collections;
23 import java.util.List;
24 import java.util.zip.GZIPInputStream;
25 import java.util.zip.Inflater;
26 import java.util.zip.InflaterInputStream;
27
28 import org.apache.commons.io.ByteOrderMark;
29 import org.apache.commons.io.IOUtils;
30 import org.apache.commons.io.input.BOMInputStream;
31 import org.apache.commons.logging.Log;
32 import org.apache.commons.logging.LogFactory;
33 import org.htmlunit.util.ArrayUtils;
34 import org.htmlunit.util.MimeType;
35 import org.htmlunit.util.NameValuePair;
36 import org.htmlunit.util.StringUtils;
37 import org.htmlunit.util.brotli.BrotliInputStream;
38
39
40
41
42
43
44
45
46
47
48 public class WebResponseData implements Serializable {
49 private static final Log LOG = LogFactory.getLog(WebResponseData.class);
50
51 private static final String CONTENT_ENCODING_ERROR_HTML = """
52 <!DOCTYPE html><html>
53 <head><title>Problem loading page</title></head>
54 <body>
55 <h1>Content Encoding Error</h1>
56 <p>The page you are trying to view cannot be shown because\
57 it uses an invalid or unsupported form of compression.</p>
58 </body>
59 </html>""";
60
61 private final int statusCode_;
62 private final String statusMessage_;
63 private final List<NameValuePair> responseHeaders_;
64 private final DownloadedContent downloadedContent_;
65
66
67
68
69
70
71
72
73
74 public WebResponseData(final byte[] body, final int statusCode, final String statusMessage,
75 final List<NameValuePair> responseHeaders) {
76 this(new DownloadedContent.InMemory(body), statusCode, statusMessage, responseHeaders);
77 }
78
79
80
81
82
83
84
85
86 protected WebResponseData(final int statusCode,
87 final String statusMessage, final List<NameValuePair> responseHeaders) {
88 this(ArrayUtils.EMPTY_BYTE_ARRAY, statusCode, statusMessage, responseHeaders);
89 }
90
91
92
93
94
95
96
97
98 public WebResponseData(final DownloadedContent downloadedContent, final int statusCode, final String statusMessage,
99 final List<NameValuePair> responseHeaders) {
100 statusCode_ = statusCode;
101 statusMessage_ = statusMessage;
102 responseHeaders_ = Collections.unmodifiableList(responseHeaders);
103 downloadedContent_ = downloadedContent;
104 }
105
106 private InputStream getStream(final ByteOrderMark... bomHeaders) throws IOException {
107 InputStream stream = downloadedContent_.getInputStream();
108 if (downloadedContent_.isEmpty()) {
109 return stream;
110 }
111
112 final List<NameValuePair> headers = getResponseHeaders();
113 final String encoding = getHeader(headers, "content-encoding");
114 if (encoding != null) {
115 boolean isGzip = StringUtils.containsIgnoreCase(encoding, "gzip") && !"no-gzip".equals(encoding);
116 if ("gzip-only-text/html".equals(encoding)) {
117 isGzip = MimeType.TEXT_HTML.equals(getHeader(headers, "content-type"));
118 }
119
120 if (isGzip) {
121 try {
122 stream = new GZIPInputStream(stream);
123 }
124 catch (final IOException e) {
125 LOG.error("Reading gzip encoded content failed.", e);
126 stream.close();
127 stream = IOUtils.toInputStream(CONTENT_ENCODING_ERROR_HTML, ISO_8859_1);
128 }
129 }
130 else if ("br".equals(encoding)) {
131 try {
132 stream = new BrotliInputStream(stream);
133 }
134 catch (final IOException e) {
135 LOG.error("Reading Brotli encoded content failed.", e);
136 stream.close();
137 stream = IOUtils.toInputStream(CONTENT_ENCODING_ERROR_HTML, ISO_8859_1);
138 }
139 }
140 else if (StringUtils.containsIgnoreCase(encoding, "deflate")) {
141 boolean zlibHeader = false;
142 if (stream.markSupported()) {
143 stream.mark(2);
144 final byte[] buffer = new byte[2];
145 final int byteCount = IOUtils.read(stream, buffer, 0, 2);
146 if (byteCount == 2) {
147 final int header = ((buffer[0] & 0xff) << 8) | (buffer[1] & 0xff);
148 zlibHeader = (header & 0x7800) == 0x7800 && (header % 31 == 0);
149 }
150 stream.reset();
151 }
152 stream = zlibHeader ? new InflaterInputStream(stream)
153 : new InflaterInputStream(stream, new Inflater(true));
154 }
155 }
156
157 if (stream != null && bomHeaders != null && bomHeaders.length > 0) {
158 stream = BOMInputStream.builder().setInputStream(stream).setByteOrderMarks(bomHeaders).get();
159 }
160 return stream;
161 }
162
163 private static String getHeader(final List<NameValuePair> headers, final String name) {
164 for (final NameValuePair header : headers) {
165 final String headerName = header.getName().trim();
166 if (name.equalsIgnoreCase(headerName)) {
167 return header.getValue();
168 }
169 }
170
171 return null;
172 }
173
174
175
176
177
178
179 public byte[] getBody() {
180 try (InputStream is = getInputStream()) {
181 return IOUtils.toByteArray(is);
182 }
183 catch (final IOException e) {
184 throw new RuntimeException(e);
185 }
186 }
187
188
189
190
191
192
193 public InputStream getInputStream() throws IOException {
194 return getStream((ByteOrderMark[]) null);
195 }
196
197
198
199
200
201
202
203
204 public InputStream getInputStreamWithBomIfApplicable(final ByteOrderMark... bomHeaders) throws IOException {
205 return getStream(bomHeaders);
206 }
207
208
209
210
211
212
213 public List<NameValuePair> getResponseHeaders() {
214 return responseHeaders_;
215 }
216
217
218
219
220
221
222 public int getStatusCode() {
223 return statusCode_;
224 }
225
226
227
228
229
230
231 public String getStatusMessage() {
232 return statusMessage_;
233 }
234
235
236
237
238
239 public long getContentLength() {
240 return downloadedContent_.length();
241 }
242
243
244
245
246 public void cleanUp() {
247 downloadedContent_.cleanUp();
248 }
249 }