Title: [203132] trunk
Revision
203132
Author
[email protected]
Date
2016-07-12 15:21:33 -0700 (Tue, 12 Jul 2016)

Log Message

Web Inspector: ER: Copy as cURL
https://bugs.webkit.org/show_bug.cgi?id=159380

Patch by Johan K. Jensen <[email protected]> on 2016-07-12
Reviewed by Joseph Pecoraro.

Source/WebInspectorUI:

Inspired by https://chromium.googlesource.com/chromium/src/+/b7c1115dbae65030ad96e773d9a270465a05f5c4/third_party/WebKit/Source/devtools/front_end/network/NetworkLogView.js

This "Copy as cURL" feature only approximates the original request as only some parts of
that request is known to the frontend.

* UserInterface/Models/Resource.js:
(WebInspector.Resource.prototype.generateCURLCommand.escapeStringPosix.escapeCharacter):
(WebInspector.Resource.prototype.generateCURLCommand.escapeStringPosix):
(WebInspector.Resource.prototype.generateCURLCommand):
(WebInspector.Resource):
* UserInterface/Views/ResourceTimelineDataGridNode.js:
(WebInspector.ResourceTimelineDataGridNode.prototype.appendContextMenuItems):

LayoutTests:

* http/tests/inspector/network/copy-as-curl-expected.txt: Added.
* http/tests/inspector/network/copy-as-curl.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (203131 => 203132)


--- trunk/LayoutTests/ChangeLog	2016-07-12 22:08:31 UTC (rev 203131)
+++ trunk/LayoutTests/ChangeLog	2016-07-12 22:21:33 UTC (rev 203132)
@@ -1,3 +1,13 @@
+2016-07-12  Johan K. Jensen  <[email protected]>
+
+        Web Inspector: ER: Copy as cURL
+        https://bugs.webkit.org/show_bug.cgi?id=159380
+
+        Reviewed by Joseph Pecoraro.
+
+        * http/tests/inspector/network/copy-as-curl-expected.txt: Added.
+        * http/tests/inspector/network/copy-as-curl.html: Added.
+
 2016-07-12  Benjamin Poulain  <[email protected]>
 
         [JSC] Array.prototype.join() fails some conformance tests

Added: trunk/LayoutTests/http/tests/inspector/network/copy-as-curl-expected.txt (0 => 203132)


--- trunk/LayoutTests/http/tests/inspector/network/copy-as-curl-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/http/tests/inspector/network/copy-as-curl-expected.txt	2016-07-12 22:21:33 UTC (rev 203132)
@@ -0,0 +1,35 @@
+Tests that "Copy as cURL" returns valid POSIX output.
+
+
+== Running test suite: GenerateCURLValidPOSIXOutput
+-- Running test case: SimpleURLGenerateCURL
+PASS: Command should contain URL.
+PASS: Command should be a GET request.
+PASS: Command should contain User-Agent header.
+PASS: Command should not contain a custom header.
+
+-- Running test case: SpecialURLGenerateCURL
+PASS: Command should contain valid POSIX escaped URL.
+
+-- Running test case: SpecialHeadersGenerateCURLValidPOSIXOutput
+PASS: Command should have correct custom header 1.
+PASS: Command should have correct custom header 2.
+PASS: Command should have correct custom header 3.
+
+-- Running test case: URLWithUTF8GenerateCURL
+PASS: Command should contain URL with UTF8 characters.
+
+-- Running test case: POSTRequestURLEncodedDataGenerateCURL
+PASS: Command should be a POST request.
+PASS: Command should have correct Content-Type.
+PASS: Command should contain correct data.
+
+-- Running test case: POSTRequestURLEncodedDataUTF8GenerateCURL
+PASS: Command should contain URL with UTF8 characters.
+PASS: Command should contain correct UTF8 data.
+
+-- Running test case: PUTRequestWithJSONGenerateCURL
+PASS: Command should be a PUT request.
+PASS: Command should have JSON Content-Type.
+PASS: Command should contain correct JSON data.
+

Added: trunk/LayoutTests/http/tests/inspector/network/copy-as-curl.html (0 => 203132)


--- trunk/LayoutTests/http/tests/inspector/network/copy-as-curl.html	                        (rev 0)
+++ trunk/LayoutTests/http/tests/inspector/network/copy-as-curl.html	2016-07-12 22:21:33 UTC (rev 203132)
@@ -0,0 +1,188 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<script src=""
+<script>
+function createSimpleGETRequest()
+{
+    var request = new XMLHttpRequest();
+    request.open("GET", "resources/url?query=true", true);
+    request.send();
+}
+
+function createGETRequestWithSpecialURL()
+{
+    var request = new XMLHttpRequest();
+    request.open("GET", "resources/url'with$special{1..20}chars[] .html", true);
+    request.send();
+}
+
+function createGETRequestWithSpecialCharsInHeaders()
+{
+    var request = new XMLHttpRequest();
+    request.open("GET", "resources/url", true);
+    request.setRequestHeader("X-Custom1", "test1");
+    request.setRequestHeader("X-Custom2'%", "\'Test'\'1\\'2");
+    request.setRequestHeader("X-Custom3", "\'${PWD}");
+    request.send();
+}
+
+function createGETRequestWithUTF8()
+{
+    var request = new XMLHttpRequest();
+    request.open("GET", "resources/url?utf8=👍", true);
+    request.send();
+}
+
+function createPOSTRequestWithURLEncodedData()
+{
+    var request = new XMLHttpRequest();
+    request.open("POST", "resources/url", true);
+    request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
+    request.send("lorem=ipsum&$dolor='sit'&amet={1..20}");
+}
+
+function createPOSTRequestWithUTF8()
+{
+    var request = new XMLHttpRequest();
+    request.open("POST", "resources/url?utf8=👍", true);
+    request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
+    request.send("🌨=⛄️");
+}
+
+function createPUTRequestWithJSON()
+{
+    var request = new XMLHttpRequest();
+    request.open("PUT", "resources/url", true);
+    request.setRequestHeader("Content-Type", "application/json");
+    request.send("{\"update\":\"now\"}");
+}
+
+function test()
+{
+    let suite = InspectorTest.createAsyncSuite("GenerateCURLValidPOSIXOutput");
+
+    suite.addTestCase({
+        name: "SimpleURLGenerateCURL",
+        description: "Generate cURL command from a simple URL.",
+        test: (resolve, reject) => {
+            InspectorTest.evaluateInPage("createSimpleGETRequest()");
+            WebInspector.Frame.singleFireEventListener(WebInspector.Frame.Event.ResourceWasAdded, (event) => {
+                let resource = event.data.resource;
+                let curl = resource.generateCURLCommand().split(" \\\n");
+
+                InspectorTest.expectThat(curl[0].match("https?://.*?/resources/url\\?query=true") !== null, "Command should contain URL.");
+                InspectorTest.expectThat(curl[1] === "-XGET", "Command should be a GET request.");
+                InspectorTest.expectThat(curl.find((cmd) => cmd.includes('User-Agent')) !== undefined, "Command should contain User-Agent header.");
+                InspectorTest.expectThat(curl.find((cmd) => cmd.includes('X-Custom')) === undefined, "Command should not contain a custom header.");
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "SpecialURLGenerateCURL",
+        description: "Generate cURL command from a URL containing special characters.",
+        test: (resolve, reject) => {
+            InspectorTest.evaluateInPage("createGETRequestWithSpecialURL()");
+            WebInspector.Frame.singleFireEventListener(WebInspector.Frame.Event.ResourceWasAdded, (event) => {
+                let resource = event.data.resource;
+                let curl = resource.generateCURLCommand().split(" \\\n");
+
+                InspectorTest.expectThat(curl[0].match("https?://.*?/resources/url\\\\'with\\$special\\\\{1\.\.20\\\\}chars\\\\\\[\\\\\\]%20.html") !== null, "Command should contain valid POSIX escaped URL.");
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "SpecialHeadersGenerateCURLValidPOSIXOutput",
+        description: "Generate cURL command from a request containing special characters in the headers and verify valid POSIX output.",
+        test: (resolve, reject) => {
+            InspectorTest.evaluateInPage("createGETRequestWithSpecialCharsInHeaders()");
+            WebInspector.Frame.singleFireEventListener(WebInspector.Frame.Event.ResourceWasAdded, (event) => {
+                let resource = event.data.resource;
+                let curl = resource.generateCURLCommand().split(" \\\n");
+
+                InspectorTest.expectThat(curl.find((cmd) => cmd.includes('X-Custom1')) === "-H 'X-Custom1: test1'", "Command should have correct custom header 1.");
+                InspectorTest.expectThat(curl.find((cmd) => cmd.includes('X-Custom2')) === "-H $'X-Custom2\\'%: \\'Test\\'\\'1\\\\\\'2'", "Command should have correct custom header 2.");
+                InspectorTest.expectThat(curl.find((cmd) => cmd.includes('X-Custom3')) === "-H $'X-Custom3: \\'${PWD}'", "Command should have correct custom header 3.");
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "URLWithUTF8GenerateCURL",
+        description: "Generate cURL command from a URL containing UTF8 characters.",
+        test: (resolve, reject) => {
+            InspectorTest.evaluateInPage("createGETRequestWithUTF8()");
+            WebInspector.Frame.singleFireEventListener(WebInspector.Frame.Event.ResourceWasAdded, (event) => {
+                let resource = event.data.resource;
+                let curl = resource.generateCURLCommand().split(" \\\n");
+
+                InspectorTest.expectThat(curl[0].match("https?://.*?/resources/url\\?utf8=%F0%9F%91%8D") !== null, "Command should contain URL with UTF8 characters.");
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "POSTRequestURLEncodedDataGenerateCURL",
+        description: "Generate cURL command from a POST request with URL encoded data.",
+        test: (resolve, reject) => {
+            InspectorTest.evaluateInPage("createPOSTRequestWithURLEncodedData()");
+            WebInspector.Frame.singleFireEventListener(WebInspector.Frame.Event.ResourceWasAdded, (event) => {
+                let resource = event.data.resource;
+                let curl = resource.generateCURLCommand().split(" \\\n");
+
+                InspectorTest.expectThat(curl[1] === "-XPOST", "Command should be a POST request.");
+                InspectorTest.expectThat(curl.find((cmd) => cmd.includes('Content-Type')) === "-H 'Content-Type: application/x-www-form-urlencoded'", "Command should have correct Content-Type.");
+                InspectorTest.expectThat(curl.find((cmd) => cmd === "--data $'lorem=ipsum&$dolor=\\'sit\\'&amet={1..20}'") !== undefined, "Command should contain correct data.");
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "POSTRequestURLEncodedDataUTF8GenerateCURL",
+        description: "Generate cURL command from a POST request with URL encoded UTF8 data.",
+        test: (resolve, reject) => {
+            InspectorTest.evaluateInPage("createPOSTRequestWithUTF8()");
+            WebInspector.Frame.singleFireEventListener(WebInspector.Frame.Event.ResourceWasAdded, (event) => {
+                let resource = event.data.resource;
+                let curl = resource.generateCURLCommand().split(" \\\n");
+
+                InspectorTest.expectThat(curl[0].match("https?://.*?/resources/url\\?utf8=%F0%9F%91%8D") !== null, "Command should contain URL with UTF8 characters.");
+                InspectorTest.expectThat(curl.find((cmd) => cmd === "--data $'\\ud83c\\udf28=\\u26c4\\ufe0f'") !== undefined, "Command should contain correct UTF8 data.");
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "PUTRequestWithJSONGenerateCURL",
+        description: "Generate cURL command from a PUT request with JSON data.",
+        test: (resolve, reject) => {
+            InspectorTest.evaluateInPage("createPUTRequestWithJSON()");
+            WebInspector.Frame.singleFireEventListener(WebInspector.Frame.Event.ResourceWasAdded, (event) => {
+                let resource = event.data.resource;
+                let curl = resource.generateCURLCommand().split(" \\\n");
+
+                InspectorTest.expectThat(curl[1] === "-XPUT", "Command should be a PUT request.");
+                InspectorTest.expectThat(curl.find((cmd) => cmd.includes('Content-Type')) === "-H 'Content-Type: application/json'", "Command should have JSON Content-Type.");
+                InspectorTest.expectThat(curl.find((cmd) => cmd === "--data-binary '{\"update\":\"now\"}'") !== undefined, "Command should contain correct JSON data.");
+                resolve();
+            });
+        }
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body _onload_="runTest()">
+    <p>Tests that "Copy as cURL" returns valid POSIX output.</p>
+</body>
+</html>
\ No newline at end of file

Modified: trunk/Source/WebInspectorUI/ChangeLog (203131 => 203132)


--- trunk/Source/WebInspectorUI/ChangeLog	2016-07-12 22:08:31 UTC (rev 203131)
+++ trunk/Source/WebInspectorUI/ChangeLog	2016-07-12 22:21:33 UTC (rev 203132)
@@ -1,3 +1,23 @@
+2016-07-12  Johan K. Jensen  <[email protected]>
+
+        Web Inspector: ER: Copy as cURL
+        https://bugs.webkit.org/show_bug.cgi?id=159380
+
+        Reviewed by Joseph Pecoraro.
+
+        Inspired by https://chromium.googlesource.com/chromium/src/+/b7c1115dbae65030ad96e773d9a270465a05f5c4/third_party/WebKit/Source/devtools/front_end/network/NetworkLogView.js
+
+        This "Copy as cURL" feature only approximates the original request as only some parts of
+        that request is known to the frontend.
+
+        * UserInterface/Models/Resource.js:
+        (WebInspector.Resource.prototype.generateCURLCommand.escapeStringPosix.escapeCharacter):
+        (WebInspector.Resource.prototype.generateCURLCommand.escapeStringPosix):
+        (WebInspector.Resource.prototype.generateCURLCommand):
+        (WebInspector.Resource):
+        * UserInterface/Views/ResourceTimelineDataGridNode.js:
+        (WebInspector.ResourceTimelineDataGridNode.prototype.appendContextMenuItems):
+
 2016-07-12  Joseph Pecoraro  <[email protected]>
 
         Web Inspector: Use separate files for TreeOutline/TreeElement and DataGrid/DataGridNode

Modified: trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js (203131 => 203132)


--- trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js	2016-07-12 22:08:31 UTC (rev 203131)
+++ trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js	2016-07-12 22:21:33 UTC (rev 203132)
@@ -195,6 +195,7 @@
 localizedStrings["Copy Rule"] = "Copy Rule";
 localizedStrings["Copy Table"] = "Copy Table";
 localizedStrings["Copy as HTML"] = "Copy as HTML";
+localizedStrings["Copy as cURL"] = "Copy as cURL";
 localizedStrings["Could not fetch properties. Object may no longer exist."] = "Could not fetch properties. Object may no longer exist.";
 localizedStrings["Count"] = "Count";
 localizedStrings["Create %s Rule"] = "Create %s Rule";

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/Resource.js (203131 => 203132)


--- trunk/Source/WebInspectorUI/UserInterface/Models/Resource.js	2016-07-12 22:08:31 UTC (rev 203131)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/Resource.js	2016-07-12 22:21:33 UTC (rev 203132)
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2013 Apple Inc. All rights reserved.
+ * Copyright (C) 2011 Google Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -678,6 +679,48 @@
         cookie[WebInspector.Resource.URLCookieKey] = this.url.hash;
         cookie[WebInspector.Resource.MainResourceCookieKey] = this.isMainResource();
     }
+
+    generateCURLCommand()
+    {
+        function escapeStringPosix(str) {
+            function escapeCharacter(x) {
+                let code = x.charCodeAt(0);
+                let hex = code.toString(16);
+                if (code < 256)
+                    return "\\x" + hex.padStart(2, "0");
+                return "\\u" + hex.padStart(4, "0");
+            }
+
+            if (/[^\x20-\x7E]|'/.test(str)) {
+                // Use ANSI-C quoting syntax.
+                return "$'" + str.replace(/\\/g, "\\\\")
+                                 .replace(/'/g, "\\'")
+                                 .replace(/\n/g, "\\n")
+                                 .replace(/\r/g, "\\r")
+                                 .replace(/[^\x20-\x7E]/g, escapeCharacter) + "'";
+            } else {
+                // Use single quote syntax.
+                return `'${str}'`;
+            }
+        }
+
+        let command = ["curl " + escapeStringPosix(this.url).replace(/[[{}\]]/g, "\\$&")];
+        command.push(`-X${this.requestMethod}`);
+
+        for (let key in this.requestHeaders)
+            command.push("-H " + escapeStringPosix(`${key}: ${this.requestHeaders[key]}`));
+
+        if (this.requestDataContentType && this.requestMethod !== "GET" && this.requestData) {
+            if (this.requestDataContentType.match(/^application\/x-www-form-urlencoded\s*(;.*)?$/i))
+                command.push("--data " + escapeStringPosix(this.requestData))
+            else
+                command.push("--data-binary " + escapeStringPosix(this.requestData))
+        }
+
+        let curlCommand = command.join(" \\\n");
+        InspectorFrontendHost.copyText(curlCommand);
+        return curlCommand;
+    }
 };
 
 WebInspector.Resource.TypeIdentifier = "resource";

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/ResourceTimelineDataGridNode.js (203131 => 203132)


--- trunk/Source/WebInspectorUI/UserInterface/Views/ResourceTimelineDataGridNode.js	2016-07-12 22:08:31 UTC (rev 203131)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/ResourceTimelineDataGridNode.js	2016-07-12 22:21:33 UTC (rev 203132)
@@ -146,6 +146,12 @@
         return [WebInspector.ResourceTreeElement.ResourceIconStyleClassName, this.resource.type];
     }
 
+    appendContextMenuItems(contextMenu)
+    {
+        if (this._resource.urlComponents.scheme !== "data")
+            contextMenu.appendItem(WebInspector.UIString("Copy as cURL"), () => { this._resource.generateCURLCommand(); });
+    }
+
     // Protected
 
     filterableDataForColumn(columnIdentifier)
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to