Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | 'use strict'; |
michael@0 | 5 | |
michael@0 | 6 | module.metadata = { |
michael@0 | 7 | 'stability': 'experimental' |
michael@0 | 8 | }; |
michael@0 | 9 | |
michael@0 | 10 | let { Ci } = require('chrome'); |
michael@0 | 11 | let subprocess = require('./child_process/subprocess'); |
michael@0 | 12 | let { EventTarget } = require('../event/target'); |
michael@0 | 13 | let { Stream } = require('../io/stream'); |
michael@0 | 14 | let { on, emit, off } = require('../event/core'); |
michael@0 | 15 | let { Class } = require('../core/heritage'); |
michael@0 | 16 | let { platform } = require('../system'); |
michael@0 | 17 | let { isFunction, isArray } = require('../lang/type'); |
michael@0 | 18 | let { delay } = require('../lang/functional'); |
michael@0 | 19 | let { merge } = require('../util/object'); |
michael@0 | 20 | let { setTimeout, clearTimeout } = require('../timers'); |
michael@0 | 21 | let isWindows = platform.indexOf('win') === 0; |
michael@0 | 22 | |
michael@0 | 23 | let processes = WeakMap(); |
michael@0 | 24 | |
michael@0 | 25 | |
michael@0 | 26 | /** |
michael@0 | 27 | * The `Child` class wraps a subprocess command, exposes |
michael@0 | 28 | * the stdio streams, and methods to manipulate the subprocess |
michael@0 | 29 | */ |
michael@0 | 30 | let Child = Class({ |
michael@0 | 31 | implements: [EventTarget], |
michael@0 | 32 | initialize: function initialize (options) { |
michael@0 | 33 | let child = this; |
michael@0 | 34 | let proc; |
michael@0 | 35 | |
michael@0 | 36 | this.killed = false; |
michael@0 | 37 | this.exitCode = undefined; |
michael@0 | 38 | this.signalCode = undefined; |
michael@0 | 39 | |
michael@0 | 40 | this.stdin = Stream(); |
michael@0 | 41 | this.stdout = Stream(); |
michael@0 | 42 | this.stderr = Stream(); |
michael@0 | 43 | |
michael@0 | 44 | try { |
michael@0 | 45 | proc = subprocess.call({ |
michael@0 | 46 | command: options.file, |
michael@0 | 47 | arguments: options.cmdArgs, |
michael@0 | 48 | environment: serializeEnv(options.env), |
michael@0 | 49 | workdir: options.cwd, |
michael@0 | 50 | charset: options.encoding, |
michael@0 | 51 | stdout: data => emit(child.stdout, 'data', data), |
michael@0 | 52 | stderr: data => emit(child.stderr, 'data', data), |
michael@0 | 53 | stdin: stream => { |
michael@0 | 54 | child.stdin.on('data', pumpStdin); |
michael@0 | 55 | child.stdin.on('end', function closeStdin () { |
michael@0 | 56 | child.stdin.off('data', pumpStdin); |
michael@0 | 57 | child.stdin.off('end', closeStdin); |
michael@0 | 58 | stream.close(); |
michael@0 | 59 | }); |
michael@0 | 60 | function pumpStdin (data) { |
michael@0 | 61 | stream.write(data); |
michael@0 | 62 | } |
michael@0 | 63 | }, |
michael@0 | 64 | done: function (result) { |
michael@0 | 65 | // Only emit if child is not killed; otherwise, |
michael@0 | 66 | // the `kill` method will handle this |
michael@0 | 67 | if (!child.killed) { |
michael@0 | 68 | child.exitCode = result.exitCode; |
michael@0 | 69 | child.signalCode = null; |
michael@0 | 70 | |
michael@0 | 71 | // If process exits with < 0, there was an error |
michael@0 | 72 | if (child.exitCode < 0) { |
michael@0 | 73 | handleError(new Error('Process exited with exit code ' + child.exitCode)); |
michael@0 | 74 | } |
michael@0 | 75 | else { |
michael@0 | 76 | // Also do 'exit' event as there's not much of |
michael@0 | 77 | // a difference in our implementation as we're not using |
michael@0 | 78 | // node streams |
michael@0 | 79 | emit(child, 'exit', child.exitCode, child.signalCode); |
michael@0 | 80 | } |
michael@0 | 81 | |
michael@0 | 82 | // Emit 'close' event with exit code and signal, |
michael@0 | 83 | // which is `null`, as it was not a killed process |
michael@0 | 84 | emit(child, 'close', child.exitCode, child.signalCode); |
michael@0 | 85 | } |
michael@0 | 86 | } |
michael@0 | 87 | }); |
michael@0 | 88 | processes.set(child, proc); |
michael@0 | 89 | } catch (e) { |
michael@0 | 90 | // Delay the error handling so an error handler can be set |
michael@0 | 91 | // during the same tick that the Child was created |
michael@0 | 92 | delay(() => handleError(e)); |
michael@0 | 93 | } |
michael@0 | 94 | |
michael@0 | 95 | // `handleError` is called when process could not even |
michael@0 | 96 | // be spawned |
michael@0 | 97 | function handleError (e) { |
michael@0 | 98 | // If error is an nsIObject, make a fresh error object |
michael@0 | 99 | // so we're not exposing nsIObjects, and we can modify it |
michael@0 | 100 | // with additional process information, like node |
michael@0 | 101 | let error = e; |
michael@0 | 102 | if (e instanceof Ci.nsISupports) { |
michael@0 | 103 | error = new Error(e.message, e.filename, e.lineNumber); |
michael@0 | 104 | } |
michael@0 | 105 | emit(child, 'error', error); |
michael@0 | 106 | child.exitCode = -1; |
michael@0 | 107 | child.signalCode = null; |
michael@0 | 108 | emit(child, 'close', child.exitCode, child.signalCode); |
michael@0 | 109 | } |
michael@0 | 110 | }, |
michael@0 | 111 | kill: function kill (signal) { |
michael@0 | 112 | let proc = processes.get(this); |
michael@0 | 113 | proc.kill(signal); |
michael@0 | 114 | this.killed = true; |
michael@0 | 115 | this.exitCode = null; |
michael@0 | 116 | this.signalCode = signal; |
michael@0 | 117 | emit(this, 'exit', this.exitCode, this.signalCode); |
michael@0 | 118 | emit(this, 'close', this.exitCode, this.signalCode); |
michael@0 | 119 | }, |
michael@0 | 120 | get pid() { return processes.get(this, {}).pid || -1; } |
michael@0 | 121 | }); |
michael@0 | 122 | |
michael@0 | 123 | function spawn (file, ...args) { |
michael@0 | 124 | let cmdArgs = []; |
michael@0 | 125 | // Default options |
michael@0 | 126 | let options = { |
michael@0 | 127 | cwd: null, |
michael@0 | 128 | env: null, |
michael@0 | 129 | encoding: 'UTF-8' |
michael@0 | 130 | }; |
michael@0 | 131 | |
michael@0 | 132 | if (args[1]) { |
michael@0 | 133 | merge(options, args[1]); |
michael@0 | 134 | cmdArgs = args[0]; |
michael@0 | 135 | } |
michael@0 | 136 | else { |
michael@0 | 137 | if (isArray(args[0])) |
michael@0 | 138 | cmdArgs = args[0]; |
michael@0 | 139 | else |
michael@0 | 140 | merge(options, args[0]); |
michael@0 | 141 | } |
michael@0 | 142 | |
michael@0 | 143 | if ('gid' in options) |
michael@0 | 144 | console.warn('`gid` option is not yet supported for `child_process`'); |
michael@0 | 145 | if ('uid' in options) |
michael@0 | 146 | console.warn('`uid` option is not yet supported for `child_process`'); |
michael@0 | 147 | if ('detached' in options) |
michael@0 | 148 | console.warn('`detached` option is not yet supported for `child_process`'); |
michael@0 | 149 | |
michael@0 | 150 | options.file = file; |
michael@0 | 151 | options.cmdArgs = cmdArgs; |
michael@0 | 152 | |
michael@0 | 153 | return Child(options); |
michael@0 | 154 | } |
michael@0 | 155 | |
michael@0 | 156 | exports.spawn = spawn; |
michael@0 | 157 | |
michael@0 | 158 | /** |
michael@0 | 159 | * exec(command, options, callback) |
michael@0 | 160 | */ |
michael@0 | 161 | function exec (cmd, ...args) { |
michael@0 | 162 | let file, cmdArgs, callback, options = {}; |
michael@0 | 163 | |
michael@0 | 164 | if (isFunction(args[0])) |
michael@0 | 165 | callback = args[0]; |
michael@0 | 166 | else { |
michael@0 | 167 | merge(options, args[0]); |
michael@0 | 168 | callback = args[1]; |
michael@0 | 169 | } |
michael@0 | 170 | |
michael@0 | 171 | if (isWindows) { |
michael@0 | 172 | file = 'C:\\Windows\\System32\\cmd.exe'; |
michael@0 | 173 | cmdArgs = ['/s', '/c', (cmd || '').split(' ')]; |
michael@0 | 174 | } |
michael@0 | 175 | else { |
michael@0 | 176 | file = '/bin/sh'; |
michael@0 | 177 | cmdArgs = ['-c', cmd]; |
michael@0 | 178 | } |
michael@0 | 179 | |
michael@0 | 180 | // Undocumented option from node being able to specify shell |
michael@0 | 181 | if (options && options.shell) |
michael@0 | 182 | file = options.shell; |
michael@0 | 183 | |
michael@0 | 184 | return execFile(file, cmdArgs, options, callback); |
michael@0 | 185 | } |
michael@0 | 186 | exports.exec = exec; |
michael@0 | 187 | /** |
michael@0 | 188 | * execFile (file, args, options, callback) |
michael@0 | 189 | */ |
michael@0 | 190 | function execFile (file, ...args) { |
michael@0 | 191 | let cmdArgs = [], callback; |
michael@0 | 192 | // Default options |
michael@0 | 193 | let options = { |
michael@0 | 194 | cwd: null, |
michael@0 | 195 | env: null, |
michael@0 | 196 | encoding: 'utf8', |
michael@0 | 197 | timeout: 0, |
michael@0 | 198 | maxBuffer: 200 * 1024, |
michael@0 | 199 | killSignal: 'SIGTERM' |
michael@0 | 200 | }; |
michael@0 | 201 | |
michael@0 | 202 | if (isFunction(args[args.length - 1])) |
michael@0 | 203 | callback = args[args.length - 1]; |
michael@0 | 204 | |
michael@0 | 205 | if (isArray(args[0])) { |
michael@0 | 206 | cmdArgs = args[0]; |
michael@0 | 207 | merge(options, args[1]); |
michael@0 | 208 | } else if (!isFunction(args[0])) |
michael@0 | 209 | merge(options, args[0]); |
michael@0 | 210 | |
michael@0 | 211 | let child = spawn(file, cmdArgs, options); |
michael@0 | 212 | let exited = false; |
michael@0 | 213 | let stdout = ''; |
michael@0 | 214 | let stderr = ''; |
michael@0 | 215 | let error = null; |
michael@0 | 216 | let timeoutId = null; |
michael@0 | 217 | |
michael@0 | 218 | child.stdout.setEncoding(options.encoding); |
michael@0 | 219 | child.stderr.setEncoding(options.encoding); |
michael@0 | 220 | |
michael@0 | 221 | on(child.stdout, 'data', pumpStdout); |
michael@0 | 222 | on(child.stderr, 'data', pumpStderr); |
michael@0 | 223 | on(child, 'close', exitHandler); |
michael@0 | 224 | on(child, 'error', errorHandler); |
michael@0 | 225 | |
michael@0 | 226 | if (options.timeout > 0) { |
michael@0 | 227 | setTimeout(() => { |
michael@0 | 228 | kill(); |
michael@0 | 229 | timeoutId = null; |
michael@0 | 230 | }, options.timeout); |
michael@0 | 231 | } |
michael@0 | 232 | |
michael@0 | 233 | function exitHandler (code, signal) { |
michael@0 | 234 | |
michael@0 | 235 | // Return if exitHandler called previously, occurs |
michael@0 | 236 | // when multiple maxBuffer errors thrown and attempt to kill multiple |
michael@0 | 237 | // times |
michael@0 | 238 | if (exited) return; |
michael@0 | 239 | exited = true; |
michael@0 | 240 | |
michael@0 | 241 | if (!isFunction(callback)) return; |
michael@0 | 242 | |
michael@0 | 243 | if (timeoutId) { |
michael@0 | 244 | clearTimeout(timeoutId); |
michael@0 | 245 | timeoutId = null; |
michael@0 | 246 | } |
michael@0 | 247 | |
michael@0 | 248 | if (!error && (code !== 0 || signal !== null)) |
michael@0 | 249 | error = createProcessError(new Error('Command failed: ' + stderr), { |
michael@0 | 250 | code: code, |
michael@0 | 251 | signal: signal, |
michael@0 | 252 | killed: !!child.killed |
michael@0 | 253 | }); |
michael@0 | 254 | |
michael@0 | 255 | callback(error, stdout, stderr); |
michael@0 | 256 | |
michael@0 | 257 | off(child.stdout, 'data', pumpStdout); |
michael@0 | 258 | off(child.stderr, 'data', pumpStderr); |
michael@0 | 259 | off(child, 'close', exitHandler); |
michael@0 | 260 | off(child, 'error', errorHandler); |
michael@0 | 261 | } |
michael@0 | 262 | |
michael@0 | 263 | function errorHandler (e) { |
michael@0 | 264 | error = e; |
michael@0 | 265 | exitHandler(); |
michael@0 | 266 | } |
michael@0 | 267 | |
michael@0 | 268 | function kill () { |
michael@0 | 269 | try { |
michael@0 | 270 | child.kill(options.killSignal); |
michael@0 | 271 | } catch (e) { |
michael@0 | 272 | // In the scenario where the kill signal happens when |
michael@0 | 273 | // the process is already closing, just abort the kill fail |
michael@0 | 274 | if (/library is not open/.test(e)) |
michael@0 | 275 | return; |
michael@0 | 276 | error = e; |
michael@0 | 277 | exitHandler(-1, options.killSignal); |
michael@0 | 278 | } |
michael@0 | 279 | } |
michael@0 | 280 | |
michael@0 | 281 | function pumpStdout (data) { |
michael@0 | 282 | stdout += data; |
michael@0 | 283 | if (stdout.length > options.maxBuffer) { |
michael@0 | 284 | error = new Error('stdout maxBuffer exceeded'); |
michael@0 | 285 | kill(); |
michael@0 | 286 | } |
michael@0 | 287 | } |
michael@0 | 288 | |
michael@0 | 289 | function pumpStderr (data) { |
michael@0 | 290 | stderr += data; |
michael@0 | 291 | if (stderr.length > options.maxBuffer) { |
michael@0 | 292 | error = new Error('stderr maxBuffer exceeded'); |
michael@0 | 293 | kill(); |
michael@0 | 294 | } |
michael@0 | 295 | } |
michael@0 | 296 | |
michael@0 | 297 | return child; |
michael@0 | 298 | } |
michael@0 | 299 | exports.execFile = execFile; |
michael@0 | 300 | |
michael@0 | 301 | exports.fork = function fork () { |
michael@0 | 302 | throw new Error("child_process#fork is not currently supported"); |
michael@0 | 303 | }; |
michael@0 | 304 | |
michael@0 | 305 | function serializeEnv (obj) { |
michael@0 | 306 | return Object.keys(obj || {}).map(prop => prop + '=' + obj[prop]); |
michael@0 | 307 | } |
michael@0 | 308 | |
michael@0 | 309 | function createProcessError (err, options = {}) { |
michael@0 | 310 | // If code and signal look OK, this was probably a failure |
michael@0 | 311 | // attempting to spawn the process (like ENOENT in node) -- use |
michael@0 | 312 | // the code from the error message |
michael@0 | 313 | if (!options.code && !options.signal) { |
michael@0 | 314 | let match = err.message.match(/(NS_ERROR_\w*)/); |
michael@0 | 315 | if (match && match.length > 1) |
michael@0 | 316 | err.code = match[1]; |
michael@0 | 317 | else { |
michael@0 | 318 | // If no good error message found, use the passed in exit code; |
michael@0 | 319 | // this occurs when killing a process that's already closing, |
michael@0 | 320 | // where we want both a valid exit code (0) and the error |
michael@0 | 321 | err.code = options.code != null ? options.code : null; |
michael@0 | 322 | } |
michael@0 | 323 | } |
michael@0 | 324 | else |
michael@0 | 325 | err.code = options.code != null ? options.code : null; |
michael@0 | 326 | err.signal = options.signal || null; |
michael@0 | 327 | err.killed = options.killed || false; |
michael@0 | 328 | return err; |
michael@0 | 329 | } |