1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit.util;
16
17 import static java.nio.charset.StandardCharsets.ISO_8859_1;
18
19 import java.io.BufferedWriter;
20 import java.io.EOFException;
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.OutputStream;
25 import java.net.URL;
26 import java.nio.charset.StandardCharsets;
27 import java.nio.file.Files;
28 import java.nio.file.StandardOpenOption;
29 import java.util.ArrayList;
30 import java.util.List;
31 import java.util.regex.Pattern;
32
33 import org.apache.commons.io.FileUtils;
34 import org.apache.commons.io.IOUtils;
35 import org.apache.commons.lang3.StringUtils;
36 import org.apache.commons.logging.Log;
37 import org.apache.commons.logging.LogFactory;
38 import org.htmlunit.FormEncodingType;
39 import org.htmlunit.HttpMethod;
40 import org.htmlunit.WebConnection;
41 import org.htmlunit.WebRequest;
42 import org.htmlunit.WebResponse;
43 import org.htmlunit.WebResponseData;
44 import org.htmlunit.javascript.JavaScriptEngine;
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68 public class DebuggingWebConnection extends WebConnectionWrapper {
69 private static final Log LOG = LogFactory.getLog(DebuggingWebConnection.class);
70
71 private static final Pattern ESCAPE_QUOTE_PATTERN = Pattern.compile("'");
72
73 private int counter_;
74 private final WebConnection wrappedWebConnection_;
75 private final File javaScriptFile_;
76 private final File reportFolder_;
77 private boolean uncompressJavaScript_ = true;
78
79
80
81
82
83
84
85
86
87 public DebuggingWebConnection(final WebConnection webConnection,
88 final String dirName) throws IOException {
89
90 super(webConnection);
91
92 wrappedWebConnection_ = webConnection;
93 final File tmpDir = new File(System.getProperty("java.io.tmpdir"));
94 reportFolder_ = new File(tmpDir, dirName);
95 if (reportFolder_.exists()) {
96 FileUtils.forceDelete(reportFolder_);
97 }
98 FileUtils.forceMkdir(reportFolder_);
99 javaScriptFile_ = new File(reportFolder_, "hu.js");
100 createOverview();
101 }
102
103
104
105
106
107 @Override
108 public WebResponse getResponse(final WebRequest request) throws IOException {
109 WebResponse response = wrappedWebConnection_.getResponse(request);
110 if (isUncompressJavaScript() && isJavaScript(response.getContentType())) {
111 response = uncompressJavaScript(response);
112 }
113 saveResponse(response, request);
114 return response;
115 }
116
117
118
119
120
121
122
123 protected WebResponse uncompressJavaScript(final WebResponse response) {
124 final WebRequest request = response.getWebRequest();
125 final String scriptName = request.getUrl().toString();
126 final String scriptSource = response.getContentAsString();
127
128
129
130 try {
131 final String decompileScript = JavaScriptEngine.uncompressJavaScript(scriptSource, scriptName);
132
133 final List<NameValuePair> responseHeaders = new ArrayList<>(response.getResponseHeaders());
134 for (int i = responseHeaders.size() - 1; i >= 0; i--) {
135 if ("content-encoding".equalsIgnoreCase(responseHeaders.get(i).getName())) {
136 responseHeaders.remove(i);
137 }
138 }
139 final WebResponseData wrd = new WebResponseData(decompileScript.getBytes(), response.getStatusCode(),
140 response.getStatusMessage(), responseHeaders);
141 return new WebResponse(wrd, response.getWebRequest().getUrl(),
142 response.getWebRequest().getHttpMethod(), response.getLoadTime());
143 }
144 catch (final Exception e) {
145 LOG.warn("Failed to decompress JavaScript response. Delivering as it.", e);
146 }
147
148 return response;
149 }
150
151
152
153
154
155
156
157 public void addMark(String mark) throws IOException {
158 if (mark != null) {
159 mark = mark.replace("\"", "\\\"");
160 }
161 appendToJSFile("tab[tab.length] = \"" + mark + "\";\n");
162 if (LOG.isInfoEnabled()) {
163 LOG.info("--- " + mark + " ---");
164 }
165 }
166
167
168
169
170
171
172
173
174 protected void saveResponse(final WebResponse response, final WebRequest request)
175 throws IOException {
176 counter_++;
177 final String extension = chooseExtension(response.getContentType());
178 final File file = createFile(request.getUrl(), extension);
179 int length = 0;
180 try (InputStream input = response.getContentAsStream()) {
181 try (OutputStream fos = Files.newOutputStream(file.toPath())) {
182 length = IOUtils.copy(input, fos);
183 }
184 catch (final EOFException ignored) {
185
186 }
187 }
188
189 final URL url = response.getWebRequest().getUrl();
190 if (LOG.isInfoEnabled()) {
191 LOG.info("Created file " + file.getAbsolutePath() + " for response " + counter_ + ": " + url);
192 }
193
194 final StringBuilder bduiler = new StringBuilder();
195 bduiler.append("tab[tab.length] = {code: ").append(response.getStatusCode())
196 .append(", fileName: '").append(file.getName()).append("', ")
197 .append("contentType: '").append(response.getContentType())
198 .append("', method: '").append(request.getHttpMethod().name()).append("', ");
199 if (request.getHttpMethod() == HttpMethod.POST && request.getEncodingType() == FormEncodingType.URL_ENCODED) {
200 bduiler.append("postParameters: ").append(nameValueListToJsMap(request.getRequestParameters()))
201 .append(", ");
202 }
203 bduiler.append("url: '").append(escapeJSString(url.toString()))
204 .append("', loadTime: ").append(response.getLoadTime())
205 .append(", responseSize: ").append(length)
206 .append(", responseHeaders: ").append(nameValueListToJsMap(response.getResponseHeaders()))
207 .append("};\n");
208 appendToJSFile(bduiler.toString());
209 }
210
211 static String escapeJSString(final String string) {
212 return ESCAPE_QUOTE_PATTERN.matcher(string).replaceAll("\\\\'");
213 }
214
215 static String chooseExtension(final String contentType) {
216 if (isJavaScript(contentType)) {
217 return ".js";
218 }
219 else if (MimeType.TEXT_HTML.equals(contentType)) {
220 return ".html";
221 }
222 else if (MimeType.TEXT_CSS.equals(contentType)) {
223 return ".css";
224 }
225 else if (MimeType.TEXT_XML.equals(contentType)) {
226 return ".xml";
227 }
228 else if (MimeType.IMAGE_GIF.equals(contentType)) {
229 return ".gif";
230 }
231 return ".txt";
232 }
233
234
235
236
237
238
239
240 static boolean isJavaScript(final String contentType) {
241 return contentType.contains("javascript") || contentType.contains("ecmascript")
242 || (contentType.startsWith("text/") && contentType.endsWith("js"));
243 }
244
245
246
247
248
249
250 public boolean isUncompressJavaScript() {
251 return uncompressJavaScript_;
252 }
253
254
255
256
257
258
259
260
261 public void setUncompressJavaScript(final boolean decompress) {
262 uncompressJavaScript_ = decompress;
263 }
264
265 private void appendToJSFile(final String str) throws IOException {
266 try (BufferedWriter jsFileWriter = Files.newBufferedWriter(javaScriptFile_.toPath(),
267 StandardCharsets.UTF_8, StandardOpenOption.APPEND)) {
268 jsFileWriter.write(str);
269 }
270 }
271
272
273
274
275
276
277
278
279
280 private File createFile(final URL url, final String extension) throws IOException {
281 String name = url.getPath().replaceFirst("/$", "").replaceAll(".*/", "");
282 name = org.htmlunit.util.StringUtils.substringBefore(name, "?");
283 name = org.htmlunit.util.StringUtils.substringBefore(name, ";");
284 name = StringUtils.substring(name, 0, 30);
285 name = org.htmlunit.util.StringUtils.sanitizeForFileName(name);
286 if (!name.endsWith(extension)) {
287 name += extension;
288 }
289 int counter = 0;
290 while (true) {
291 final String fileName;
292 if (counter != 0) {
293 fileName = StringUtils.substringBeforeLast(name, ".")
294 + "_" + counter + "." + StringUtils.substringAfterLast(name, ".");
295 }
296 else {
297 fileName = name;
298 }
299 final File f = new File(reportFolder_, fileName);
300 if (f.createNewFile()) {
301 return f;
302 }
303 counter++;
304 }
305 }
306
307
308
309
310
311
312
313
314 static String nameValueListToJsMap(final List<NameValuePair> headers) {
315 if (headers == null || headers.isEmpty()) {
316 return "{}";
317 }
318 final StringBuilder bduiler = new StringBuilder("{");
319 for (final NameValuePair header : headers) {
320 bduiler.append('\'').append(header.getName()).append("': '")
321 .append(escapeJSString(header.getValue())).append("', ");
322 }
323 bduiler.delete(bduiler.length() - 2, bduiler.length());
324 bduiler.append('}');
325 return bduiler.toString();
326 }
327
328
329
330
331
332
333 private void createOverview() throws IOException {
334 FileUtils.writeStringToFile(javaScriptFile_, "var tab = [];\n", ISO_8859_1);
335
336 final URL indexResource = DebuggingWebConnection.class.getResource("DebuggingWebConnection.index.html");
337 if (indexResource == null) {
338 throw new RuntimeException("Missing dependency DebuggingWebConnection.index.html");
339 }
340 final File summary = new File(reportFolder_, "index.html");
341 FileUtils.copyURLToFile(indexResource, summary);
342
343 if (LOG.isInfoEnabled()) {
344 LOG.info("Summary will be in " + summary.getAbsolutePath());
345 }
346 }
347
348 File getReportFolder() {
349 return reportFolder_;
350 }
351 }