Revision d59beb9f

View differences:

lib/tls.js
24 24
var net = require('net');
25 25
var url = require('url');
26 26
var events = require('events');
27
var Stream = require('stream');
28
var END_OF_FILE = 42;
27
var stream = require('stream');
29 28
var assert = require('assert').ok;
30 29
var constants = require('constants');
31 30

  
......
209 208
};
210 209

  
211 210

  
212
SlabBuffer.prototype.use = function use(context, fn) {
211
SlabBuffer.prototype.use = function use(context, fn, size) {
213 212
  if (this.remaining === 0) {
214 213
    this.isFull = true;
215 214
    return 0;
216 215
  }
217 216

  
218
  var bytes = fn.call(context, this.pool, this.offset, this.remaining);
217
  var actualSize = this.remaining;
219 218

  
219
  if (size !== null) actualSize = Math.min(size, actualSize);
220

  
221
  var bytes = fn.call(context, this.pool, this.offset, actualSize);
220 222
  if (bytes > 0) {
221 223
    this.offset += bytes;
222 224
    this.remaining -= bytes;
......
232 234

  
233 235

  
234 236
// Base class of both CleartextStream and EncryptedStream
235
function CryptoStream(pair) {
236
  Stream.call(this);
237
function CryptoStream(pair, options) {
238
  stream.Duplex.call(this, options);
237 239

  
238 240
  this.pair = pair;
241
  this._pending = null;
242
  this._pendingCallback = null;
243
  this._doneFlag = false;
244
  this._resumingSession = false;
245
  this._destroyed = false;
246
  this._ended = false;
247
  this._finished = false;
248
  this._opposite = null;
239 249

  
240
  this.readable = this.writable = true;
241

  
242
  this._paused = false;
243
  this._needDrain = false;
244
  this._pending = [];
245
  this._pendingCallbacks = [];
246
  this._pendingBytes = 0;
247 250
  if (slabBuffer === null) slabBuffer = new SlabBuffer();
248 251
  this._buffer = slabBuffer;
252

  
253
  this.once('finish', onCryptoStreamFinish);
254

  
255
  // net.Socket calls .onend too
256
  this.once('end', onCryptoStreamEnd);
249 257
}
250
util.inherits(CryptoStream, Stream);
258
util.inherits(CryptoStream, stream.Duplex);
259

  
251 260

  
261
function onCryptoStreamFinish() {
262
  this._finished = true;
252 263

  
253
CryptoStream.prototype.write = function(data /* , encoding, cb */) {
254
  if (this == this.pair.cleartext) {
255
    debug('cleartext.write called with ' + data.length + ' bytes');
264
  if (this === this.pair.cleartext) {
265
    debug('cleartext.onfinish');
266
    if (this.pair.ssl) {
267
      // Generate close notify
268
      // NOTE: first call checks if client has sent us shutdown,
269
      // second call enqueues shutdown into the BIO.
270
      if (this.pair.ssl.shutdown() !== 1) {
271
        this.pair.ssl.shutdown();
272
      }
273
    }
256 274
  } else {
257
    debug('encrypted.write called with ' + data.length + ' bytes');
275
    debug('encrypted.onfinish');
258 276
  }
259 277

  
260
  if (!this.writable) {
261
    throw new Error('CryptoStream is not writable');
262
  }
278
  // Try to read just to get sure that we won't miss EOF
279
  if (this._opposite.readable) this._opposite.read(0);
263 280

  
264
  var encoding, cb;
281
  if (this._opposite._ended) {
282
    this._done();
265 283

  
266
  // parse arguments
267
  if (typeof arguments[1] == 'string') {
268
    encoding = arguments[1];
269
    cb = arguments[2];
270
  } else {
271
    cb = arguments[1];
284
    // No half-close, sorry
285
    if (this === this.pair.cleartext) this._opposite._done();
272 286
  }
287
}
273 288

  
274 289

  
275
  // Transform strings into buffers.
276
  if (typeof data == 'string') {
277
    data = new Buffer(data, encoding);
290
function onCryptoStreamEnd() {
291
  this._ended = true;
292
  if (this === this.pair.cleartext) {
293
    debug('cleartext.onend');
294
  } else {
295
    debug('encrypted.onend');
278 296
  }
279 297

  
280
  debug((this === this.pair.cleartext ? 'clear' : 'encrypted') + 'In data');
298
  if (this.onend) this.onend();
299
}
281 300

  
282
  this._pending.push(data);
283
  this._pendingCallbacks.push(cb);
284
  this._pendingBytes += data.length;
285 301

  
286
  this.pair._writeCalled = true;
287
  this.pair.cycle();
302
CryptoStream.prototype._write = function write(data, cb) {
303
  assert(this._pending === null);
288 304

  
289
  // In the following cases, write() should return a false,
290
  // then this stream should eventually emit 'drain' event.
291
  //
292
  // 1. There are pending data more than 128k bytes.
293
  // 2. A forward stream shown below is paused.
294
  //    A) EncryptedStream for CleartextStream.write().
295
  //    B) CleartextStream for EncryptedStream.write().
305
  // Black-hole data
306
  if (!this.pair.ssl) return cb(null);
307

  
308
  // When resuming session don't accept any new data.
309
  // And do not put too much data into openssl, before writing it from encrypted
310
  // side.
296 311
  //
297
  if (!this._needDrain) {
298
    if (this._pendingBytes >= 128 * 1024) {
299
      this._needDrain = true;
312
  // TODO(indutny): Remove magic number, use watermark based limits
313
  if (!this._resumingSession &&
314
      (this !== this.pair.cleartext ||
315
      this.pair.encrypted._internallyPendingBytes() < 128 * 1024)) {
316
    // Write current buffer now
317
    var written;
318
    if (this === this.pair.cleartext) {
319
      debug('cleartext.write called with ' + data.length + ' bytes');
320
      written = this.pair.ssl.clearIn(data, 0, data.length);
300 321
    } else {
322
      debug('encrypted.write called with ' + data.length + ' bytes');
323
      written = this.pair.ssl.encIn(data, 0, data.length);
324
    }
325

  
326
    var self = this;
327

  
328
    // Force SSL_read call to cycle some states/data inside OpenSSL
329
    this.pair.cleartext.read(0);
330

  
331
    // Cycle encrypted data
332
    if (this.pair.encrypted._internallyPendingBytes()) {
333
      this.pair.encrypted.read(0);
334
    }
335

  
336
    // Handle and report errors
337
    if (this.pair.ssl && this.pair.ssl.error) {
338
      return cb(this.pair.error());
339
    }
340

  
341
    // Get NPN and Server name when ready
342
    this.pair.maybeInitFinished();
343

  
344
    // Whole buffer was written
345
    if (written === data.length) {
301 346
      if (this === this.pair.cleartext) {
302
        this._needDrain = this.pair.encrypted._paused;
347
        debug('cleartext.write succeed with ' + data.length + ' bytes');
303 348
      } else {
304
        this._needDrain = this.pair.cleartext._paused;
349
        debug('encrypted.write succeed with ' + data.length + ' bytes');
305 350
      }
351

  
352
      return cb(null);
306 353
    }
354
    assert(written === 0 || written === -1);
355
  } else {
356
    debug('cleartext.write queue is full');
357

  
358
    // Force SSL_read call to cycle some states/data inside OpenSSL
359
    this.pair.cleartext.read(0);
360
  }
361

  
362
  // No write has happened
363
  this._pending = data;
364
  this._pendingCallback = cb;
365

  
366
  if (this === this.pair.cleartext) {
367
    debug('cleartext.write queued with ' + data.length + ' bytes');
368
  } else {
369
    debug('encrypted.write queued with ' + data.length + ' bytes');
307 370
  }
308
  return !this._needDrain;
309 371
};
310 372

  
311 373

  
312
CryptoStream.prototype.pause = function() {
313
  debug('paused ' + (this == this.pair.cleartext ? 'cleartext' : 'encrypted'));
314
  this._paused = true;
374
CryptoStream.prototype._writePending = function writePending() {
375
  var data = this._pending,
376
      cb = this._pendingCallback;
377

  
378
  this._pending = null;
379
  this._pendingCallback = null;
380
  this._write(data, cb);
315 381
};
316 382

  
317 383

  
318
CryptoStream.prototype.resume = function() {
319
  debug('resume ' + (this == this.pair.cleartext ? 'cleartext' : 'encrypted'));
320
  this._paused = false;
321
  this.pair.cycle();
384
CryptoStream.prototype._read = function read(size, cb) {
385
  // XXX: EOF?!
386
  if (!this.pair.ssl) return cb(null, null);
387

  
388
  // Wait for session to be resumed
389
  if (this._resumingSession) return cb(null, '');
390

  
391
  var out;
392
  if (this === this.pair.cleartext) {
393
    debug('cleartext.read called with ' + size + ' bytes');
394
    out = this.pair.ssl.clearOut;
395
  } else {
396
    debug('encrypted.read called with ' + size + ' bytes');
397
    out = this.pair.ssl.encOut;
398
  }
399

  
400
  var bytesRead = 0,
401
      start = this._buffer.offset;
402
  do {
403
    var read = this._buffer.use(this.pair.ssl, out, size);
404
    if (read > 0) {
405
      bytesRead += read;
406
      size -= read;
407
    }
408

  
409
    // Handle and report errors
410
    if (this.pair.ssl && this.pair.ssl.error) {
411
      this.pair.error();
412
      break;
413
    }
414

  
415
    // Get NPN and Server name when ready
416
    this.pair.maybeInitFinished();
417
  } while (read > 0 && !this._buffer.isFull && bytesRead < size);
418

  
419
  // Create new buffer if previous was filled up
420
  var pool = this._buffer.pool;
421
  if (this._buffer.isFull) this._buffer.create();
422

  
423
  assert(bytesRead >= 0);
424

  
425
  if (this === this.pair.cleartext) {
426
    debug('cleartext.read succeed with ' + bytesRead + ' bytes');
427
  } else {
428
    debug('encrypted.read succeed with ' + bytesRead + ' bytes');
429
  }
430

  
431
  // Try writing pending data
432
  if (this._pending !== null) this._writePending();
433

  
434
  if (bytesRead === 0) {
435
    // EOF when cleartext has finished and we have nothing to read
436
    if (this._opposite._finished && this._internallyPendingBytes() === 0) {
437
      // Perform graceful shutdown
438
      this._done();
439

  
440
      // No half-open, sorry!
441
      if (this === this.pair.cleartext)
442
        this._opposite._done();
443

  
444
      return cb(null, null);
445
    }
446

  
447
    // Bail out
448
    return cb(null, '');
449
  }
450

  
451
  // Give them requested data
452
  if (this.ondata) {
453
    var self = this;
454
    this.ondata(pool, start, start + bytesRead);
455

  
456
    // Consume data automatically
457
    // simple/test-https-drain fails without it
458
    process.nextTick(function() {
459
      self.read(bytesRead);
460
    });
461
  }
462
  return cb(null, pool.slice(start, start + bytesRead));
322 463
};
323 464

  
324 465

  
......
340 481
  return this.socket ? this.socket.bytesWritten : 0;
341 482
});
342 483

  
343
CryptoStream.prototype.setEncoding = function(encoding) {
344
  var StringDecoder = require('string_decoder').StringDecoder; // lazy load
345
  this._decoder = new StringDecoder(encoding);
346
};
347

  
348 484

  
349 485
// Example:
350 486
// C=US\nST=CA\nL=SF\nO=Joyent\nOU=Node.js\nCN=ca1\nemailAddress=ry@clouds.org
......
409 545
};
410 546

  
411 547

  
412
CryptoStream.prototype.end = function(d) {
413
  if (this.pair._doneFlag) return;
414
  if (!this.writable) return;
415

  
416
  if (d) {
417
    this.write(d);
548
CryptoStream.prototype.end = function(chunk, encoding) {
549
  if (this === this.pair.cleartext) {
550
    debug('cleartext.end');
551
  } else {
552
    debug('encrypted.end');
418 553
  }
419 554

  
420
  this._pending.push(END_OF_FILE);
421
  this._pendingCallbacks.push(null);
422

  
423
  // If this is an encrypted stream then we need to disable further 'data'
424
  // events.
555
  // Write pending data first
556
  if (this._pending !== null) this._writePending();
425 557

  
426 558
  this.writable = false;
427 559

  
428
  this.pair.cycle();
560
  stream.Duplex.prototype.end.call(this, chunk, encoding);
429 561
};
430 562

  
431 563

  
432 564
CryptoStream.prototype.destroySoon = function(err) {
433
  if (this.writable) {
434
    this.end();
565
  if (this === this.pair.cleartext) {
566
    debug('cleartext.destroySoon');
435 567
  } else {
436
    this.destroy();
568
    debug('encrypted.destroySoon');
437 569
  }
570

  
571
  if (this.writable)
572
    this.end();
573

  
574
  if (this._writableState.finishing || this._writableState.finished)
575
    this.destroy();
576
  else
577
    this.once('finish', this.destroy);
438 578
};
439 579

  
440 580

  
441 581
CryptoStream.prototype.destroy = function(err) {
442
  if (this.pair._doneFlag) return;
443
  this.pair.destroy();
582
  if (this._destroyed) return;
583
  this._destroyed = true;
584
  this.readable = this.writable = false;
585

  
586
  // Destroy both ends
587
  if (this === this.pair.cleartext) {
588
    debug('cleartext.destroy');
589
  } else {
590
    debug('encrypted.destroy');
591
  }
592
  this._opposite.destroy();
593

  
594
  var self = this;
595
  process.nextTick(function() {
596
    // Force EOF
597
    self.push(null);
598

  
599
    // Emit 'close' event
600
    self.emit('close', err ? true : false);
601
  });
444 602
};
445 603

  
446 604

  
447 605
CryptoStream.prototype._done = function() {
448 606
  this._doneFlag = true;
449 607

  
608
  if (this === this.pair.encrypted && !this.pair._secureEstablished)
609
    return this.pair.error();
610

  
450 611
  if (this.pair.cleartext._doneFlag &&
451 612
      this.pair.encrypted._doneFlag &&
452 613
      !this.pair._doneFlag) {
453 614
    // If both streams are done:
454
    if (!this.pair._secureEstablished) {
455
      this.pair.error();
456
    } else {
457
      this.pair.destroy();
458
    }
615
    this.pair.destroy();
459 616
  }
460 617
};
461 618

  
......
478 635
});
479 636

  
480 637

  
481
// Move decrypted, clear data out into the application.
482
// From the user's perspective this occurs as a 'data' event
483
// on the pair.cleartext.
484
// also
485
// Move encrypted data to the stream. From the user's perspective this
486
// occurs as a 'data' event on the pair.encrypted. Usually the application
487
// will have some code which pipes the stream to a socket:
488
//
489
//   pair.encrypted.on('data', function (d) {
490
//     socket.write(d);
491
//   });
492
//
493
CryptoStream.prototype._push = function() {
494
  if (this == this.pair.encrypted && !this.writable) {
495
    // If the encrypted side got EOF, we do not attempt
496
    // to write out data anymore.
497
    return;
498
  }
499

  
500
  while (!this._paused) {
501
    var chunkBytes = 0,
502
        bytesRead = 0,
503
        start = this._buffer.offset;
504

  
505
    do {
506
      chunkBytes = this._buffer.use(this, this._pusher);
507
      if (chunkBytes > 0) bytesRead += chunkBytes;
508

  
509
      if (this.pair.ssl && this.pair.ssl.error) {
510
        this.pair.error();
511
        return;
512
      }
513

  
514
      this.pair.maybeInitFinished();
515

  
516
    } while (chunkBytes > 0 && !this._buffer.isFull);
517

  
518
    var pool = this._buffer.pool;
519

  
520
    // Create new buffer if previous was filled up
521
    if (this._buffer.isFull) this._buffer.create();
522

  
523
    assert(bytesRead >= 0);
524

  
525
    // Bail out if we didn't read any data.
526
    if (bytesRead == 0) {
527
      if (this._internallyPendingBytes() == 0 && this._destroyAfterPush) {
528
        this._done();
529
      }
530
      return;
531
    }
532

  
533
    var chunk = pool.slice(start, start + bytesRead);
534

  
535
    if (this === this.pair.cleartext) {
536
      debug('cleartext emit "data" with ' + bytesRead + ' bytes');
537
    } else {
538
      debug('encrypted emit "data" with ' + bytesRead + ' bytes');
539
    }
540

  
541
    if (this._decoder) {
542
      var string = this._decoder.write(chunk);
543
      if (string.length) this.emit('data', string);
544
    } else {
545
      this.emit('data', chunk);
546
    }
547

  
548
    // Optimization: emit the original buffer with end points
549
    if (this.ondata) this.ondata(pool, start, start + bytesRead);
550
  }
551
};
552

  
553

  
554
// Push in any clear data coming from the application.
555
// This arrives via some code like this:
556
//
557
//   pair.cleartext.write("hello world");
558
//
559
// also
560
//
561
// Push in incoming encrypted data from the socket.
562
// This arrives via some code like this:
563
//
564
//   socket.on('data', function (d) {
565
//     pair.encrypted.write(d)
566
//   });
567
//
568
CryptoStream.prototype._pull = function() {
569
  var havePending = this._pending.length > 0;
570

  
571
  assert(havePending || this._pendingBytes == 0);
572

  
573
  while (this._pending.length > 0) {
574
    if (!this.pair.ssl) break;
575

  
576
    var tmp = this._pending.shift();
577
    var cb = this._pendingCallbacks.shift();
578

  
579
    assert(this._pending.length === this._pendingCallbacks.length);
580

  
581
    if (tmp === END_OF_FILE) {
582
      // Sending EOF
583
      if (this === this.pair.encrypted) {
584
        debug('end encrypted ' + this.pair.fd);
585
        this.pair.cleartext._destroyAfterPush = true;
586
      } else {
587
        // CleartextStream
588
        assert(this === this.pair.cleartext);
589
        debug('end cleartext');
590

  
591
        this.pair.ssl.shutdown();
592

  
593
        // TODO check if we get EAGAIN From shutdown, would have to do it
594
        // again. should unshift END_OF_FILE back onto pending and wait for
595
        // next cycle.
596

  
597
        this.pair.encrypted._destroyAfterPush = true;
598
      }
599
      this.pair.cycle();
600
      this._done();
601
      return;
602
    }
603

  
604
    if (tmp.length == 0) continue;
605

  
606
    var rv = this._puller(tmp);
607

  
608
    if (this.pair.ssl && this.pair.ssl.error) {
609
      this.pair.error();
610
      return;
611
    }
612

  
613
    this.pair.maybeInitFinished();
614

  
615
    if (rv === 0 || rv < 0) {
616
      this._pending.unshift(tmp);
617
      this._pendingCallbacks.unshift(cb);
618
      break;
619
    }
620

  
621
    this._pendingBytes -= tmp.length;
622
    assert(this._pendingBytes >= 0);
623

  
624
    if (cb) cb();
625

  
626
    assert(rv === tmp.length);
627
  }
628

  
629
  // If pending data has cleared, 'drain' event should be emitted
630
  // after write() returns a false.
631
  // Except when a forward stream shown below is paused.
632
  //   A) EncryptedStream for CleartextStream._pull().
633
  //   B) CleartextStream for EncryptedStream._pull().
634
  //
635
  if (this._needDrain && this._pending.length === 0) {
636
    var paused;
637
    if (this === this.pair.cleartext) {
638
      paused = this.pair.encrypted._paused;
639
    } else {
640
      paused = this.pair.cleartext._paused;
641
    }
642
    if (!paused) {
643
      debug('drain ' + (this === this.pair.cleartext ? 'clear' : 'encrypted'));
644
      var self = this;
645
      process.nextTick(function() {
646
        self.emit('drain');
647
      });
648
      this._needDrain = false;
649
      if (this.__destroyOnDrain) this.end();
650
    }
651
  }
652
};
653

  
654

  
655
function CleartextStream(pair) {
656
  CryptoStream.call(this, pair);
638
function CleartextStream(pair, options) {
639
  CryptoStream.call(this, pair, options);
657 640
}
658 641
util.inherits(CleartextStream, CryptoStream);
659 642

  
......
667 650
};
668 651

  
669 652

  
670
CleartextStream.prototype._puller = function(b) {
671
  debug('clearIn ' + b.length + ' bytes');
672
  return this.pair.ssl.clearIn(b, 0, b.length);
673
};
674

  
675

  
676
CleartextStream.prototype._pusher = function(pool, offset, length) {
677
  debug('reading from clearOut');
678
  if (!this.pair.ssl) return -1;
679
  return this.pair.ssl.clearOut(pool, offset, length);
680
};
681

  
682 653
CleartextStream.prototype.address = function() {
683 654
  return this.socket && this.socket.address();
684 655
};
685 656

  
657

  
686 658
CleartextStream.prototype.__defineGetter__('remoteAddress', function() {
687 659
  return this.socket && this.socket.remoteAddress;
688 660
});
......
692 664
  return this.socket && this.socket.remotePort;
693 665
});
694 666

  
695
function EncryptedStream(pair) {
696
  CryptoStream.call(this, pair);
667
function EncryptedStream(pair, options) {
668
  CryptoStream.call(this, pair, options);
697 669
}
698 670
util.inherits(EncryptedStream, CryptoStream);
699 671

  
......
707 679
};
708 680

  
709 681

  
710
EncryptedStream.prototype._puller = function(b) {
711
  debug('writing from encIn');
712
  return this.pair.ssl.encIn(b, 0, b.length);
713
};
714

  
715

  
716
EncryptedStream.prototype._pusher = function(pool, offset, length) {
717
  debug('reading from encOut');
718
  if (!this.pair.ssl) return -1;
719
  return this.pair.ssl.encOut(pool, offset, length);
720
};
721

  
722

  
723 682
function onhandshakestart() {
724 683
  debug('onhandshakestart');
725 684

  
......
754 713
  debug('onhandshakedone');
755 714
}
756 715

  
716

  
757 717
function onclienthello(hello) {
758 718
  var self = this,
759 719
      once = false;
760 720

  
761
  this.encrypted.pause();
762
  this.cleartext.pause();
721
  this._resumingSession = true;
763 722
  function callback(err, session) {
764 723
    if (once) return;
765 724
    once = true;
......
768 727

  
769 728
    self.ssl.loadSession(session);
770 729

  
771
    self.encrypted.resume();
772
    self.cleartext.resume();
730
    // Cycle data
731
    self._resumingSession = false;
732
    self.cleartext.read(0);
733
    self.encrypted.read(0);
773 734
  }
774 735

  
775 736
  if (hello.sessionId.length <= 0 ||
......
812 773
  this._encWriteState = true;
813 774
  this._clearWriteState = true;
814 775
  this._doneFlag = false;
776
  this._destroying = false;
815 777

  
816 778
  if (!credentials) {
817 779
    this.credentials = crypto.createCredentials();
......
856 818
  }
857 819

  
858 820
  /* Acts as a r/w stream to the cleartext side of the stream. */
859
  this.cleartext = new CleartextStream(this);
821
  this.cleartext = new CleartextStream(this, options.cleartext);
860 822

  
861 823
  /* Acts as a r/w stream to the encrypted side of the stream. */
862
  this.encrypted = new EncryptedStream(this);
824
  this.encrypted = new EncryptedStream(this, options.encrypted);
825

  
826
  /* Let streams know about each other */
827
  this.cleartext._opposite = this.encrypted;
828
  this.encrypted._opposite = this.cleartext;
863 829

  
864 830
  process.nextTick(function() {
865 831
    /* The Connection may be destroyed by an abort call */
866 832
    if (self.ssl) {
867 833
      self.ssl.start();
868 834
    }
869
    self.cycle();
870 835
  });
871 836
}
872 837

  
......
885 850
};
886 851

  
887 852

  
888

  
889

  
890
/* Attempt to cycle OpenSSLs buffers in various directions.
891
 *
892
 * An SSL Connection can be viewed as four separate piplines,
893
 * interacting with one has no connection to the behavoir of
894
 * any of the other 3 -- This might not sound reasonable,
895
 * but consider things like mid-stream renegotiation of
896
 * the ciphers.
897
 *
898
 * The four pipelines, using terminology of the client (server is just
899
 * reversed):
900
 *  (1) Encrypted Output stream (Writing encrypted data to peer)
901
 *  (2) Encrypted Input stream (Reading encrypted data from peer)
902
 *  (3) Cleartext Output stream (Decrypted content from the peer)
903
 *  (4) Cleartext Input stream (Cleartext content to send to the peer)
904
 *
905
 * This function attempts to pull any available data out of the Cleartext
906
 * input stream (4), and the Encrypted input stream (2).  Then it pushes any
907
 * data available from the cleartext output stream (3), and finally from the
908
 * Encrypted output stream (1)
909
 *
910
 * It is called whenever we do something with OpenSSL -- post reciving
911
 * content, trying to flush, trying to change ciphers, or shutting down the
912
 * connection.
913
 *
914
 * Because it is also called everywhere, we also check if the connection has
915
 * completed negotiation and emit 'secure' from here if it has.
916
 */
917
SecurePair.prototype.cycle = function(depth) {
918
  if (this._doneFlag) return;
919

  
920
  depth = depth ? depth : 0;
921

  
922
  if (depth == 0) this._writeCalled = false;
923

  
924
  var established = this._secureEstablished;
925

  
926
  if (!this.cycleEncryptedPullLock) {
927
    this.cycleEncryptedPullLock = true;
928
    debug('encrypted._pull');
929
    this.encrypted._pull();
930
    this.cycleEncryptedPullLock = false;
931
  }
932

  
933
  if (!this.cycleCleartextPullLock) {
934
    this.cycleCleartextPullLock = true;
935
    debug('cleartext._pull');
936
    this.cleartext._pull();
937
    this.cycleCleartextPullLock = false;
938
  }
939

  
940
  if (!this.cycleCleartextPushLock) {
941
    this.cycleCleartextPushLock = true;
942
    debug('cleartext._push');
943
    this.cleartext._push();
944
    this.cycleCleartextPushLock = false;
945
  }
946

  
947
  if (!this.cycleEncryptedPushLock) {
948
    this.cycleEncryptedPushLock = true;
949
    debug('encrypted._push');
950
    this.encrypted._push();
951
    this.cycleEncryptedPushLock = false;
952
  }
953

  
954
  if ((!established && this._secureEstablished) ||
955
      (depth == 0 && this._writeCalled)) {
956
    // If we were not established but now we are, let's cycle again.
957
    // Or if there is some data to write...
958
    this.cycle(depth + 1);
959
  }
960
};
961

  
962

  
963 853
SecurePair.prototype.maybeInitFinished = function() {
964 854
  if (this.ssl && !this._secureEstablished && this.ssl.isInitFinished()) {
965 855
    if (process.features.tls_npn) {
......
978 868

  
979 869

  
980 870
SecurePair.prototype.destroy = function() {
981
  var self = this;
871
  if (this._destroying) return;
982 872

  
983 873
  if (!this._doneFlag) {
874
    debug('SecurePair.destroy');
875
    this._destroying = true;
876

  
877
    // SecurePair should be destroyed only after it's streams
878
    this.cleartext.destroy();
879
    this.encrypted.destroy();
880

  
984 881
    this._doneFlag = true;
985 882
    this.ssl.error = null;
986 883
    this.ssl.close();
987 884
    this.ssl = null;
988

  
989
    self.encrypted.writable = self.encrypted.readable = false;
990
    self.cleartext.writable = self.cleartext.readable = false;
991

  
992
    process.nextTick(function() {
993
      if (self.cleartext._decoder) {
994
        var ret = self.cleartext._decoder.end();
995
        if (ret)
996
          self.cleartext.emit('data', ret);
997
      }
998
      self.cleartext.emit('end');
999
      self.encrypted.emit('close');
1000
      self.cleartext.emit('close');
1001
    });
1002 885
  }
1003 886
};
1004 887

  
......
1012 895
    }
1013 896
    this.destroy();
1014 897
    this.emit('error', error);
898
    return error;
1015 899
  } else {
1016 900
    var err = this.ssl.error;
1017 901
    this.ssl.error = null;
......
1024 908
    } else {
1025 909
      this.cleartext.emit('error', err);
1026 910
    }
911

  
912
    return err;
1027 913
  }
1028 914
};
1029 915

  
......
1155 1041
                              {
1156 1042
                                server: self,
1157 1043
                                NPNProtocols: self.NPNProtocols,
1158
                                SNICallback: self.SNICallback
1044
                                SNICallback: self.SNICallback,
1045

  
1046
                                // Stream options
1047
                                cleartext: self._cleartext,
1048
                                encrypted: self._encrypted
1159 1049
                              });
1160 1050

  
1161 1051
    var cleartext = pipe(pair, socket);
......
1254 1144
                                  .update(process.argv.join(' '))
1255 1145
                                  .digest('hex');
1256 1146
  }
1147
  if (options.cleartext) this.cleartext = options.cleartext;
1148
  if (options.encrypted) this.encrypted = options.encrypted;
1257 1149
};
1258 1150

  
1259 1151
// SNI Contexts High-Level API
......
1331 1223
                            options.rejectUnauthorized === true ? true : false,
1332 1224
                            {
1333 1225
                              NPNProtocols: this.NPNProtocols,
1334
                              servername: hostname
1226
                              servername: hostname,
1227
                              cleartext: options.cleartext,
1228
                              encrypted: options.encrypted
1335 1229
                            });
1336 1230

  
1337 1231
  if (options.session) {

Also available in: Unified diff