Browse Source

Merge remote-tracking branch 'ry/v0.6' into v0.6-merge

Conflicts:
	AUTHORS
	ChangeLog
	Makefile
	doc/about/index.html
	doc/api/tls.markdown
	doc/community/index.html
	doc/index.html
	doc/logos/index.html
	doc/template.html
	lib/http.js
	lib/tls.js
	src/node_version.h
	src/platform_win32.cc
	test/simple/test-tls-connect-given-socket.js
v0.9.1-release
isaacs 13 years ago
parent
commit
31721da4b1
  1. 3
      AUTHORS
  2. 42
      ChangeLog
  3. 4
      deps/uv/src/unix/tty.c
  4. 3
      deps/uv/src/win/error.c
  5. 2
      doc/about/index.html
  6. 97
      doc/api/tls.markdown
  7. 6
      doc/community/index.html
  8. 6
      doc/index.html
  9. 23
      doc/logos/index.html
  10. 2
      lib/dgram.js
  11. 2
      lib/http.js
  12. 8
      lib/path.js
  13. 7
      lib/repl.js
  14. 80
      lib/tls.js
  15. 1
      node.gyp
  16. 241
      src/node.cc
  17. 16
      src/node_crypto.cc
  18. 2
      src/node_crypto.h
  19. 41
      src/node_main.cc
  20. 5
      src/pipe_wrap.cc
  21. 5
      src/tcp_wrap.cc
  22. 100
      test/pummel/test-tls-ci-reneg-attack.js
  23. 77
      test/simple/test-cluster-bind-twice.js
  24. 35
      test/simple/test-dgram-close.js
  25. 9
      test/simple/test-http-server-multiheaders.js
  26. 9
      test/simple/test-repl-tab-complete.js
  27. 156
      test/simple/test-tls-over-http-tunnel.js
  28. 41
      vcbuild.bat

3
AUTHORS

@ -263,3 +263,6 @@ Dan VerWeire <dverweire@gmail.com>
Matthew Fitzsimmons <matt@joyent.com> Matthew Fitzsimmons <matt@joyent.com>
Philip Tellis <philip.tellis@gmail.com> Philip Tellis <philip.tellis@gmail.com>
Christopher Jeffrey <chjjeffrey@gmail.com> Christopher Jeffrey <chjjeffrey@gmail.com>
Paddy Byers <paddy.byers@gmail.com>
Seth Fitzsimmons <seth@mojodna.net>
Einar Otto Stangvik <einaros@gmail.com>

42
ChangeLog

@ -97,7 +97,47 @@
* Bug fixes * Bug fixes
2012.02.02, Version 0.6.10 (stable) 2012.02.17 Version 0.6.11 (stable), 1eb1fe32250fc88cb5b0a97cddf3e02be02e3f4a
* http: allow multiple WebSocket RFC6455 headers (Einar Otto Stangvik)
* http: allow multiple WWW-Authenticate headers (Ben Noordhuis)
* windows: support unicode argv and environment variables (Bert Belder)
* tls: mitigate session renegotiation attacks (Ben Noordhuis)
* tcp, pipe: don't assert on uv_accept() errors (Ben Noordhuis)
* tls: Allow establishing secure connection on the existing socket (koichik)
* dgram: handle close of dgram socket before DNS lookup completes (Seth Fitzsimmons)
* windows: Support half-duplex pipes (Igor Zinkovsky)
* build: disable omit-frame-pointer on solaris systems (Dave Pacheco)
* debugger: fix --debug-brk (Ben Noordhuis)
* net: fix large file downloads failing (koichik)
* fs: fix ReadStream failure to read from existing fd (Christopher Jeffrey)
* net: destroy socket on DNS error (Stefan Rusu)
* dtrace: add missing translator (Dave Pacheco)
* unix: don't flush tty on switch to raw mode (Ben Noordhuis)
* windows: reset brightness when reverting to default text color (Bert Belder)
* npm: update to 1.1.1
- Update which, fstream, mkdirp, request, and rimraf
- Fix #2123 Set path properly for lifecycle scripts on windows
- Mark the root as seen, so we don't recurse into it. Fixes #1838. (Martin Cooper)
2012.02.02, Version 0.6.10 (stable), 051908e023f87894fa68f5b64d0b99a19a7db01e
* Update V8 to 3.6.6.20 * Update V8 to 3.6.6.20

4
deps/uv/src/unix/tty.c

@ -76,8 +76,8 @@ int uv_tty_set_mode(uv_tty_t* tty, int mode) {
raw.c_cc[VMIN] = 1; raw.c_cc[VMIN] = 1;
raw.c_cc[VTIME] = 0; raw.c_cc[VTIME] = 0;
/* Put terminal in raw mode after flushing */ /* Put terminal in raw mode after draining */
if (tcsetattr(fd, TCSAFLUSH, &raw)) { if (tcsetattr(fd, TCSADRAIN, &raw)) {
goto fatal; goto fatal;
} }

3
deps/uv/src/win/error.c

@ -108,6 +108,9 @@ uv_err_code uv_translate_sys_error(int sys_errno) {
case ERROR_INVALID_PARAMETER: return UV_EINVAL; case ERROR_INVALID_PARAMETER: return UV_EINVAL;
case ERROR_NO_UNICODE_TRANSLATION: return UV_ECHARSET; case ERROR_NO_UNICODE_TRANSLATION: return UV_ECHARSET;
case ERROR_BROKEN_PIPE: return UV_EOF; case ERROR_BROKEN_PIPE: return UV_EOF;
case ERROR_BAD_PIPE: return UV_EPIPE;
case ERROR_NO_DATA: return UV_EPIPE;
case ERROR_PIPE_NOT_CONNECTED: return UV_EPIPE;
case ERROR_PIPE_BUSY: return UV_EBUSY; case ERROR_PIPE_BUSY: return UV_EBUSY;
case ERROR_SEM_TIMEOUT: return UV_ETIMEDOUT; case ERROR_SEM_TIMEOUT: return UV_ETIMEDOUT;
case WSAETIMEDOUT: return UV_ETIMEDOUT; case WSAETIMEDOUT: return UV_ETIMEDOUT;

2
doc/about/index.html

@ -1,5 +1,5 @@
<!doctype html> <!doctype html>
<html> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<style> <style>

97
doc/api/tls.markdown

@ -26,8 +26,40 @@ Alternatively you can send the CSR to a Certificate Authority for signing.
(TODO: docs on creating a CA, for now interested users should just look at (TODO: docs on creating a CA, for now interested users should just look at
`test/fixtures/keys/Makefile` in the Node source code) `test/fixtures/keys/Makefile` in the Node source code)
### Client-initiated renegotiation attack mitigation
#### tls.createServer(options, [secureConnectionListener]) The TLS protocol lets the client renegotiate certain aspects of the TLS session.
Unfortunately, session renegotiation requires a disproportional amount of
server-side resources, which makes it a potential vector for denial-of-service
attacks.
To mitigate this, renegotiations are limited to three times every 10 minutes. An
error is emitted on the [CleartextStream](#tls.CleartextStream) instance when
the threshold is exceeded. The limits are configurable:
- `tls.CLIENT_RENEG_LIMIT`: renegotiation limit, default is 3.
- `tls.CLIENT_RENEG_WINDOW`: renegotiation window in seconds, default is
10 minutes.
Don't change the defaults unless you know what you are doing.
To test your server, connect to it with `openssl s_client -connect address:port`
and tap `R<CR>` (that's the letter `R` followed by a carriage return) a few
times.
### NPN and SNI
NPN (Next Protocol Negotiation) and SNI (Server Name Indication) are TLS
handshake extensions allowing you:
* NPN - to use one TLS server for multiple protocols (HTTP, SPDY)
* SNI - to use one TLS server for multiple hostnames with different SSL
certificates.
## tls.createServer(options, [secureConnectionListener])
Creates a new [tls.Server](#tls.Server). Creates a new [tls.Server](#tls.Server).
The `connectionListener` argument is automatically set as a listener for the The `connectionListener` argument is automatically set as a listener for the
@ -144,6 +176,11 @@ Creates a new client connection to the given `port` and `host` (old API) or
- `servername`: Servername for SNI (Server Name Indication) TLS extension. - `servername`: Servername for SNI (Server Name Indication) TLS extension.
- `socket`: Establish secure connection on a given socket rather than
creating a new socket. If this option is specified, `host` and `port`
are ignored. This is intended FOR INTERNAL USE ONLY. As with all
undocumented APIs in Node, they should not be used.
The `secureConnectListener` parameter will be added as a listener for the The `secureConnectListener` parameter will be added as a listener for the
['secureConnect'](#event_secureConnect_) event. ['secureConnect'](#event_secureConnect_) event.
@ -178,27 +215,7 @@ Here is an example of a client of echo server as described previously:
}); });
### STARTTLS ## tls.createSecurePair([credentials], [isServer], [requestCert], [rejectUnauthorized])
In the v0.4 branch no function exists for starting a TLS session on an
already existing TCP connection. This is possible it just requires a bit of
work. The technique is to use `tls.createSecurePair()` which returns two
streams: an encrypted stream and a cleartext stream. The encrypted stream is
then piped to the socket, the cleartext stream is what the user interacts with
thereafter.
[Here is some code that does it.](http://gist.github.com/848444)
### NPN and SNI
NPN (Next Protocol Negotiation) and SNI (Server Name Indication) are TLS
handshake extensions allowing you:
* NPN - to use one TLS server for multiple protocols (HTTP, SPDY)
* SNI - to use one TLS server for multiple hostnames with different SSL
certificates.
### pair = tls.createSecurePair([credentials], [isServer], [requestCert], [rejectUnauthorized])
Creates a new secure pair object with two streams, one of which reads/writes Creates a new secure pair object with two streams, one of which reads/writes
encrypted data, and one reads/writes cleartext data. encrypted data, and one reads/writes cleartext data.
@ -220,7 +237,7 @@ and the cleartext one is used as a replacement for the initial encrypted stream.
`tls.createSecurePair()` returns a SecurePair object with `tls.createSecurePair()` returns a SecurePair object with
[cleartext](#tls.CleartextStream) and `encrypted` stream properties. [cleartext](#tls.CleartextStream) and `encrypted` stream properties.
#### Event: 'secure' ### Event: 'secure'
The event is emitted from the SecurePair once the pair has successfully The event is emitted from the SecurePair once the pair has successfully
established a secure connection. established a secure connection.
@ -229,13 +246,13 @@ Similarly to the checking for the server 'secureConnection' event,
pair.cleartext.authorized should be checked to confirm whether the certificate pair.cleartext.authorized should be checked to confirm whether the certificate
used properly authorized. used properly authorized.
### tls.Server ## tls.Server
This class is a subclass of `net.Server` and has the same methods on it. This class is a subclass of `net.Server` and has the same methods on it.
Instead of accepting just raw TCP connections, this accepts encrypted Instead of accepting just raw TCP connections, this accepts encrypted
connections using TLS or SSL. connections using TLS or SSL.
#### Event: 'secureConnection' ### Event: 'secureConnection'
`function (cleartextStream) {}` `function (cleartextStream) {}`
@ -255,7 +272,7 @@ server, you unauthorized connections may be accepted.
SNI. SNI.
#### Event: 'clientError' ### Event: 'clientError'
`function (exception) { }` `function (exception) { }`
@ -263,7 +280,7 @@ When a client connection emits an 'error' event before secure connection is
established - it will be forwarded here. established - it will be forwarded here.
#### server.listen(port, [host], [callback]) ### server.listen(port, [host], [callback])
Begin accepting connections on the specified `port` and `host`. If the Begin accepting connections on the specified `port` and `host`. If the
`host` is omitted, the server will accept connections directed to any `host` is omitted, the server will accept connections directed to any
@ -275,35 +292,35 @@ when the server has been bound.
See `net.Server` for more information. See `net.Server` for more information.
#### server.close() ### server.close()
Stops the server from accepting new connections. This function is Stops the server from accepting new connections. This function is
asynchronous, the server is finally closed when the server emits a `'close'` asynchronous, the server is finally closed when the server emits a `'close'`
event. event.
#### server.address() ### server.address()
Returns the bound address and port of the server as reported by the operating Returns the bound address and port of the server as reported by the operating
system. system.
See [net.Server.address()](net.html#server.address) for more information. See [net.Server.address()](net.html#server.address) for more information.
#### server.addContext(hostname, credentials) ### server.addContext(hostname, credentials)
Add secure context that will be used if client request's SNI hostname is Add secure context that will be used if client request's SNI hostname is
matching passed `hostname` (wildcards can be used). `credentials` can contain matching passed `hostname` (wildcards can be used). `credentials` can contain
`key`, `cert` and `ca`. `key`, `cert` and `ca`.
#### server.maxConnections ### server.maxConnections
Set this property to reject connections when the server's connection count Set this property to reject connections when the server's connection count
gets high. gets high.
#### server.connections ### server.connections
The number of concurrent connections on the server. The number of concurrent connections on the server.
### tls.CleartextStream ## tls.CleartextStream
This is a stream on top of the *Encrypted* stream that makes it possible to This is a stream on top of the *Encrypted* stream that makes it possible to
read/write an encrypted data as a cleartext data. read/write an encrypted data as a cleartext data.
@ -311,7 +328,7 @@ read/write an encrypted data as a cleartext data.
This instance implements a duplex [Stream](streams.html#streams) interfaces. This instance implements a duplex [Stream](streams.html#streams) interfaces.
It has all the common stream methods and events. It has all the common stream methods and events.
#### Event: 'secureConnect' ### Event: 'secureConnect'
`function () {}` `function () {}`
@ -323,17 +340,17 @@ If `cleartextStream.authorized === false` then the error can be found in
`cleartextStream.authorizationError`. Also if NPN was used - you can check `cleartextStream.authorizationError`. Also if NPN was used - you can check
`cleartextStream.npnProtocol` for negotiated protocol. `cleartextStream.npnProtocol` for negotiated protocol.
#### cleartextStream.authorized ### cleartextStream.authorized
A boolean that is `true` if the peer certificate was signed by one of the A boolean that is `true` if the peer certificate was signed by one of the
specified CAs, otherwise `false` specified CAs, otherwise `false`
#### cleartextStream.authorizationError ### cleartextStream.authorizationError
The reason why the peer's certificate has not been verified. This property The reason why the peer's certificate has not been verified. This property
becomes available only when `cleartextStream.authorized === false`. becomes available only when `cleartextStream.authorized === false`.
#### cleartextStream.getPeerCertificate() ### cleartextStream.getPeerCertificate()
Returns an object representing the peer's certificate. The returned object has Returns an object representing the peer's certificate. The returned object has
some properties corresponding to the field of the certificate. some properties corresponding to the field of the certificate.
@ -361,17 +378,17 @@ Example:
If the peer does not provide a certificate, it returns `null` or an empty If the peer does not provide a certificate, it returns `null` or an empty
object. object.
#### cleartextStream.address() ### cleartextStream.address()
Returns the bound address and port of the underlying socket as reported by the Returns the bound address and port of the underlying socket as reported by the
operating system. Returns an object with two properties, e.g. operating system. Returns an object with two properties, e.g.
`{"address":"192.168.57.1", "port":62053}` `{"address":"192.168.57.1", "port":62053}`
#### cleartextStream.remoteAddress ### cleartextStream.remoteAddress
The string representation of the remote IP address. For example, The string representation of the remote IP address. For example,
`'74.125.127.100'` or `'2001:4860:a005::68'`. `'74.125.127.100'` or `'2001:4860:a005::68'`.
#### cleartextStream.remotePort ### cleartextStream.remotePort
The numeric representation of the remote port. For example, `443`. The numeric representation of the remote port. For example, `443`.

6
doc/community/index.html

@ -1,5 +1,5 @@
<!doctype html> <!doctype html>
<html> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<style> <style>
@ -12,8 +12,8 @@
<script src="../sh_javascript.min.js"></script> <script src="../sh_javascript.min.js"></script>
<link type="image/x-icon" rel="icon" href="../favicon.ico"> <link type="image/x-icon" rel="icon" href="../favicon.ico">
<link type="image/x-icon" rel="shortcut icon" href="../favicon.ico"> <link type="image/x-icon" rel="shortcut icon" href="../favicon.ico">
<link type="text/css" rel="stylesheet" href="../pipe.css"> <link rel="stylesheet" href="../pipe.css">
<link type="text/css" rel="stylesheet" href="../sh_vim-dark.css"> <link rel="stylesheet" href="../sh_vim-dark.css">
<link rel="alternate" <link rel="alternate"
type="application/rss+xml" type="application/rss+xml"
title="node blog" title="node blog"

6
doc/index.html

@ -157,7 +157,7 @@ var http = require('http');
http.createServer(function (req, res) { http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'}); res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World\n'); res.end('Hello World\n');
}).listen(1337, "127.0.0.1"); }).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');</pre> console.log('Server running at http://127.0.0.1:1337/');</pre>
<p>To run the server, put the code into a file <code>example.js</code> and execute it with the <code>node</code> program:</p> <p>To run the server, put the code into a file <code>example.js</code> and execute it with the <code>node</code> program:</p>
@ -171,11 +171,11 @@ Server running at http://127.0.0.1:1337/</pre>
var net = require('net'); var net = require('net');
var server = net.createServer(function (socket) { var server = net.createServer(function (socket) {
socket.write("Echo server\r\n"); socket.write('Echo server\r\n');
socket.pipe(socket); socket.pipe(socket);
}); });
server.listen(1337, "127.0.0.1");</pre> server.listen(1337, '127.0.0.1');</pre>
<!-- <p>Ready to dig in? <a href="">Download the latest version</a> of node.js or learn how other organizations are <a href="">using the technology</a>.</p> --> <!-- <p>Ready to dig in? <a href="">Download the latest version</a> of node.js or learn how other organizations are <a href="">using the technology</a>.</p> -->
</div> </div>

23
doc/logos/index.html

@ -1,24 +1,21 @@
<!doctype html> <!doctype html>
<html> <html lang="en">
<head> <head>
<style type="text/css"> <meta charset="utf-8">
<style>
ul { ul {
padding: 0; padding: 0;
margin: 0; margin: 0;
} }
</style> </style>
<script type='text/javascript' src='http://ajax.googleapis.com/ajax/libs/jquery/1.5/jquery.min.js?ver=3.1.3'></script>
<script type="text/javascript" src="../sh_main.js"></script>
<script type="text/javascript" src="../sh_javascript.min.js"></script>
<link type="image/x-icon" rel="icon" href="../favicon.ico"> <link type="image/x-icon" rel="icon" href="../favicon.ico">
<link type="image/x-icon" rel="shortcut icon" href="../favicon.ico"> <link type="image/x-icon" rel="shortcut icon" href="../favicon.ico">
<link type="text/css" rel="stylesheet" href="../pipe.css"> <link rel="stylesheet" href="../pipe.css">
<link type="text/css" rel="stylesheet" href="../sh_vim-dark.css"> <link rel="stylesheet" href="../sh_vim-dark.css">
<link rel="alternate" <link rel="alternate"
type="application/rss+xml" type="application/rss+xml"
title="node blog" title="node blog"
href="http://feeds.feedburner.com/nodejs/123123123"> href="http://feeds.feedburner.com/nodejs/123123123">
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>node.js</title> <title>node.js</title>
</head> </head>
<body class="int" id="logos"> <body class="int" id="logos">
@ -88,10 +85,7 @@
<p>Copyright <a href="http://joyent.com">Joyent, Inc</a>., Node.js is a <a href="/trademark-policy.pdf">trademark of Joyent, Inc</a>., <a href="https://raw.github.com/joyent/node/v0.7.4/LICENSE">View License</a></p> <p>Copyright <a href="http://joyent.com">Joyent, Inc</a>., Node.js is a <a href="/trademark-policy.pdf">trademark of Joyent, Inc</a>., <a href="https://raw.github.com/joyent/node/v0.7.4/LICENSE">View License</a></p>
</div> </div>
<script>
<script type="text/javascript">
var gaJsHost = (("https:" == document.location.protocol) ? var gaJsHost = (("https:" == document.location.protocol) ?
"https://ssl." : "http://www."); "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E")); document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
@ -102,6 +96,5 @@
pageTracker._trackPageview(); pageTracker._trackPageview();
} catch(err) {} } catch(err) {}
</script> </script>
</body>
</html>
</body></html>

2
lib/dgram.js

@ -175,7 +175,7 @@ Socket.prototype.send = function(buffer,
if (callback) callback(err); if (callback) callback(err);
self.emit('error', err); self.emit('error', err);
} }
else { else if (self._handle) {
var req = self._handle.send(buffer, offset, length, port, ip); var req = self._handle.send(buffer, offset, length, port, ip);
if (req) { if (req) {
req.oncomplete = afterSend; req.oncomplete = afterSend;

2
lib/http.js

@ -359,6 +359,8 @@ IncomingMessage.prototype._addHeaderLine = function(field, value) {
case 'pragma': case 'pragma':
case 'link': case 'link':
case 'www-authenticate': case 'www-authenticate':
case 'sec-websocket-extensions':
case 'sec-websocket-protocol':
if (field in dest) { if (field in dest) {
dest[field] += ', ' + value; dest[field] += ', ' + value;
} else { } else {

8
lib/path.js

@ -97,7 +97,13 @@ if (isWindows) {
// directories. If we've resolved a drive letter but not yet an // directories. If we've resolved a drive letter but not yet an
// absolute path, get cwd for that drive. We're sure the device is not // absolute path, get cwd for that drive. We're sure the device is not
// an unc path at this points, because unc paths are always absolute. // an unc path at this points, because unc paths are always absolute.
path = process._cwdForDrive(resolvedDevice[0]); path = process.env['=' + resolvedDevice];
// Verify that a drive-local cwd was found and that it actually points
// to our drive. If not, default to the drive's root.
if (!path || path.slice(0, 3).toLowerCase() !==
resolvedDevice.toLowerCase() + '\\') {
path = resolvedDevice + '\\';
}
} }
// Skip empty and invalid entries // Skip empty and invalid entries

7
lib/repl.js

@ -525,8 +525,13 @@ REPLServer.prototype.complete = function(line, callback) {
} }
// works for non-objects // works for non-objects
try { try {
var p = Object.getPrototypeOf(obj);
var sentinel = 5; var sentinel = 5;
var p;
if (typeof obj == 'object') {
p = Object.getPrototypeOf(obj);
} else {
p = obj.constructor ? obj.constructor.prototype : null;
}
while (p !== null) { while (p !== null) {
memberGroups.push(Object.getOwnPropertyNames(p)); memberGroups.push(Object.getOwnPropertyNames(p));
p = Object.getPrototypeOf(p); p = Object.getPrototypeOf(p);

80
lib/tls.js

@ -27,6 +27,14 @@ var stream = require('stream');
var END_OF_FILE = 42; var END_OF_FILE = 42;
var assert = require('assert').ok; var assert = require('assert').ok;
// Allow {CLIENT_RENEG_LIMIT} client-initiated session renegotiations
// every {CLIENT_RENEG_WINDOW} seconds. An error event is emitted if more
// renegotations are seen. The settings are applied to all remote client
// connections.
exports.CLIENT_RENEG_LIMIT = 3;
exports.CLIENT_RENEG_WINDOW = 600;
var debug; var debug;
if (process.env.NODE_DEBUG && /tls/.test(process.env.NODE_DEBUG)) { if (process.env.NODE_DEBUG && /tls/.test(process.env.NODE_DEBUG)) {
debug = function(a) { console.error('TLS:', a); }; debug = function(a) { console.error('TLS:', a); };
@ -539,6 +547,37 @@ EncryptedStream.prototype._pusher = function(pool, offset, length) {
}; };
function onhandshakestart() {
debug('onhandshakestart');
var self = this, ssl = this.ssl;
ssl.handshakes++;
if (ssl.handshakes === 1) {
function timeout() {
ssl.handshakes = 0;
ssl.timer = null;
}
ssl.timer = setTimeout(timeout, exports.CLIENT_RENEG_WINDOW * 1000);
}
else if (ssl.handshakes >= exports.CLIENT_RENEG_LIMIT) {
// Defer the error event to the next tick. We're being called from OpenSSL's
// state machine and OpenSSL is not re-entrant. We cannot allow the user's
// callback to destroy the connection right now, it would crash and burn.
process.nextTick(function() {
var err = new Error('TLS session renegotiation attack detected.');
if (self.cleartext) self.cleartext.emit('error', err);
});
}
}
function onhandshakedone() {
// for future use
debug('onhandshakedone');
}
/** /**
* Provides a pair of streams to do encrypted communication. * Provides a pair of streams to do encrypted communication.
*/ */
@ -585,6 +624,13 @@ function SecurePair(credentials, isServer, requestCert, rejectUnauthorized,
this._isServer ? this._requestCert : options.servername, this._isServer ? this._requestCert : options.servername,
this._rejectUnauthorized); this._rejectUnauthorized);
if (this._isServer) {
this.ssl.onhandshakestart = onhandshakestart.bind(this);
this.ssl.onhandshakedone = onhandshakedone.bind(this);
this.ssl.handshakes = 0;
this.ssl.timer = null;
}
if (process.features.tls_sni) { if (process.features.tls_sni) {
if (this._isServer && options.SNICallback) { if (this._isServer && options.SNICallback) {
this.ssl.setSNICallback(options.SNICallback); this.ssl.setSNICallback(options.SNICallback);
@ -720,25 +766,29 @@ SecurePair.prototype.maybeInitFinished = function() {
SecurePair.prototype.destroy = function() { SecurePair.prototype.destroy = function() {
if (this._doneFlag) {
return;
}
var self = this; var self = this;
this._doneFlag = true; if (!this._doneFlag) {
this.ssl.error = null; this._doneFlag = true;
this.ssl.close();
this.ssl = null; if (this.ssl.timer) {
clearTimeout(this.ssl.timer);
this.ssl.timer = null;
}
self.encrypted.writable = self.encrypted.readable = false; this.ssl.error = null;
self.cleartext.writable = self.cleartext.readable = false; this.ssl.close();
this.ssl = null;
process.nextTick(function() { self.encrypted.writable = self.encrypted.readable = false;
self.cleartext.emit('end'); self.cleartext.writable = self.cleartext.readable = false;
self.encrypted.emit('close');
self.cleartext.emit('close'); process.nextTick(function() {
}); self.cleartext.emit('end');
self.encrypted.emit('close');
self.cleartext.emit('close');
});
}
}; };

1
node.gyp

@ -155,6 +155,7 @@
'FD_SETSIZE=1024', 'FD_SETSIZE=1024',
# we need to use node's preferred "win32" rather than gyp's preferred "win" # we need to use node's preferred "win32" rather than gyp's preferred "win"
'PLATFORM="win32"', 'PLATFORM="win32"',
'_UNICODE=1',
], ],
'libraries': [ '-lpsapi.lib' ] 'libraries': [ '-lpsapi.lib' ]
},{ # POSIX },{ # POSIX

241
src/node.cc

@ -1290,76 +1290,6 @@ static Handle<Value> Cwd(const Arguments& args) {
} }
#ifdef _WIN32
static Handle<Value> CwdForDrive(const Arguments& args) {
HandleScope scope;
if (args.Length() < 1) {
Local<Value> exception = Exception::Error(
String::New("process._cwdForDrive takes exactly 1 argument."));
return ThrowException(exception);
}
Local<String> driveLetter = args[0]->ToString();
if (driveLetter->Length() != 1) {
Local<Value> exception = Exception::Error(
String::New("Drive name should be 1 character."));
return ThrowException(exception);
}
char drive;
driveLetter->WriteAscii(&drive, 0, 1, 0);
if (drive >= 'a' && drive <= 'z') {
// Convert to uppercase
drive += 'A' - 'a';
} else if (drive < 'A' || drive > 'Z') {
// Not a letter
Local<Value> exception = Exception::Error(
String::New("Drive name should be a letter."));
return ThrowException(exception);
}
WCHAR env_key[] = L"=X:";
env_key[1] = (WCHAR) drive;
DWORD len = GetEnvironmentVariableW(env_key, NULL, 0);
if (len == 0 && GetLastError() == ERROR_ENVVAR_NOT_FOUND) {
// There is no current directory for that drive. Default to drive + ":\".
Local<String> cwd = String::Concat(String::New(&drive, 1),
String::New(":\\"));
return scope.Close(cwd);
} else if (len == 0) {
// Error
Local<Value> exception = Exception::Error(
String::New(winapi_strerror(GetLastError())));
return ThrowException(exception);
}
WCHAR* buffer = new WCHAR[len];
if (buffer == NULL) {
Local<Value> exception = Exception::Error(
String::New("Out of memory."));
return ThrowException(exception);
}
DWORD len2 = GetEnvironmentVariableW(env_key, buffer, len);
if (len2 == 0 || len2 >= len) {
// Error
delete[] buffer;
Local<Value> exception = Exception::Error(
String::New(winapi_strerror(GetLastError())));
return ThrowException(exception);
}
Local<String> cwd = String::New(reinterpret_cast<uint16_t*>(buffer), len2);
delete[] buffer;
return scope.Close(cwd);
}
#endif
static Handle<Value> Umask(const Arguments& args) { static Handle<Value> Umask(const Arguments& args) {
HandleScope scope; HandleScope scope;
unsigned int old; unsigned int old;
@ -1883,12 +1813,28 @@ static void ProcessTitleSetter(Local<String> property,
static Handle<Value> EnvGetter(Local<String> property, static Handle<Value> EnvGetter(Local<String> property,
const AccessorInfo& info) { const AccessorInfo& info) {
HandleScope scope;
#ifdef __POSIX__
String::Utf8Value key(property); String::Utf8Value key(property);
const char* val = getenv(*key); const char* val = getenv(*key);
if (val) { if (val) {
HandleScope scope;
return scope.Close(String::New(val)); return scope.Close(String::New(val));
} }
#else // _WIN32
String::Value key(property);
WCHAR buffer[32767]; // The maximum size allowed for environment variables.
DWORD result = GetEnvironmentVariableW(reinterpret_cast<WCHAR*>(*key),
buffer,
ARRAY_SIZE(buffer));
// If result >= sizeof buffer the buffer was too small. That should never
// happen. If result == 0 and result != ERROR_SUCCESS the variable was not
// not found.
if ((result > 0 || GetLastError() == ERROR_SUCCESS) &&
result < ARRAY_SIZE(buffer)) {
return scope.Close(String::New(reinterpret_cast<uint16_t*>(buffer), result));
}
#endif
// Not found
return Undefined(); return Undefined();
} }
@ -1897,66 +1843,82 @@ static Handle<Value> EnvSetter(Local<String> property,
Local<Value> value, Local<Value> value,
const AccessorInfo& info) { const AccessorInfo& info) {
HandleScope scope; HandleScope scope;
#ifdef __POSIX__
String::Utf8Value key(property); String::Utf8Value key(property);
String::Utf8Value val(value); String::Utf8Value val(value);
#ifdef __POSIX__
setenv(*key, *val, 1); setenv(*key, *val, 1);
#else // __WIN32__ #else // _WIN32
int n = key.length() + val.length() + 2; String::Value key(property);
char* pair = new char[n]; String::Value val(value);
snprintf(pair, n, "%s=%s", *key, *val); WCHAR* key_ptr = reinterpret_cast<WCHAR*>(*key);
int r = _putenv(pair); // Environment variables that start with '=' are read-only.
if (r) { if (key_ptr[0] != L'=') {
fprintf(stderr, "error putenv: '%s'\n", pair); SetEnvironmentVariableW(key_ptr, reinterpret_cast<WCHAR*>(*val));
} }
delete [] pair;
#endif #endif
// Whether it worked or not, always return rval.
return value; return scope.Close(value);
} }
static Handle<Integer> EnvQuery(Local<String> property, static Handle<Integer> EnvQuery(Local<String> property,
const AccessorInfo& info) { const AccessorInfo& info) {
HandleScope scope;
#ifdef __POSIX__
String::Utf8Value key(property); String::Utf8Value key(property);
if (getenv(*key)) { if (getenv(*key)) {
HandleScope scope;
return scope.Close(Integer::New(None)); return scope.Close(Integer::New(None));
} }
return Handle<Integer>(); #else // _WIN32
String::Value key(property);
WCHAR* key_ptr = reinterpret_cast<WCHAR*>(*key);
if (GetEnvironmentVariableW(key_ptr, NULL, 0) > 0 ||
GetLastError() == ERROR_SUCCESS) {
if (key_ptr[0] == L'=') {
// Environment variables that start with '=' are hidden and read-only.
return scope.Close(Integer::New(v8::ReadOnly ||
v8::DontDelete ||
v8::DontEnum));
} else {
return scope.Close(Integer::New(None));
}
}
#endif
// Not found
return scope.Close(Handle<Integer>());
} }
static Handle<Boolean> EnvDeleter(Local<String> property, static Handle<Boolean> EnvDeleter(Local<String> property,
const AccessorInfo& info) { const AccessorInfo& info) {
HandleScope scope; HandleScope scope;
String::Utf8Value key(property);
if (getenv(*key)) {
#ifdef __POSIX__ #ifdef __POSIX__
unsetenv(*key); // prototyped as `void unsetenv(const char*)` on some platforms String::Utf8Value key(property);
// prototyped as `void unsetenv(const char*)` on some platforms
if (unsetenv(*key) < 0) {
// Deletion failed. Return true if the key wasn't there in the first place,
// false if it is still there.
return scope.Close(Boolean::New(getenv(*key) == NULL));
};
#else #else
int n = key.length() + 2; String::Value key(property);
char* pair = new char[n]; WCHAR* key_ptr = reinterpret_cast<WCHAR*>(*key);
snprintf(pair, n, "%s=", *key); if (key_ptr[0] == L'=' || !SetEnvironmentVariableW(key_ptr, NULL)) {
int r = _putenv(pair); // Deletion failed. Return true if the key wasn't there in the first place,
if (r) { // false if it is still there.
fprintf(stderr, "error unsetenv: '%s'\n", pair); bool rv = GetEnvironmentVariableW(key_ptr, NULL, NULL) == 0 &&
} GetLastError() != ERROR_SUCCESS;
delete [] pair; return scope.Close(Boolean::New(rv));
#endif
return True();
} }
#endif
return False(); // It worked
return v8::True();
} }
static Handle<Array> EnvEnumerator(const AccessorInfo& info) { static Handle<Array> EnvEnumerator(const AccessorInfo& info) {
HandleScope scope; HandleScope scope;
#ifdef __POSIX__
int size = 0; int size = 0;
while (environ[size]) size++; while (environ[size]) size++;
@ -1968,7 +1930,32 @@ static Handle<Array> EnvEnumerator(const AccessorInfo& info) {
const int length = s ? s - var : strlen(var); const int length = s ? s - var : strlen(var);
env->Set(i, String::New(var, length)); env->Set(i, String::New(var, length));
} }
#else // _WIN32
WCHAR* environment = GetEnvironmentStringsW();
if (environment == NULL) {
// This should not happen.
return scope.Close(Handle<Array>());
}
Local<Array> env = Array::New();
WCHAR* p = environment;
int i = 0;
while (*p != NULL) {
WCHAR *s;
if (*p == L'=') {
// If the key starts with '=' it is a hidden environment variable.
p += wcslen(p) + 1;
continue;
} else {
s = wcschr(p, L'=');
}
if (!s) {
s = p + wcslen(p);
}
env->Set(i++, String::New(reinterpret_cast<uint16_t*>(p), s - p));
p = s + wcslen(s) + 1;
}
FreeEnvironmentStringsW(environment);
#endif
return scope.Close(env); return scope.Close(env);
} }
@ -2127,10 +2114,6 @@ Handle<Object> SetupProcessObject(int argc, char *argv[]) {
NODE_SET_METHOD(process, "chdir", Chdir); NODE_SET_METHOD(process, "chdir", Chdir);
NODE_SET_METHOD(process, "cwd", Cwd); NODE_SET_METHOD(process, "cwd", Cwd);
#ifdef _WIN32
NODE_SET_METHOD(process, "_cwdForDrive", CwdForDrive);
#endif
NODE_SET_METHOD(process, "umask", Umask); NODE_SET_METHOD(process, "umask", Umask);
#ifdef __POSIX__ #ifdef __POSIX__
@ -2416,13 +2399,14 @@ DWORD WINAPI EnableDebugThreadProc(void* arg) {
} }
static int GetDebugSignalHandlerMappingName(DWORD pid, char* buf, size_t buf_len) { static int GetDebugSignalHandlerMappingName(DWORD pid, wchar_t* buf,
return snprintf(buf, buf_len, "node-debug-handler-%u", pid); size_t buf_len) {
return _snwprintf(buf, buf_len, L"node-debug-handler-%u", pid);
} }
static int RegisterDebugSignalHandler() { static int RegisterDebugSignalHandler() {
char mapping_name[32]; wchar_t mapping_name[32];
HANDLE mapping_handle; HANDLE mapping_handle;
DWORD pid; DWORD pid;
LPTHREAD_START_ROUTINE* handler; LPTHREAD_START_ROUTINE* handler;
@ -2431,11 +2415,11 @@ static int RegisterDebugSignalHandler() {
if (GetDebugSignalHandlerMappingName(pid, if (GetDebugSignalHandlerMappingName(pid,
mapping_name, mapping_name,
sizeof mapping_name) < 0) { ARRAY_SIZE(mapping_name)) < 0) {
return -1; return -1;
} }
mapping_handle = CreateFileMappingA(INVALID_HANDLE_VALUE, mapping_handle = CreateFileMappingW(INVALID_HANDLE_VALUE,
NULL, NULL,
PAGE_READWRITE, PAGE_READWRITE,
0, 0,
@ -2445,11 +2429,12 @@ static int RegisterDebugSignalHandler() {
return -1; return -1;
} }
handler = (LPTHREAD_START_ROUTINE*) MapViewOfFile(mapping_handle, handler = reinterpret_cast<LPTHREAD_START_ROUTINE*>(
FILE_MAP_ALL_ACCESS, MapViewOfFile(mapping_handle,
0, FILE_MAP_ALL_ACCESS,
0, 0,
sizeof *handler); 0,
sizeof *handler));
if (handler == NULL) { if (handler == NULL) {
CloseHandle(mapping_handle); CloseHandle(mapping_handle);
return -1; return -1;
@ -2470,7 +2455,7 @@ static Handle<Value> DebugProcess(const Arguments& args) {
HANDLE process = NULL; HANDLE process = NULL;
HANDLE thread = NULL; HANDLE thread = NULL;
HANDLE mapping = NULL; HANDLE mapping = NULL;
char mapping_name[32]; wchar_t mapping_name[32];
LPTHREAD_START_ROUTINE* handler = NULL; LPTHREAD_START_ROUTINE* handler = NULL;
if (args.Length() != 1) { if (args.Length() != 1) {
@ -2492,22 +2477,24 @@ static Handle<Value> DebugProcess(const Arguments& args) {
if (GetDebugSignalHandlerMappingName(pid, if (GetDebugSignalHandlerMappingName(pid,
mapping_name, mapping_name,
sizeof mapping_name) < 0) { ARRAY_SIZE(mapping_name)) < 0) {
rv = ThrowException(ErrnoException(errno, "sprintf")); rv = ThrowException(ErrnoException(errno, "sprintf"));
goto out; goto out;
} }
mapping = OpenFileMapping(FILE_MAP_READ, FALSE, mapping_name); mapping = OpenFileMappingW(FILE_MAP_READ, FALSE, mapping_name);
if (mapping == NULL) { if (mapping == NULL) {
rv = ThrowException(WinapiErrnoException(GetLastError(), "sprintf")); rv = ThrowException(WinapiErrnoException(GetLastError(),
"OpenFileMappingW"));
goto out; goto out;
} }
handler = (LPTHREAD_START_ROUTINE*) MapViewOfFile(mapping, handler = reinterpret_cast<LPTHREAD_START_ROUTINE*>(
FILE_MAP_READ, MapViewOfFile(mapping,
0, FILE_MAP_READ,
0, 0,
sizeof *handler); 0,
sizeof *handler));
if (handler == NULL || *handler == NULL) { if (handler == NULL || *handler == NULL) {
rv = ThrowException(WinapiErrnoException(GetLastError(), "MapViewOfFile")); rv = ThrowException(WinapiErrnoException(GetLastError(), "MapViewOfFile"));
goto out; goto out;

16
src/node_crypto.cc

@ -908,6 +908,8 @@ Handle<Value> Connection::New(const Arguments& args) {
SSL_set_app_data(p->ssl_, p); SSL_set_app_data(p->ssl_, p);
if (is_server) SSL_set_info_callback(p->ssl_, SSLInfoCallback);
#ifdef OPENSSL_NPN_NEGOTIATED #ifdef OPENSSL_NPN_NEGOTIATED
if (is_server) { if (is_server) {
// Server should advertise NPN protocols // Server should advertise NPN protocols
@ -970,6 +972,20 @@ Handle<Value> Connection::New(const Arguments& args) {
} }
void Connection::SSLInfoCallback(const SSL *ssl, int where, int ret) {
if (where & SSL_CB_HANDSHAKE_START) {
HandleScope scope;
Connection* c = static_cast<Connection*>(SSL_get_app_data(ssl));
MakeCallback(c->handle_, "onhandshakestart", 0, NULL);
}
if (where & SSL_CB_HANDSHAKE_DONE) {
HandleScope scope;
Connection* c = static_cast<Connection*>(SSL_get_app_data(ssl));
MakeCallback(c->handle_, "onhandshakedone", 0, NULL);
}
}
Handle<Value> Connection::EncIn(const Arguments& args) { Handle<Value> Connection::EncIn(const Arguments& args) {
HandleScope scope; HandleScope scope;

2
src/node_crypto.h

@ -190,6 +190,8 @@ class Connection : ObjectWrap {
} }
private: private:
static void SSLInfoCallback(const SSL *ssl, int where, int ret);
BIO *bio_read_; BIO *bio_read_;
BIO *bio_write_; BIO *bio_write_;
SSL *ssl_; SSL *ssl_;

41
src/node_main.cc

@ -21,6 +21,47 @@
#include <node.h> #include <node.h>
#ifdef _WIN32
int wmain(int argc, wchar_t *wargv[]) {
// Convert argv to to UTF8
char** argv = new char*[argc];
for (int i = 0; i < argc; i++) {
// Compute the size of the required buffer
DWORD size = WideCharToMultiByte(CP_UTF8,
0,
wargv[i],
-1,
NULL,
0,
NULL,
NULL);
if (size == 0) {
// This should never happen.
fprintf(stderr, "Could not convert arguments to utf8.");
exit(1);
}
// Do the actual conversion
argv[i] = new char[size];
DWORD result = WideCharToMultiByte(CP_UTF8,
0,
wargv[i],
-1,
argv[i],
size,
NULL,
NULL);
if (result == 0) {
// This should never happen.
fprintf(stderr, "Could not convert arguments to utf8.");
exit(1);
}
}
// Now that conversion is done, we can finally start.
return node::Start(argc, argv);
}
#else
// UNIX
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
return node::Start(argc, argv); return node::Start(argc, argv);
} }
#endif

5
src/pipe_wrap.cc

@ -204,10 +204,7 @@ void PipeWrap::OnConnection(uv_stream_t* handle, int status) {
PipeWrap* client_wrap = PipeWrap* client_wrap =
static_cast<PipeWrap*>(client_obj->GetPointerFromInternalField(0)); static_cast<PipeWrap*>(client_obj->GetPointerFromInternalField(0));
int r = uv_accept(handle, (uv_stream_t*)&client_wrap->handle_); if (uv_accept(handle, (uv_stream_t*)&client_wrap->handle_)) return;
// uv_accept should always work.
assert(r == 0);
// Successful accept. Call the onconnection callback in JavaScript land. // Successful accept. Call the onconnection callback in JavaScript land.
Local<Value> argv[1] = { client_obj }; Local<Value> argv[1] = { client_obj };

5
src/tcp_wrap.cc

@ -366,10 +366,7 @@ void TCPWrap::OnConnection(uv_stream_t* handle, int status) {
TCPWrap* client_wrap = TCPWrap* client_wrap =
static_cast<TCPWrap*>(client_obj->GetPointerFromInternalField(0)); static_cast<TCPWrap*>(client_obj->GetPointerFromInternalField(0));
int r = uv_accept(handle, (uv_stream_t*)&client_wrap->handle_); if (uv_accept(handle, (uv_stream_t*)&client_wrap->handle_)) return;
// uv_accept should always work.
assert(r == 0);
// Successful accept. Call the onconnection callback in JavaScript land. // Successful accept. Call the onconnection callback in JavaScript land.
argv[0] = client_obj; argv[0] = client_obj;

100
test/pummel/test-tls-ci-reneg-attack.js

@ -0,0 +1,100 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var common = require('../common');
var assert = require('assert');
var spawn = require('child_process').spawn;
var tls = require('tls');
var fs = require('fs');
// renegotiation limits to test
var LIMITS = [0, 1, 2, 3, 5, 10, 16];
if (process.platform === 'win32') {
console.log("Skipping test, you probably don't have openssl installed.");
process.exit();
}
(function() {
var n = 0;
function next() {
if (n >= LIMITS.length) return;
tls.CLIENT_RENEG_LIMIT = LIMITS[n++];
test(next);
}
next();
})();
function test(next) {
var options = {
cert: fs.readFileSync(common.fixturesDir + '/test_cert.pem'),
key: fs.readFileSync(common.fixturesDir + '/test_key.pem')
};
var server = tls.createServer(options, function(conn) {
conn.on('error', function(err) {
console.error('Caught exception: ' + err);
assert(/TLS session renegotiation attack/.test(err));
conn.destroy();
});
conn.pipe(conn);
});
server.listen(common.PORT, function() {
var args = ('s_client -connect 127.0.0.1:' + common.PORT).split(' ');
var child = spawn('openssl', args);
child.stdout.pipe(process.stdout);
child.stderr.pipe(process.stderr);
// count handshakes, start the attack after the initial handshake is done
var handshakes = 0;
child.stderr.on('data', function(data) {
handshakes += (('' + data).match(/verify return:1/g) || []).length;
if (handshakes === 2) spam();
});
child.on('exit', function() {
// with a renegotiation limit <= 1, we always see 4 handshake markers:
// two for the initial handshake and another two for the attempted
// renegotiation
assert.equal(handshakes, 2 * Math.max(2, tls.CLIENT_RENEG_LIMIT));
server.close();
process.nextTick(next);
});
var closed = false;
child.stdin.on('error', function(err) {
assert.equal(err.code, 'EPIPE');
closed = true;
});
child.stdin.on('close', function() {
closed = true;
});
// simulate renegotiation attack
function spam() {
if (closed) return;
child.stdin.write("R\n");
setTimeout(spam, 250);
}
});
}

77
test/simple/test-cluster-bind-twice.js

@ -1,77 +0,0 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
// This test starts two clustered HTTP servers on the same port. It expects the
// first cluster to succeed and the second cluster to fail with EADDRINUSE.
var common = require('../common');
var assert = require('assert');
var cluster = require('cluster');
var fork = require('child_process').fork;
var http = require('http');
var id = process.argv[2];
if (!id) {
var a = fork(__filename, ['one']);
var b = fork(__filename, ['two']);
a.on('message', function(m) {
assert.equal(m, 'READY');
b.send('START');
});
var ok = false;
b.on('message', function(m) {
assert.equal(m, 'EADDRINUSE');
a.kill();
b.kill();
ok = true;
});
process.on('exit', function() {
a.kill();
b.kill();
assert(ok);
});
}
else if (id === 'one') {
if (cluster.isMaster) cluster.fork();
http.createServer(assert.fail).listen(common.PORT, function() {
process.send('READY');
});
}
else if (id === 'two') {
if (cluster.isMaster) cluster.fork();
process.on('message', function(m) {
assert.equal(m, 'START');
var server = http.createServer(assert.fail);
server.listen(common.PORT, assert.fail);
server.on('error', function(e) {
assert.equal(e.code, 'EADDRINUSE');
process.send(e.code);
});
});
}
else {
assert(0); // bad command line argument
}

35
test/simple/test-dgram-close.js

@ -0,0 +1,35 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
// Ensure that if a dgram socket is closed before the DNS lookup completes, it
// won't crash.
var assert = require('assert'),
common = require('../common'),
dgram = require('dgram');
var buf = new Buffer(1024);
buf.fill(42);
var socket = dgram.createSocket('udp4');
socket.send(buf, 0, buf.length, common.port, 'localhost');
socket.close();

9
test/simple/test-http-server-multiheaders.js

@ -33,6 +33,8 @@ var srv = http.createServer(function(req, res) {
assert.equal(req.headers['www-authenticate'], 'foo, bar, baz'); assert.equal(req.headers['www-authenticate'], 'foo, bar, baz');
assert.equal(req.headers['x-foo'], 'bingo'); assert.equal(req.headers['x-foo'], 'bingo');
assert.equal(req.headers['x-bar'], 'banjo, bango'); assert.equal(req.headers['x-bar'], 'banjo, bango');
assert.equal(req.headers['sec-websocket-protocol'], 'chat, share');
assert.equal(req.headers['sec-websocket-extensions'], 'foo; 1, bar; 2, baz');
res.writeHead(200, {'Content-Type' : 'text/plain'}); res.writeHead(200, {'Content-Type' : 'text/plain'});
res.end('EOF'); res.end('EOF');
@ -57,7 +59,12 @@ srv.listen(common.PORT, function() {
['WWW-AUTHENTICATE', 'baz'], ['WWW-AUTHENTICATE', 'baz'],
['x-foo', 'bingo'], ['x-foo', 'bingo'],
['x-bar', 'banjo'], ['x-bar', 'banjo'],
['x-bar', 'bango'] ['x-bar', 'bango'],
['sec-websocket-protocol', 'chat'],
['sec-websocket-protocol', 'share'],
['sec-websocket-extensions', 'foo; 1'],
['sec-websocket-extensions', 'bar; 2'],
['sec-websocket-extensions', 'baz']
] ]
}); });
}); });

9
test/simple/test-repl-tab-complete.js

@ -180,3 +180,12 @@ testMe.complete('inner.o', function(error, data) {
assert.deepEqual(data, doesNotBreak); assert.deepEqual(data, doesNotBreak);
}); });
putIn.run(['.clear']);
// make sure tab completion works on non-Objects
putIn.run([
'var str = "test";'
]);
testMe.complete('str.len', function(error, data) {
assert.deepEqual(data, [ [ 'str.length' ], 'str.len' ]);
});

156
test/simple/test-tls-over-http-tunnel.js

@ -0,0 +1,156 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
if (!process.versions.openssl) {
console.error('Skipping because node compiled without OpenSSL.');
process.exit(0);
}
var common = require('../common');
var assert = require('assert');
var fs = require('fs');
var net = require('net');
var http = require('http');
var https = require('https');
var proxyPort = common.PORT + 1;
var gotRequest = false;
var key = fs.readFileSync(common.fixturesDir + '/keys/agent1-key.pem');
var cert = fs.readFileSync(common.fixturesDir + '/keys/agent1-cert.pem');
var options = {
key: key,
cert: cert
};
var server = https.createServer(options, function(req, res) {
console.log('SERVER: got request');
res.writeHead(200, {
'content-type': 'text/plain',
});
console.log('SERVER: sending response');
res.end('hello world\n');
});
var proxy = net.createServer(function(clientSocket) {
console.log('PROXY: got a client connection');
var serverSocket = null;
clientSocket.on('data', function(chunk) {
if (!serverSocket) {
// Verify the CONNECT request
assert.equal('CONNECT localhost:' + common.PORT + ' HTTP/1.1\r\n' +
'Proxy-Connections: keep-alive\r\nContent-Length:' +
' 0\r\nHost: localhost:' + proxyPort + '\r\n\r\n',
chunk);
console.log('PROXY: got CONNECT request');
console.log('PROXY: creating a tunnel');
// create the tunnel
serverSocket = net.connect(common.PORT, function() {
console.log('PROXY: replying to client CONNECT request');
// Send the response
clientSocket.write('HTTP/1.1 200 OK\r\nProxy-Connections: keep' +
'-alive\r\nConnections: keep-alive\r\nVia: ' +
'localhost:' + proxyPort + '\r\n\r\n');
});
serverSocket.on('data', function(chunk) {
clientSocket.write(chunk);
});
serverSocket.on('end', function() {
clientSocket.destroy();
});
} else {
serverSocket.write(chunk);
}
});
clientSocket.on('end', function() {
serverSocket.destroy();
});
});
server.listen(common.PORT);
proxy.listen(proxyPort, function() {
console.log('CLIENT: Making CONNECT request');
http.request({
port: proxyPort,
method: 'CONNECT',
path: 'localhost:' + common.PORT,
headers: {
'Proxy-Connections': 'keep-alive',
'Content-Length': 0
}
}, function(res) {
assert.equal(200, res.statusCode);
console.log('CLIENT: got CONNECT response');
// detach the socket
res.socket.emit('agentRemove');
res.socket.removeAllListeners('data');
res.socket.removeAllListeners('close');
res.socket.removeAllListeners('error');
res.socket.removeAllListeners('drain');
res.socket.removeAllListeners('end');
res.socket.ondata = null;
res.socket.onend = null;
res.socket.ondrain = null;
console.log('CLIENT: Making HTTPS request');
https.get({
path: '/foo',
key: key,
cert: cert,
socket: res.socket, // reuse the socket
agent: false,
}, function(res) {
assert.equal(200, res.statusCode);
res.on('data', function(chunk) {
assert.equal('hello world\n', chunk);
console.log('CLIENT: got HTTPS response');
gotRequest = true;
});
res.on('end', function() {
proxy.close();
server.close();
});
}).end();
}).end();
});
process.on('exit', function() {
assert.ok(gotRequest);
});

41
vcbuild.bat

@ -26,26 +26,27 @@ set upload=
:next-arg :next-arg
if "%1"=="" goto args-done if "%1"=="" goto args-done
if /i "%1"=="debug" set config=Debug&goto arg-ok if /i "%1"=="debug" set config=Debug&goto arg-ok
if /i "%1"=="release" set config=Release&goto arg-ok if /i "%1"=="release" set config=Release&goto arg-ok
if /i "%1"=="clean" set target=Clean&goto arg-ok if /i "%1"=="clean" set target=Clean&goto arg-ok
if /i "%1"=="ia32" set target_arch=ia32&goto arg-ok if /i "%1"=="ia32" set target_arch=ia32&goto arg-ok
if /i "%1"=="x86" set target_arch=ia32&goto arg-ok if /i "%1"=="x86" set target_arch=ia32&goto arg-ok
if /i "%1"=="x64" set target_arch=x64&goto arg-ok if /i "%1"=="x64" set target_arch=x64&goto arg-ok
if /i "%1"=="noprojgen" set noprojgen=1&goto arg-ok if /i "%1"=="noprojgen" set noprojgen=1&goto arg-ok
if /i "%1"=="nobuild" set nobuild=1&goto arg-ok if /i "%1"=="nobuild" set nobuild=1&goto arg-ok
if /i "%1"=="nosign" set nosign=1&goto arg-ok if /i "%1"=="nosign" set nosign=1&goto arg-ok
if /i "%1"=="nosnapshot" set nosnapshot=1&goto arg-ok if /i "%1"=="nosnapshot" set nosnapshot=1&goto arg-ok
if /i "%1"=="test-uv" set test=test-uv&goto arg-ok if /i "%1"=="test-uv" set test=test-uv&goto arg-ok
if /i "%1"=="test-internet"set test=test-internet&goto arg-ok if /i "%1"=="test-internet" set test=test-internet&goto arg-ok
if /i "%1"=="test-pummel" set test=test-pummel&goto arg-ok if /i "%1"=="test-pummel" set test=test-pummel&goto arg-ok
if /i "%1"=="test-simple" set test=test-simple&goto arg-ok if /i "%1"=="test-simple" set test=test-simple&goto arg-ok
if /i "%1"=="test-message" set test=test-message&goto arg-ok if /i "%1"=="test-message" set test=test-message&goto arg-ok
if /i "%1"=="test-all" set test=test-all&goto arg-ok if /i "%1"=="test-all" set test=test-all&goto arg-ok
if /i "%1"=="test" set test=test&goto arg-ok if /i "%1"=="test" set test=test&goto arg-ok
if /i "%1"=="msi" set msi=1&goto arg-ok if /i "%1"=="msi" set msi=1&goto arg-ok
if /i "%1"=="upload" set upload=1&goto arg-ok if /i "%1"=="upload" set upload=1&goto arg-ok
echo Warning: ignoring invalid command line option `%1`.
:arg-ok :arg-ok
shift shift

Loading…
Cancel
Save