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.

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

mercurial