Bug 1297082 - Part 1: Convert Curl.jsm to a CommonJS module r?Honza draft
authorJarda Snajdr <jsnajdr@gmail.com>
Thu, 18 Aug 2016 17:05:15 +0200
changeset 404349 f88fedc9edd56e6172fb0731b2eac3c7d322782d
parent 404348 c5d8368a48c634faa4f8c0d5699b9138da76b7b1
child 404350 360989a998568152e9871e4f71f2cdd0ef39f5be
push id27190
push userbmo:jsnajdr@gmail.com
push dateTue, 23 Aug 2016 09:42:05 +0000
reviewersHonza
bugs1297082
milestone51.0a1
Bug 1297082 - Part 1: Convert Curl.jsm to a CommonJS module r?Honza MozReview-Commit-ID: LI7Ra1w3RBj
devtools/client/netmonitor/netmonitor-controller.js
devtools/client/netmonitor/netmonitor-view.js
devtools/client/netmonitor/test/browser_net_curl-utils.js
devtools/client/netmonitor/test/head.js
devtools/client/shared/Curl.jsm
devtools/client/shared/curl.js
devtools/client/shared/moz.build
--- a/devtools/client/netmonitor/netmonitor-controller.js
+++ b/devtools/client/netmonitor/netmonitor-controller.js
@@ -119,22 +119,16 @@ const {Task} = require("devtools/shared/
 
 XPCOMUtils.defineConstant(this, "EVENTS", EVENTS);
 XPCOMUtils.defineConstant(this, "ACTIVITY_TYPE", ACTIVITY_TYPE);
 XPCOMUtils.defineConstant(this, "Editor", Editor);
 
 XPCOMUtils.defineLazyModuleGetter(this, "Chart",
   "resource://devtools/client/shared/widgets/Chart.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "Curl",
-  "resource://devtools/client/shared/Curl.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "CurlUtils",
-  "resource://devtools/client/shared/Curl.jsm");
-
 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
   "resource://gre/modules/PluralForm.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper",
   "@mozilla.org/widget/clipboardhelper;1", "nsIClipboardHelper");
 
 Object.defineProperty(this, "NetworkHelper", {
   get: function () {
--- a/devtools/client/netmonitor/netmonitor-view.js
+++ b/devtools/client/netmonitor/netmonitor-view.js
@@ -26,16 +26,17 @@ const {HTMLTooltip} = require("devtools/
 const {setImageTooltip, getImageDimensions} =
   require("devtools/client/shared/widgets/tooltip/ImageTooltipHelper");
 const { testing: isTesting } = require("devtools/shared/flags");
 const {LocalizationHelper} = require("devtools/client/shared/l10n");
 const {PrefsHelper} = require("devtools/client/shared/prefs");
 const {ViewHelpers, Heritage, WidgetMethods, setNamedTimeout} =
   require("devtools/client/shared/widgets/view-helpers");
 const {gDevTools} = require("devtools/client/framework/devtools");
+const {Curl, CurlUtils} = require("devtools/client/shared/curl");
 
 /**
  * Localization convenience methods.
  */
 const NET_STRINGS_URI = "chrome://devtools/locale/netmonitor.properties";
 const WEBCONSOLE_STRINGS_URI = "chrome://devtools/locale/webconsole.properties";
 var L10N = new LocalizationHelper(NET_STRINGS_URI);
 const WEBCONSOLE_L10N = new LocalizationHelper(WEBCONSOLE_STRINGS_URI);
--- a/devtools/client/netmonitor/test/browser_net_curl-utils.js
+++ b/devtools/client/netmonitor/test/browser_net_curl-utils.js
@@ -1,15 +1,19 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+"use strict";
+
 /**
  * Tests Curl Utils functionality.
  */
 
+const { CurlUtils } = require("devtools/client/shared/curl");
+
 function test() {
   initNetMonitor(CURL_UTILS_URL).then(([aTab, aDebuggee, aMonitor]) => {
     info("Starting test... ");
 
     let { NetMonitorView, gNetwork } = aMonitor.panelWin;
     let { RequestsMenu } = NetMonitorView;
 
     RequestsMenu.lazyUpdate = false;
--- a/devtools/client/netmonitor/test/head.js
+++ b/devtools/client/netmonitor/test/head.js
@@ -4,17 +4,16 @@
 
 /* import-globals-from ../../framework/test/shared-head.js */
 
 // shared-head.js handles imports, constants, and utility functions
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js",
   this);
 
-var { CurlUtils } = Cu.import("resource://devtools/client/shared/Curl.jsm", {});
 var NetworkHelper = require("devtools/shared/webconsole/network-helper");
 var { Toolbox } = require("devtools/client/framework/toolbox");
 
 const EXAMPLE_URL = "http://example.com/browser/devtools/client/netmonitor/test/";
 
 const API_CALLS_URL = EXAMPLE_URL + "html_api-calls-test-page.html";
 const SIMPLE_URL = EXAMPLE_URL + "html_simple-test-page.html";
 const NAVIGATE_URL = EXAMPLE_URL + "html_navigate-test-page.html";
deleted file mode 100644
--- a/devtools/client/shared/Curl.jsm
+++ /dev/null
@@ -1,400 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-/*
- * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
- * Copyright (C) 2008, 2009 Anthony Ricaud <rik@webkit.org>
- * Copyright (C) 2011 Google Inc. All rights reserved.
- * Copyright (C) 2009 Mozilla Foundation. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * 1.  Redistributions of source code must retain the above copyright
- *     notice, this list of conditions and the following disclaimer.
- * 2.  Redistributions in binary form must reproduce the above copyright
- *     notice, this list of conditions and the following disclaimer in the
- *     documentation and/or other materials provided with the distribution.
- * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
- *     its contributors may be used to endorse or promote products derived
- *     from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = ["Curl", "CurlUtils"];
-
-Components.utils.import("resource://gre/modules/Services.jsm");
-
-const DEFAULT_HTTP_VERSION = "HTTP/1.1";
-
-this.Curl = {
-  /**
-   * Generates a cURL command string which can be used from the command line etc.
-   *
-   * @param object aData
-   *        Datasource to create the command from.
-   *        The object must contain the following properties:
-   *          - url:string, the URL of the request.
-   *          - method:string, the request method upper cased. HEAD / GET / POST etc.
-   *          - headers:array, an array of request headers {name:x, value:x} tuples.
-   *          - httpVersion:string, http protocol version rfc2616 formatted. Eg. "HTTP/1.1"
-   *          - postDataText:string, optional - the request payload.
-   *
-   * @return string
-   *         A cURL command.
-   */
-  generateCommand: function (aData) {
-    let utils = CurlUtils;
-
-    let command = ["curl"];
-    let ignoredHeaders = new Set();
-
-    // The cURL command is expected to run on the same platform that Firefox runs
-    // (it may be different from the inspected page platform).
-    let escapeString = Services.appinfo.OS == "WINNT" ?
-                       utils.escapeStringWin : utils.escapeStringPosix;
-
-    // Add URL.
-    command.push(escapeString(aData.url));
-
-    let postDataText = null;
-    let multipartRequest = utils.isMultipartRequest(aData);
-
-    // Create post data.
-    let data = [];
-    if (utils.isUrlEncodedRequest(aData) || aData.method == "PUT") {
-      postDataText = aData.postDataText;
-      data.push("--data");
-      data.push(escapeString(utils.writePostDataTextParams(postDataText)));
-      ignoredHeaders.add("Content-Length");
-    } else if (multipartRequest) {
-      postDataText = aData.postDataText;
-      data.push("--data-binary");
-      let boundary = utils.getMultipartBoundary(aData);
-      let text = utils.removeBinaryDataFromMultipartText(postDataText, boundary);
-      data.push(escapeString(text));
-      ignoredHeaders.add("Content-Length");
-    }
-
-    // Add method.
-    // For GET and POST requests this is not necessary as GET is the
-    // default. If --data or --binary is added POST is the default.
-    if (!(aData.method == "GET" || aData.method == "POST")) {
-      command.push("-X");
-      command.push(aData.method);
-    }
-
-    // Add -I (HEAD)
-    // For servers that supports HEAD.
-    // This will fetch the header of a document only.
-    if (aData.method == "HEAD") {
-      command.push("-I");
-    }
-
-    // Add http version.
-    if (aData.httpVersion && aData.httpVersion != DEFAULT_HTTP_VERSION) {
-      command.push("--" + aData.httpVersion.split("/")[1]);
-    }
-
-    // Add request headers.
-    let headers = aData.headers;
-    if (multipartRequest) {
-      let multipartHeaders = utils.getHeadersFromMultipartText(postDataText);
-      headers = headers.concat(multipartHeaders);
-    }
-    for (let i = 0; i < headers.length; i++) {
-      let header = headers[i];
-      if (header.name === "Accept-Encoding") {
-        command.push("--compressed");
-        continue;
-      }
-      if (ignoredHeaders.has(header.name)) {
-        continue;
-      }
-      command.push("-H");
-      command.push(escapeString(header.name + ": " + header.value));
-    }
-
-    // Add post data.
-    command = command.concat(data);
-
-    return command.join(" ");
-  }
-};
-
-/**
- * Utility functions for the Curl command generator.
- */
-this.CurlUtils = {
-  /**
-   * Check if the request is an URL encoded request.
-   *
-   * @param object aData
-   *        The data source. See the description in the Curl object.
-   * @return boolean
-   *         True if the request is URL encoded, false otherwise.
-   */
-  isUrlEncodedRequest: function (aData) {
-    let postDataText = aData.postDataText;
-    if (!postDataText) {
-      return false;
-    }
-
-    postDataText = postDataText.toLowerCase();
-    if (postDataText.includes("content-type: application/x-www-form-urlencoded")) {
-      return true;
-    }
-
-    let contentType = this.findHeader(aData.headers, "content-type");
-
-    return (contentType &&
-      contentType.toLowerCase().includes("application/x-www-form-urlencoded"));
-  },
-
-  /**
-   * Check if the request is a multipart request.
-   *
-   * @param object aData
-   *        The data source.
-   * @return boolean
-   *         True if the request is multipart reqeust, false otherwise.
-   */
-  isMultipartRequest: function (aData) {
-    let postDataText = aData.postDataText;
-    if (!postDataText) {
-      return false;
-    }
-
-    postDataText = postDataText.toLowerCase();
-    if (postDataText.includes("content-type: multipart/form-data")) {
-      return true;
-    }
-
-    let contentType = this.findHeader(aData.headers, "content-type");
-
-    return (contentType &&
-      contentType.toLowerCase().includes("multipart/form-data;"));
-  },
-
-  /**
-   * Write out paramters from post data text.
-   *
-   * @param object aPostDataText
-   *        Post data text.
-   * @return string
-   *         Post data parameters.
-   */
-  writePostDataTextParams: function (aPostDataText) {
-    let lines = aPostDataText.split("\r\n");
-    return lines[lines.length - 1];
-  },
-
-  /**
-   * Finds the header with the given name in the headers array.
-   *
-   * @param array aHeaders
-   *        Array of headers info {name:x, value:x}.
-   * @param string aName
-   *        The header name to find.
-   * @return string
-   *         The found header value or null if not found.
-   */
-  findHeader: function (aHeaders, aName) {
-    if (!aHeaders) {
-      return null;
-    }
-
-    let name = aName.toLowerCase();
-    for (let header of aHeaders) {
-      if (name == header.name.toLowerCase()) {
-        return header.value;
-      }
-    }
-
-    return null;
-  },
-
-  /**
-   * Returns the boundary string for a multipart request.
-   *
-   * @param string aData
-   *        The data source. See the description in the Curl object.
-   * @return string
-   *         The boundary string for the request.
-   */
-  getMultipartBoundary: function (aData) {
-    let boundaryRe = /\bboundary=(-{3,}\w+)/i;
-
-    // Get the boundary string from the Content-Type request header.
-    let contentType = this.findHeader(aData.headers, "Content-Type");
-    if (boundaryRe.test(contentType)) {
-      return contentType.match(boundaryRe)[1];
-    }
-    // Temporary workaround. As of 2014-03-11 the requestHeaders array does not
-    // always contain the Content-Type header for mulitpart requests. See bug 978144.
-    // Find the header from the request payload.
-    let boundaryString = aData.postDataText.match(boundaryRe)[1];
-    if (boundaryString) {
-      return boundaryString;
-    }
-
-    return null;
-  },
-
-  /**
-   * Removes the binary data from mulitpart text.
-   *
-   * @param string aMultipartText
-   *        Multipart form data text.
-   * @param string aBoundary
-   *        The boundary string.
-   * @return string
-   *         The mulitpart text without the binary data.
-   */
-  removeBinaryDataFromMultipartText: function (aMultipartText, aBoundary) {
-    let result = "";
-    let boundary = "--" + aBoundary;
-    let parts = aMultipartText.split(boundary);
-    for (let part of parts) {
-      // Each part is expected to have a content disposition line.
-      let contentDispositionLine = part.trimLeft().split("\r\n")[0];
-      if (!contentDispositionLine) {
-        continue;
-      }
-      contentDispositionLine = contentDispositionLine.toLowerCase();
-      if (contentDispositionLine.includes("content-disposition: form-data")) {
-        if (contentDispositionLine.includes("filename=")) {
-          // The header lines and the binary blob is separated by 2 CRLF's.
-          // Add only the headers to the result.
-          let headers = part.split("\r\n\r\n")[0];
-          result += boundary + "\r\n" + headers + "\r\n\r\n";
-        }
-        else {
-          result += boundary + "\r\n" + part;
-        }
-      }
-    }
-    result += boundary + "--\r\n";
-
-    return result;
-  },
-
-  /**
-   * Get the headers from a multipart post data text.
-   *
-   * @param string aMultipartText
-   *        Multipart post text.
-   * @return array
-   *         An array of header objects {name:x, value:x}
-   */
-  getHeadersFromMultipartText: function (aMultipartText) {
-    let headers = [];
-    if (!aMultipartText || aMultipartText.startsWith("---")) {
-      return headers;
-    }
-
-    // Get the header section.
-    let index = aMultipartText.indexOf("\r\n\r\n");
-    if (index == -1) {
-      return headers;
-    }
-
-    // Parse the header lines.
-    let headersText = aMultipartText.substring(0, index);
-    let headerLines = headersText.split("\r\n");
-    let lastHeaderName = null;
-
-    for (let line of headerLines) {
-      // Create a header for each line in fields that spans across multiple lines.
-      // Subsquent lines always begins with at least one space or tab character.
-      // (rfc2616)
-      if (lastHeaderName && /^\s+/.test(line)) {
-        headers.push({ name: lastHeaderName, value: line.trim() });
-        continue;
-      }
-
-      let indexOfColon = line.indexOf(":");
-      if (indexOfColon == -1) {
-        continue;
-      }
-
-      let header = [line.slice(0, indexOfColon), line.slice(indexOfColon + 1)];
-      if (header.length != 2) {
-        continue;
-      }
-      lastHeaderName = header[0].trim();
-      headers.push({ name: lastHeaderName, value: header[1].trim() });
-    }
-
-    return headers;
-  },
-
-  /**
-   * Escape util function for POSIX oriented operating systems.
-   * Credit: Google DevTools
-   */
-  escapeStringPosix: function (str) {
-    function escapeCharacter(x) {
-      let code = x.charCodeAt(0);
-      if (code < 256) {
-        // Add leading zero when needed to not care about the next character.
-        return code < 16 ? "\\x0" + code.toString(16) : "\\x" + code.toString(16);
-      }
-      code = code.toString(16);
-      return "\\u" + ("0000" + code).substr(code.length, 4);
-    }
-
-    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 + "'";
-    }
-  },
-
-  /**
-   * Escape util function for Windows systems.
-   * Credit: Google DevTools
-   */
-  escapeStringWin: function (str) {
-    /* Replace quote by double quote (but not by \") because it is
-       recognized by both cmd.exe and MS Crt arguments parser.
-
-       Replace % by "%" because it could be expanded to an environment
-       variable value. So %% becomes "%""%". Even if an env variable ""
-       (2 doublequotes) is declared, the cmd.exe will not
-       substitute it with its value.
-
-       Replace each backslash with double backslash to make sure
-       MS Crt arguments parser won't collapse them.
-
-       Replace new line outside of quotes since cmd.exe doesn't let
-       to do it inside.
-    */
-    return "\"" + str.replace(/"/g, "\"\"")
-                     .replace(/%/g, "\"%\"")
-                     .replace(/\\/g, "\\\\")
-                     .replace(/[\r\n]+/g, "\"^$&\"") + "\"";
-  }
-};
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/curl.js
@@ -0,0 +1,402 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
+ * Copyright (C) 2008, 2009 Anthony Ricaud <rik@webkit.org>
+ * Copyright (C) 2011 Google Inc. All rights reserved.
+ * Copyright (C) 2009 Mozilla Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ *     its contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+"use strict";
+
+const Services = require("Services");
+
+const DEFAULT_HTTP_VERSION = "HTTP/1.1";
+
+const Curl = {
+  /**
+   * Generates a cURL command string which can be used from the command line etc.
+   *
+   * @param object aData
+   *        Datasource to create the command from.
+   *        The object must contain the following properties:
+   *          - url:string, the URL of the request.
+   *          - method:string, the request method upper cased. HEAD / GET / POST etc.
+   *          - headers:array, an array of request headers {name:x, value:x} tuples.
+   *          - httpVersion:string, http protocol version rfc2616 formatted. Eg. "HTTP/1.1"
+   *          - postDataText:string, optional - the request payload.
+   *
+   * @return string
+   *         A cURL command.
+   */
+  generateCommand: function (aData) {
+    let utils = CurlUtils;
+
+    let command = ["curl"];
+    let ignoredHeaders = new Set();
+
+    // The cURL command is expected to run on the same platform that Firefox runs
+    // (it may be different from the inspected page platform).
+    let escapeString = Services.appinfo.OS == "WINNT" ?
+                       utils.escapeStringWin : utils.escapeStringPosix;
+
+    // Add URL.
+    command.push(escapeString(aData.url));
+
+    let postDataText = null;
+    let multipartRequest = utils.isMultipartRequest(aData);
+
+    // Create post data.
+    let data = [];
+    if (utils.isUrlEncodedRequest(aData) || aData.method == "PUT") {
+      postDataText = aData.postDataText;
+      data.push("--data");
+      data.push(escapeString(utils.writePostDataTextParams(postDataText)));
+      ignoredHeaders.add("Content-Length");
+    } else if (multipartRequest) {
+      postDataText = aData.postDataText;
+      data.push("--data-binary");
+      let boundary = utils.getMultipartBoundary(aData);
+      let text = utils.removeBinaryDataFromMultipartText(postDataText, boundary);
+      data.push(escapeString(text));
+      ignoredHeaders.add("Content-Length");
+    }
+
+    // Add method.
+    // For GET and POST requests this is not necessary as GET is the
+    // default. If --data or --binary is added POST is the default.
+    if (!(aData.method == "GET" || aData.method == "POST")) {
+      command.push("-X");
+      command.push(aData.method);
+    }
+
+    // Add -I (HEAD)
+    // For servers that supports HEAD.
+    // This will fetch the header of a document only.
+    if (aData.method == "HEAD") {
+      command.push("-I");
+    }
+
+    // Add http version.
+    if (aData.httpVersion && aData.httpVersion != DEFAULT_HTTP_VERSION) {
+      command.push("--" + aData.httpVersion.split("/")[1]);
+    }
+
+    // Add request headers.
+    let headers = aData.headers;
+    if (multipartRequest) {
+      let multipartHeaders = utils.getHeadersFromMultipartText(postDataText);
+      headers = headers.concat(multipartHeaders);
+    }
+    for (let i = 0; i < headers.length; i++) {
+      let header = headers[i];
+      if (header.name === "Accept-Encoding") {
+        command.push("--compressed");
+        continue;
+      }
+      if (ignoredHeaders.has(header.name)) {
+        continue;
+      }
+      command.push("-H");
+      command.push(escapeString(header.name + ": " + header.value));
+    }
+
+    // Add post data.
+    command = command.concat(data);
+
+    return command.join(" ");
+  }
+};
+
+exports.Curl = Curl;
+
+/**
+ * Utility functions for the Curl command generator.
+ */
+const CurlUtils = {
+  /**
+   * Check if the request is an URL encoded request.
+   *
+   * @param object aData
+   *        The data source. See the description in the Curl object.
+   * @return boolean
+   *         True if the request is URL encoded, false otherwise.
+   */
+  isUrlEncodedRequest: function (aData) {
+    let postDataText = aData.postDataText;
+    if (!postDataText) {
+      return false;
+    }
+
+    postDataText = postDataText.toLowerCase();
+    if (postDataText.includes("content-type: application/x-www-form-urlencoded")) {
+      return true;
+    }
+
+    let contentType = this.findHeader(aData.headers, "content-type");
+
+    return (contentType &&
+      contentType.toLowerCase().includes("application/x-www-form-urlencoded"));
+  },
+
+  /**
+   * Check if the request is a multipart request.
+   *
+   * @param object aData
+   *        The data source.
+   * @return boolean
+   *         True if the request is multipart reqeust, false otherwise.
+   */
+  isMultipartRequest: function (aData) {
+    let postDataText = aData.postDataText;
+    if (!postDataText) {
+      return false;
+    }
+
+    postDataText = postDataText.toLowerCase();
+    if (postDataText.includes("content-type: multipart/form-data")) {
+      return true;
+    }
+
+    let contentType = this.findHeader(aData.headers, "content-type");
+
+    return (contentType &&
+      contentType.toLowerCase().includes("multipart/form-data;"));
+  },
+
+  /**
+   * Write out paramters from post data text.
+   *
+   * @param object aPostDataText
+   *        Post data text.
+   * @return string
+   *         Post data parameters.
+   */
+  writePostDataTextParams: function (aPostDataText) {
+    let lines = aPostDataText.split("\r\n");
+    return lines[lines.length - 1];
+  },
+
+  /**
+   * Finds the header with the given name in the headers array.
+   *
+   * @param array aHeaders
+   *        Array of headers info {name:x, value:x}.
+   * @param string aName
+   *        The header name to find.
+   * @return string
+   *         The found header value or null if not found.
+   */
+  findHeader: function (aHeaders, aName) {
+    if (!aHeaders) {
+      return null;
+    }
+
+    let name = aName.toLowerCase();
+    for (let header of aHeaders) {
+      if (name == header.name.toLowerCase()) {
+        return header.value;
+      }
+    }
+
+    return null;
+  },
+
+  /**
+   * Returns the boundary string for a multipart request.
+   *
+   * @param string aData
+   *        The data source. See the description in the Curl object.
+   * @return string
+   *         The boundary string for the request.
+   */
+  getMultipartBoundary: function (aData) {
+    let boundaryRe = /\bboundary=(-{3,}\w+)/i;
+
+    // Get the boundary string from the Content-Type request header.
+    let contentType = this.findHeader(aData.headers, "Content-Type");
+    if (boundaryRe.test(contentType)) {
+      return contentType.match(boundaryRe)[1];
+    }
+    // Temporary workaround. As of 2014-03-11 the requestHeaders array does not
+    // always contain the Content-Type header for mulitpart requests. See bug 978144.
+    // Find the header from the request payload.
+    let boundaryString = aData.postDataText.match(boundaryRe)[1];
+    if (boundaryString) {
+      return boundaryString;
+    }
+
+    return null;
+  },
+
+  /**
+   * Removes the binary data from mulitpart text.
+   *
+   * @param string aMultipartText
+   *        Multipart form data text.
+   * @param string aBoundary
+   *        The boundary string.
+   * @return string
+   *         The mulitpart text without the binary data.
+   */
+  removeBinaryDataFromMultipartText: function (aMultipartText, aBoundary) {
+    let result = "";
+    let boundary = "--" + aBoundary;
+    let parts = aMultipartText.split(boundary);
+    for (let part of parts) {
+      // Each part is expected to have a content disposition line.
+      let contentDispositionLine = part.trimLeft().split("\r\n")[0];
+      if (!contentDispositionLine) {
+        continue;
+      }
+      contentDispositionLine = contentDispositionLine.toLowerCase();
+      if (contentDispositionLine.includes("content-disposition: form-data")) {
+        if (contentDispositionLine.includes("filename=")) {
+          // The header lines and the binary blob is separated by 2 CRLF's.
+          // Add only the headers to the result.
+          let headers = part.split("\r\n\r\n")[0];
+          result += boundary + "\r\n" + headers + "\r\n\r\n";
+        }
+        else {
+          result += boundary + "\r\n" + part;
+        }
+      }
+    }
+    result += boundary + "--\r\n";
+
+    return result;
+  },
+
+  /**
+   * Get the headers from a multipart post data text.
+   *
+   * @param string aMultipartText
+   *        Multipart post text.
+   * @return array
+   *         An array of header objects {name:x, value:x}
+   */
+  getHeadersFromMultipartText: function (aMultipartText) {
+    let headers = [];
+    if (!aMultipartText || aMultipartText.startsWith("---")) {
+      return headers;
+    }
+
+    // Get the header section.
+    let index = aMultipartText.indexOf("\r\n\r\n");
+    if (index == -1) {
+      return headers;
+    }
+
+    // Parse the header lines.
+    let headersText = aMultipartText.substring(0, index);
+    let headerLines = headersText.split("\r\n");
+    let lastHeaderName = null;
+
+    for (let line of headerLines) {
+      // Create a header for each line in fields that spans across multiple lines.
+      // Subsquent lines always begins with at least one space or tab character.
+      // (rfc2616)
+      if (lastHeaderName && /^\s+/.test(line)) {
+        headers.push({ name: lastHeaderName, value: line.trim() });
+        continue;
+      }
+
+      let indexOfColon = line.indexOf(":");
+      if (indexOfColon == -1) {
+        continue;
+      }
+
+      let header = [line.slice(0, indexOfColon), line.slice(indexOfColon + 1)];
+      if (header.length != 2) {
+        continue;
+      }
+      lastHeaderName = header[0].trim();
+      headers.push({ name: lastHeaderName, value: header[1].trim() });
+    }
+
+    return headers;
+  },
+
+  /**
+   * Escape util function for POSIX oriented operating systems.
+   * Credit: Google DevTools
+   */
+  escapeStringPosix: function (str) {
+    function escapeCharacter(x) {
+      let code = x.charCodeAt(0);
+      if (code < 256) {
+        // Add leading zero when needed to not care about the next character.
+        return code < 16 ? "\\x0" + code.toString(16) : "\\x" + code.toString(16);
+      }
+      code = code.toString(16);
+      return "\\u" + ("0000" + code).substr(code.length, 4);
+    }
+
+    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 + "'";
+    }
+  },
+
+  /**
+   * Escape util function for Windows systems.
+   * Credit: Google DevTools
+   */
+  escapeStringWin: function (str) {
+    /* Replace quote by double quote (but not by \") because it is
+       recognized by both cmd.exe and MS Crt arguments parser.
+
+       Replace % by "%" because it could be expanded to an environment
+       variable value. So %% becomes "%""%". Even if an env variable ""
+       (2 doublequotes) is declared, the cmd.exe will not
+       substitute it with its value.
+
+       Replace each backslash with double backslash to make sure
+       MS Crt arguments parser won't collapse them.
+
+       Replace new line outside of quotes since cmd.exe doesn't let
+       to do it inside.
+    */
+    return "\"" + str.replace(/"/g, "\"\"")
+                     .replace(/%/g, "\"%\"")
+                     .replace(/\\/g, "\\\\")
+                     .replace(/[\r\n]+/g, "\"^$&\"") + "\"";
+  }
+};
+
+exports.CurlUtils = CurlUtils;
--- a/devtools/client/shared/moz.build
+++ b/devtools/client/shared/moz.build
@@ -16,17 +16,17 @@ DIRS += [
 ]
 
 DevToolsModules(
     'AppCacheUtils.jsm',
     'autocomplete-popup.js',
     'browser-loader.js',
     'css-angle.js',
     'css-reload.js',
-    'Curl.jsm',
+    'curl.js',
     'demangle.js',
     'developer-toolbar.js',
     'devices.js',
     'devtools-file-watcher.js',
     'DOMHelpers.jsm',
     'doorhanger.js',
     'file-watcher-worker.js',
     'file-watcher.js',