Wed, 31 Dec 2014 06:09:35 +0100
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 };