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