browser/devtools/shared/Curl.jsm

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
     3 /* This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 /*
     8  * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
     9  * Copyright (C) 2008, 2009 Anthony Ricaud <rik@webkit.org>
    10  * Copyright (C) 2011 Google Inc. All rights reserved.
    11  * Copyright (C) 2009 Mozilla Foundation. All rights reserved.
    12  *
    13  * Redistribution and use in source and binary forms, with or without
    14  * modification, are permitted provided that the following conditions
    15  * are met:
    16  *
    17  * 1.  Redistributions of source code must retain the above copyright
    18  *     notice, this list of conditions and the following disclaimer.
    19  * 2.  Redistributions in binary form must reproduce the above copyright
    20  *     notice, this list of conditions and the following disclaimer in the
    21  *     documentation and/or other materials provided with the distribution.
    22  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
    23  *     its contributors may be used to endorse or promote products derived
    24  *     from this software without specific prior written permission.
    25  *
    26  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    27  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    28  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    29  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
    30  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    31  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
    32  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
    33  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    34  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
    35  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    36  */
    38 "use strict";
    40 this.EXPORTED_SYMBOLS = ["Curl", "CurlUtils"];
    42 Components.utils.import("resource://gre/modules/Services.jsm");
    44 const DEFAULT_HTTP_VERSION = "HTTP/1.1";
    46 this.Curl = {
    47   /**
    48    * Generates a cURL command string which can be used from the command line etc.
    49    *
    50    * @param object aData
    51    *        Datasource to create the command from.
    52    *        The object must contain the following properties:
    53    *          - url:string, the URL of the request.
    54    *          - method:string, the request method upper cased. HEAD / GET / POST etc.
    55    *          - headers:array, an array of request headers {name:x, value:x} tuples.
    56    *          - httpVersion:string, http protocol version rfc2616 formatted. Eg. "HTTP/1.1"
    57    *          - postDataText:string, optional - the request payload.
    58    *
    59    * @return string
    60    *         A cURL command.
    61    */
    62   generateCommand: function(aData) {
    63     let utils = CurlUtils;
    65     let command = ["curl"];
    66     let ignoredHeaders = new Set();
    68     // The cURL command is expected to run on the same platform that Firefox runs
    69     // (it may be different from the inspected page platform).
    70     let escapeString = Services.appinfo.OS == "WINNT" ?
    71                        utils.escapeStringWin : utils.escapeStringPosix;
    73     // Add URL.
    74     command.push(escapeString(aData.url));
    76     let postDataText = null;
    77     let multipartRequest = utils.isMultipartRequest(aData);
    79     // Create post data.
    80     let data = [];
    81     if (utils.isUrlEncodedRequest(aData) || aData.method == "PUT") {
    82       postDataText = aData.postDataText;
    83       data.push("--data");
    84       data.push(escapeString(utils.writePostDataTextParams(postDataText)));
    85       ignoredHeaders.add("Content-Length");
    86     } else if (multipartRequest) {
    87       postDataText = aData.postDataText;
    88       data.push("--data-binary");
    89       let boundary = utils.getMultipartBoundary(aData);
    90       let text = utils.removeBinaryDataFromMultipartText(postDataText, boundary);
    91       data.push(escapeString(text));
    92       ignoredHeaders.add("Content-Length");
    93     }
    95     // Add method.
    96     // For GET and POST requests this is not necessary as GET is the
    97     // default. If --data or --binary is added POST is the default.
    98     if (!(aData.method == "GET" || aData.method == "POST")) {
    99       command.push("-X");
   100       command.push(aData.method);
   101     }
   103     // Add -I (HEAD)
   104     // For servers that supports HEAD.
   105     // This will fetch the header of a document only.
   106     if (aData.method == "HEAD") {
   107       command.push("-I");
   108     }
   110     // Add http version.
   111     if (aData.httpVersion && aData.httpVersion != DEFAULT_HTTP_VERSION) {
   112       command.push("--" + aData.httpVersion.split("/")[1]);
   113     }
   115     // Add request headers.
   116     let headers = aData.headers;
   117     if (multipartRequest) {
   118       let multipartHeaders = utils.getHeadersFromMultipartText(postDataText);
   119       headers = headers.concat(multipartHeaders);
   120     }
   121     for (let i = 0; i < headers.length; i++) {
   122       let header = headers[i];
   123       if (ignoredHeaders.has(header.name)) {
   124         continue;
   125       }
   126       command.push("-H");
   127       command.push(escapeString(header.name + ": " + header.value));
   128     }
   130     // Add post data.
   131     command = command.concat(data);
   133     return command.join(" ");
   134   }
   135 };
   137 /**
   138  * Utility functions for the Curl command generator.
   139  */
   140 this.CurlUtils = {
   141   /**
   142    * Check if the request is an URL encoded request.
   143    *
   144    * @param object aData
   145    *        The data source. See the description in the Curl object.
   146    * @return boolean
   147    *         True if the request is URL encoded, false otherwise.
   148    */
   149   isUrlEncodedRequest: function(aData) {
   150     let postDataText = aData.postDataText;
   151     if (!postDataText) {
   152       return false;
   153     }
   155     postDataText = postDataText.toLowerCase();
   156     if (postDataText.contains("content-type: application/x-www-form-urlencoded")) {
   157       return true;
   158     }
   160     let contentType = this.findHeader(aData.headers, "content-type");
   162     return (contentType &&
   163       contentType.toLowerCase().contains("application/x-www-form-urlencoded"));
   164   },
   166   /**
   167    * Check if the request is a multipart request.
   168    *
   169    * @param object aData
   170    *        The data source.
   171    * @return boolean
   172    *         True if the request is multipart reqeust, false otherwise.
   173    */
   174   isMultipartRequest: function(aData) {
   175     let postDataText = aData.postDataText;
   176     if (!postDataText) {
   177       return false;
   178     }
   180     postDataText = postDataText.toLowerCase();
   181     if (postDataText.contains("content-type: multipart/form-data")) {
   182       return true;
   183     }
   185     let contentType = this.findHeader(aData.headers, "content-type");
   187     return (contentType &&
   188       contentType.toLowerCase().contains("multipart/form-data;"));
   189   },
   191   /**
   192    * Write out paramters from post data text.
   193    *
   194    * @param object aPostDataText
   195    *        Post data text.
   196    * @return string
   197    *         Post data parameters.
   198    */
   199   writePostDataTextParams: function(aPostDataText) {
   200     let lines = aPostDataText.split("\r\n");
   201     return lines[lines.length - 1];
   202   },
   204   /**
   205    * Finds the header with the given name in the headers array.
   206    *
   207    * @param array aHeaders
   208    *        Array of headers info {name:x, value:x}.
   209    * @param string aName
   210    *        The header name to find.
   211    * @return string
   212    *         The found header value or null if not found.
   213    */
   214   findHeader: function(aHeaders, aName) {
   215     if (!aHeaders) {
   216       return null;
   217     }
   219     let name = aName.toLowerCase();
   220     for (let header of aHeaders) {
   221       if (name == header.name.toLowerCase()) {
   222         return header.value;
   223       }
   224     }
   226     return null;
   227   },
   229   /**
   230    * Returns the boundary string for a multipart request.
   231    *
   232    * @param string aData
   233    *        The data source. See the description in the Curl object.
   234    * @return string
   235    *         The boundary string for the request.
   236    */
   237   getMultipartBoundary: function(aData) {
   238     let boundaryRe = /\bboundary=(-{3,}\w+)/i;
   240     // Get the boundary string from the Content-Type request header.
   241     let contentType = this.findHeader(aData.headers, "Content-Type");
   242     if (boundaryRe.test(contentType)) {
   243       return contentType.match(boundaryRe)[1];
   244     }
   245     // Temporary workaround. As of 2014-03-11 the requestHeaders array does not
   246     // always contain the Content-Type header for mulitpart requests. See bug 978144.
   247     // Find the header from the request payload.
   248     let boundaryString = aData.postDataText.match(boundaryRe)[1];
   249     if (boundaryString) {
   250       return boundaryString;
   251     }
   253     return null;
   254   },
   256   /**
   257    * Removes the binary data from mulitpart text.
   258    *
   259    * @param string aMultipartText
   260    *        Multipart form data text.
   261    * @param string aBoundary
   262    *        The boundary string.
   263    * @return string
   264    *         The mulitpart text without the binary data.
   265    */
   266   removeBinaryDataFromMultipartText: function(aMultipartText, aBoundary) {
   267     let result = "";
   268     let boundary = "--" + aBoundary;
   269     let parts = aMultipartText.split(boundary);
   270     for (let part of parts) {
   271       // Each part is expected to have a content disposition line.
   272       let contentDispositionLine = part.trimLeft().split("\r\n")[0];
   273       if (!contentDispositionLine) {
   274         continue;
   275       }
   276       contentDispositionLine = contentDispositionLine.toLowerCase();
   277       if (contentDispositionLine.contains("content-disposition: form-data")) {
   278         if (contentDispositionLine.contains("filename=")) {
   279           // The header lines and the binary blob is separated by 2 CRLF's.
   280           // Add only the headers to the result.
   281           let headers = part.split("\r\n\r\n")[0];
   282           result += boundary + "\r\n" + headers + "\r\n\r\n";
   283         }
   284         else {
   285           result += boundary + "\r\n" + part;
   286         }
   287       }
   288     }
   289     result += aBoundary + "--\r\n";
   291     return result;
   292   },
   294   /**
   295    * Get the headers from a multipart post data text.
   296    *
   297    * @param string aMultipartText
   298    *        Multipart post text.
   299    * @return array
   300    *         An array of header objects {name:x, value:x}
   301    */
   302   getHeadersFromMultipartText: function(aMultipartText) {
   303     let headers = [];
   304     if (!aMultipartText || aMultipartText.startsWith("---")) {
   305       return headers;
   306     }
   308     // Get the header section.
   309     let index = aMultipartText.indexOf("\r\n\r\n");
   310     if (index == -1) {
   311       return headers;
   312     }
   314     // Parse the header lines.
   315     let headersText = aMultipartText.substring(0, index);
   316     let headerLines = headersText.split("\r\n");
   317     let lastHeaderName = null;
   319     for (let line of headerLines) {
   320       // Create a header for each line in fields that spans across multiple lines.
   321       // Subsquent lines always begins with at least one space or tab character.
   322       // (rfc2616)
   323       if (lastHeaderName && /^\s+/.test(line)) {
   324         headers.push({ name: lastHeaderName, value: line.trim() });
   325         continue;
   326       }
   328       let indexOfColon = line.indexOf(":");
   329       if (indexOfColon == -1) {
   330         continue;
   331       }
   333       let header = [line.slice(0, indexOfColon), line.slice(indexOfColon + 1)];
   334       if (header.length != 2) {
   335         continue;
   336       }
   337       lastHeaderName = header[0].trim();
   338       headers.push({ name: lastHeaderName, value: header[1].trim() });
   339     }
   341     return headers;
   342   },
   344   /**
   345    * Escape util function for POSIX oriented operating systems.
   346    * Credit: Google DevTools
   347    */
   348   escapeStringPosix: function(str) {
   349     function escapeCharacter(x) {
   350       let code = x.charCodeAt(0);
   351       if (code < 256) {
   352         // Add leading zero when needed to not care about the next character.
   353         return code < 16 ? "\\x0" + code.toString(16) : "\\x" + code.toString(16);
   354       }
   355       code = code.toString(16);
   356       return "\\u" + ("0000" + code).substr(code.length, 4);
   357     }
   359     if (/[^\x20-\x7E]|\'/.test(str)) {
   360       // Use ANSI-C quoting syntax.
   361       return "$\'" + str.replace(/\\/g, "\\\\")
   362                         .replace(/\'/g, "\\\'")
   363                         .replace(/\n/g, "\\n")
   364                         .replace(/\r/g, "\\r")
   365                         .replace(/[^\x20-\x7E]/g, escapeCharacter) + "'";
   366     } else {
   367       // Use single quote syntax.
   368       return "'" + str + "'";
   369     }
   370   },
   372   /**
   373    * Escape util function for Windows systems.
   374    * Credit: Google DevTools
   375    */
   376   escapeStringWin: function(str) {
   377     /* Replace quote by double quote (but not by \") because it is
   378        recognized by both cmd.exe and MS Crt arguments parser.
   380        Replace % by "%" because it could be expanded to an environment
   381        variable value. So %% becomes "%""%". Even if an env variable ""
   382        (2 doublequotes) is declared, the cmd.exe will not
   383        substitute it with its value.
   385        Replace each backslash with double backslash to make sure
   386        MS Crt arguments parser won't collapse them.
   388        Replace new line outside of quotes since cmd.exe doesn't let
   389        to do it inside.
   390     */
   391     return "\"" + str.replace(/"/g, "\"\"")
   392                      .replace(/%/g, "\"%\"")
   393                      .replace(/\\/g, "\\\\")
   394                      .replace(/[\r\n]+/g, "\"^$&\"") + "\"";
   395   }
   396 };

mercurial