diff options
author | isaacs <i@izs.me> | 2012-02-18 09:46:58 -0800 |
---|---|---|
committer | isaacs <i@izs.me> | 2012-02-18 09:46:58 -0800 |
commit | 31721da4b120ca2b1c0b6b9e93ce8beb5e810da3 (patch) | |
tree | 9a4da945ae4868c6dbd0c509f8f08c87b8776a92 | |
parent | 07872870213ed38c1978b37e9e2e22db733b40f3 (diff) | |
parent | c1f474010e8e58793408fc1d8303aeb1a01ba735 (diff) | |
download | node-new-31721da4b120ca2b1c0b6b9e93ce8beb5e810da3.tar.gz |
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
-rw-r--r-- | AUTHORS | 3 | ||||
-rw-r--r-- | ChangeLog | 42 | ||||
-rw-r--r-- | deps/uv/src/unix/tty.c | 4 | ||||
-rw-r--r-- | deps/uv/src/win/error.c | 3 | ||||
-rw-r--r-- | doc/about/index.html | 2 | ||||
-rw-r--r-- | doc/api/tls.markdown | 97 | ||||
-rw-r--r-- | doc/community/index.html | 6 | ||||
-rw-r--r-- | doc/index.html | 6 | ||||
-rw-r--r-- | doc/logos/index.html | 23 | ||||
-rw-r--r-- | lib/dgram.js | 2 | ||||
-rw-r--r-- | lib/http.js | 2 | ||||
-rw-r--r-- | lib/path.js | 8 | ||||
-rw-r--r-- | lib/repl.js | 7 | ||||
-rw-r--r-- | lib/tls.js | 80 | ||||
-rw-r--r-- | node.gyp | 1 | ||||
-rw-r--r-- | src/node.cc | 241 | ||||
-rw-r--r-- | src/node_crypto.cc | 16 | ||||
-rw-r--r-- | src/node_crypto.h | 2 | ||||
-rw-r--r-- | src/node_main.cc | 41 | ||||
-rw-r--r-- | src/pipe_wrap.cc | 5 | ||||
-rw-r--r-- | src/tcp_wrap.cc | 5 | ||||
-rw-r--r-- | test/pummel/test-tls-ci-reneg-attack.js | 100 | ||||
-rw-r--r-- | test/simple/test-cluster-bind-twice.js | 77 | ||||
-rw-r--r-- | test/simple/test-dgram-close.js | 35 | ||||
-rw-r--r-- | test/simple/test-http-server-multiheaders.js | 9 | ||||
-rw-r--r-- | test/simple/test-repl-tab-complete.js | 9 | ||||
-rw-r--r-- | test/simple/test-tls-over-http-tunnel.js | 156 | ||||
-rw-r--r-- | vcbuild.bat | 41 |
28 files changed, 707 insertions, 316 deletions
@@ -263,3 +263,6 @@ Dan VerWeire <dverweire@gmail.com> Matthew Fitzsimmons <matt@joyent.com> Philip Tellis <philip.tellis@gmail.com> Christopher Jeffrey <chjjeffrey@gmail.com> +Paddy Byers <paddy.byers@gmail.com> +Seth Fitzsimmons <seth@mojodna.net> +Einar Otto Stangvik <einaros@gmail.com> @@ -97,7 +97,47 @@ * 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 diff --git a/deps/uv/src/unix/tty.c b/deps/uv/src/unix/tty.c index 18a892168f..c1429660eb 100644 --- a/deps/uv/src/unix/tty.c +++ b/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[VTIME] = 0; - /* Put terminal in raw mode after flushing */ - if (tcsetattr(fd, TCSAFLUSH, &raw)) { + /* Put terminal in raw mode after draining */ + if (tcsetattr(fd, TCSADRAIN, &raw)) { goto fatal; } diff --git a/deps/uv/src/win/error.c b/deps/uv/src/win/error.c index bc7cfdf0bf..1922f2039b 100644 --- a/deps/uv/src/win/error.c +++ b/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_NO_UNICODE_TRANSLATION: return UV_ECHARSET; 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_SEM_TIMEOUT: return UV_ETIMEDOUT; case WSAETIMEDOUT: return UV_ETIMEDOUT; diff --git a/doc/about/index.html b/doc/about/index.html index f2bfb67f51..087cd48b80 100644 --- a/doc/about/index.html +++ b/doc/about/index.html @@ -1,5 +1,5 @@ <!doctype html> -<html> +<html lang="en"> <head> <meta charset="utf-8"> <style> diff --git a/doc/api/tls.markdown b/doc/api/tls.markdown index af469aa471..5a4f4632dc 100644 --- a/doc/api/tls.markdown +++ b/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 `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). 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. + - `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 ['secureConnect'](#event_secureConnect_) event. @@ -178,27 +215,7 @@ Here is an example of a client of echo server as described previously: }); -### STARTTLS - -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]) +## tls.createSecurePair([credentials], [isServer], [requestCert], [rejectUnauthorized]) Creates a new secure pair object with two streams, one of which reads/writes 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 [cleartext](#tls.CleartextStream) and `encrypted` stream properties. -#### Event: 'secure' +### Event: 'secure' The event is emitted from the SecurePair once the pair has successfully 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 used properly authorized. -### tls.Server +## tls.Server 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 connections using TLS or SSL. -#### Event: 'secureConnection' +### Event: 'secureConnection' `function (cleartextStream) {}` @@ -255,7 +272,7 @@ server, you unauthorized connections may be accepted. SNI. -#### Event: 'clientError' +### Event: 'clientError' `function (exception) { }` @@ -263,7 +280,7 @@ When a client connection emits an 'error' event before secure connection is 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 `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. -#### server.close() +### server.close() Stops the server from accepting new connections. This function is asynchronous, the server is finally closed when the server emits a `'close'` event. -#### server.address() +### server.address() Returns the bound address and port of the server as reported by the operating system. 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 matching passed `hostname` (wildcards can be used). `credentials` can contain `key`, `cert` and `ca`. -#### server.maxConnections +### server.maxConnections Set this property to reject connections when the server's connection count gets high. -#### server.connections +### server.connections 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 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. It has all the common stream methods and events. -#### Event: 'secureConnect' +### Event: 'secureConnect' `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.npnProtocol` for negotiated protocol. -#### cleartextStream.authorized +### cleartextStream.authorized A boolean that is `true` if the peer certificate was signed by one of the specified CAs, otherwise `false` -#### cleartextStream.authorizationError +### cleartextStream.authorizationError The reason why the peer's certificate has not been verified. This property becomes available only when `cleartextStream.authorized === false`. -#### cleartextStream.getPeerCertificate() +### cleartextStream.getPeerCertificate() Returns an object representing the peer's certificate. The returned object has 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 object. -#### cleartextStream.address() +### cleartextStream.address() Returns the bound address and port of the underlying socket as reported by the operating system. Returns an object with two properties, e.g. `{"address":"192.168.57.1", "port":62053}` -#### cleartextStream.remoteAddress +### cleartextStream.remoteAddress The string representation of the remote IP address. For example, `'74.125.127.100'` or `'2001:4860:a005::68'`. -#### cleartextStream.remotePort +### cleartextStream.remotePort The numeric representation of the remote port. For example, `443`. diff --git a/doc/community/index.html b/doc/community/index.html index 1fb408a971..1188d7f9b2 100644 --- a/doc/community/index.html +++ b/doc/community/index.html @@ -1,5 +1,5 @@ <!doctype html> -<html> +<html lang="en"> <head> <meta charset="utf-8"> <style> @@ -12,8 +12,8 @@ <script src="../sh_javascript.min.js"></script> <link type="image/x-icon" rel="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 type="text/css" rel="stylesheet" href="../sh_vim-dark.css"> + <link rel="stylesheet" href="../pipe.css"> + <link rel="stylesheet" href="../sh_vim-dark.css"> <link rel="alternate" type="application/rss+xml" title="node blog" diff --git a/doc/index.html b/doc/index.html index b367e3f5d2..e4784729b7 100644 --- a/doc/index.html +++ b/doc/index.html @@ -157,7 +157,7 @@ var http = require('http'); http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); 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> <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 server = net.createServer(function (socket) { - socket.write("Echo server\r\n"); + socket.write('Echo server\r\n'); 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> --> </div> diff --git a/doc/logos/index.html b/doc/logos/index.html index 3e7897800c..1b36f3a955 100644 --- a/doc/logos/index.html +++ b/doc/logos/index.html @@ -1,24 +1,21 @@ <!doctype html> -<html> +<html lang="en"> <head> - <style type="text/css"> + <meta charset="utf-8"> + <style> ul { padding: 0; margin: 0; } </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="shortcut icon" href="../favicon.ico"> - <link type="text/css" rel="stylesheet" href="../pipe.css"> - <link type="text/css" rel="stylesheet" href="../sh_vim-dark.css"> + <link rel="stylesheet" href="../pipe.css"> + <link rel="stylesheet" href="../sh_vim-dark.css"> <link rel="alternate" type="application/rss+xml" title="node blog" href="http://feeds.feedburner.com/nodejs/123123123"> - <meta http-equiv="content-type" content="text/html; charset=utf-8"> <title>node.js</title> </head> <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> </div> - - - - <script type="text/javascript"> + <script> var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www."); document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E")); @@ -102,6 +96,5 @@ pageTracker._trackPageview(); } catch(err) {} </script> - - -</body></html> +</body> +</html> diff --git a/lib/dgram.js b/lib/dgram.js index b5b0473de8..8a88364bb5 100644 --- a/lib/dgram.js +++ b/lib/dgram.js @@ -175,7 +175,7 @@ Socket.prototype.send = function(buffer, if (callback) callback(err); self.emit('error', err); } - else { + else if (self._handle) { var req = self._handle.send(buffer, offset, length, port, ip); if (req) { req.oncomplete = afterSend; diff --git a/lib/http.js b/lib/http.js index a0745f0b99..d800895a5a 100644 --- a/lib/http.js +++ b/lib/http.js @@ -359,6 +359,8 @@ IncomingMessage.prototype._addHeaderLine = function(field, value) { case 'pragma': case 'link': case 'www-authenticate': + case 'sec-websocket-extensions': + case 'sec-websocket-protocol': if (field in dest) { dest[field] += ', ' + value; } else { diff --git a/lib/path.js b/lib/path.js index ff04cc6762..0ca51e84f3 100644 --- a/lib/path.js +++ b/lib/path.js @@ -97,7 +97,13 @@ if (isWindows) { // 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 // 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 diff --git a/lib/repl.js b/lib/repl.js index aeb51110eb..7bbc6d1448 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -525,8 +525,13 @@ REPLServer.prototype.complete = function(line, callback) { } // works for non-objects try { - var p = Object.getPrototypeOf(obj); var sentinel = 5; + var p; + if (typeof obj == 'object') { + p = Object.getPrototypeOf(obj); + } else { + p = obj.constructor ? obj.constructor.prototype : null; + } while (p !== null) { memberGroups.push(Object.getOwnPropertyNames(p)); p = Object.getPrototypeOf(p); diff --git a/lib/tls.js b/lib/tls.js index 430ef2f259..d951ef358b 100644 --- a/lib/tls.js +++ b/lib/tls.js @@ -27,6 +27,14 @@ var stream = require('stream'); var END_OF_FILE = 42; 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; if (process.env.NODE_DEBUG && /tls/.test(process.env.NODE_DEBUG)) { 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. */ @@ -585,6 +624,13 @@ function SecurePair(credentials, isServer, requestCert, rejectUnauthorized, this._isServer ? this._requestCert : options.servername, 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 (this._isServer && options.SNICallback) { this.ssl.setSNICallback(options.SNICallback); @@ -720,25 +766,29 @@ SecurePair.prototype.maybeInitFinished = function() { SecurePair.prototype.destroy = function() { - if (this._doneFlag) { - return; - } - var self = this; - this._doneFlag = true; - this.ssl.error = null; - this.ssl.close(); - this.ssl = null; + if (!this._doneFlag) { + this._doneFlag = true; + + if (this.ssl.timer) { + clearTimeout(this.ssl.timer); + this.ssl.timer = null; + } - self.encrypted.writable = self.encrypted.readable = false; - self.cleartext.writable = self.cleartext.readable = false; + this.ssl.error = null; + this.ssl.close(); + this.ssl = null; - process.nextTick(function() { - self.cleartext.emit('end'); - self.encrypted.emit('close'); - self.cleartext.emit('close'); - }); + self.encrypted.writable = self.encrypted.readable = false; + self.cleartext.writable = self.cleartext.readable = false; + + process.nextTick(function() { + self.cleartext.emit('end'); + self.encrypted.emit('close'); + self.cleartext.emit('close'); + }); + } }; @@ -155,6 +155,7 @@ 'FD_SETSIZE=1024', # we need to use node's preferred "win32" rather than gyp's preferred "win" 'PLATFORM="win32"', + '_UNICODE=1', ], 'libraries': [ '-lpsapi.lib' ] },{ # POSIX diff --git a/src/node.cc b/src/node.cc index 9a2d8faeb1..42a2f8c0d7 100644 --- a/src/node.cc +++ b/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) { HandleScope scope; unsigned int old; @@ -1883,12 +1813,28 @@ static void ProcessTitleSetter(Local<String> property, static Handle<Value> EnvGetter(Local<String> property, const AccessorInfo& info) { + HandleScope scope; +#ifdef __POSIX__ String::Utf8Value key(property); const char* val = getenv(*key); if (val) { - HandleScope scope; 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(); } @@ -1897,66 +1843,82 @@ static Handle<Value> EnvSetter(Local<String> property, Local<Value> value, const AccessorInfo& info) { HandleScope scope; +#ifdef __POSIX__ String::Utf8Value key(property); String::Utf8Value val(value); - -#ifdef __POSIX__ setenv(*key, *val, 1); -#else // __WIN32__ - int n = key.length() + val.length() + 2; - char* pair = new char[n]; - snprintf(pair, n, "%s=%s", *key, *val); - int r = _putenv(pair); - if (r) { - fprintf(stderr, "error putenv: '%s'\n", pair); +#else // _WIN32 + String::Value key(property); + String::Value val(value); + WCHAR* key_ptr = reinterpret_cast<WCHAR*>(*key); + // Environment variables that start with '=' are read-only. + if (key_ptr[0] != L'=') { + SetEnvironmentVariableW(key_ptr, reinterpret_cast<WCHAR*>(*val)); } - delete [] pair; #endif - - return value; + // Whether it worked or not, always return rval. + return scope.Close(value); } static Handle<Integer> EnvQuery(Local<String> property, const AccessorInfo& info) { + HandleScope scope; +#ifdef __POSIX__ String::Utf8Value key(property); if (getenv(*key)) { - HandleScope scope; 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, const AccessorInfo& info) { HandleScope scope; - - String::Utf8Value key(property); - - if (getenv(*key)) { #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 - int n = key.length() + 2; - char* pair = new char[n]; - snprintf(pair, n, "%s=", *key); - int r = _putenv(pair); - if (r) { - fprintf(stderr, "error unsetenv: '%s'\n", pair); - } - delete [] pair; -#endif - return True(); + String::Value key(property); + WCHAR* key_ptr = reinterpret_cast<WCHAR*>(*key); + if (key_ptr[0] == L'=' || !SetEnvironmentVariableW(key_ptr, NULL)) { + // Deletion failed. Return true if the key wasn't there in the first place, + // false if it is still there. + bool rv = GetEnvironmentVariableW(key_ptr, NULL, NULL) == 0 && + GetLastError() != ERROR_SUCCESS; + return scope.Close(Boolean::New(rv)); } - - return False(); +#endif + // It worked + return v8::True(); } static Handle<Array> EnvEnumerator(const AccessorInfo& info) { HandleScope scope; - +#ifdef __POSIX__ int size = 0; while (environ[size]) size++; @@ -1968,7 +1930,32 @@ static Handle<Array> EnvEnumerator(const AccessorInfo& info) { const int length = s ? s - var : strlen(var); 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); } @@ -2127,10 +2114,6 @@ Handle<Object> SetupProcessObject(int argc, char *argv[]) { NODE_SET_METHOD(process, "chdir", Chdir); NODE_SET_METHOD(process, "cwd", Cwd); -#ifdef _WIN32 - NODE_SET_METHOD(process, "_cwdForDrive", CwdForDrive); -#endif - NODE_SET_METHOD(process, "umask", Umask); #ifdef __POSIX__ @@ -2416,13 +2399,14 @@ DWORD WINAPI EnableDebugThreadProc(void* arg) { } -static int GetDebugSignalHandlerMappingName(DWORD pid, char* buf, size_t buf_len) { - return snprintf(buf, buf_len, "node-debug-handler-%u", pid); +static int GetDebugSignalHandlerMappingName(DWORD pid, wchar_t* buf, + size_t buf_len) { + return _snwprintf(buf, buf_len, L"node-debug-handler-%u", pid); } static int RegisterDebugSignalHandler() { - char mapping_name[32]; + wchar_t mapping_name[32]; HANDLE mapping_handle; DWORD pid; LPTHREAD_START_ROUTINE* handler; @@ -2431,11 +2415,11 @@ static int RegisterDebugSignalHandler() { if (GetDebugSignalHandlerMappingName(pid, mapping_name, - sizeof mapping_name) < 0) { + ARRAY_SIZE(mapping_name)) < 0) { return -1; } - mapping_handle = CreateFileMappingA(INVALID_HANDLE_VALUE, + mapping_handle = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, @@ -2445,11 +2429,12 @@ static int RegisterDebugSignalHandler() { return -1; } - handler = (LPTHREAD_START_ROUTINE*) MapViewOfFile(mapping_handle, - FILE_MAP_ALL_ACCESS, - 0, - 0, - sizeof *handler); + handler = reinterpret_cast<LPTHREAD_START_ROUTINE*>( + MapViewOfFile(mapping_handle, + FILE_MAP_ALL_ACCESS, + 0, + 0, + sizeof *handler)); if (handler == NULL) { CloseHandle(mapping_handle); return -1; @@ -2470,7 +2455,7 @@ static Handle<Value> DebugProcess(const Arguments& args) { HANDLE process = NULL; HANDLE thread = NULL; HANDLE mapping = NULL; - char mapping_name[32]; + wchar_t mapping_name[32]; LPTHREAD_START_ROUTINE* handler = NULL; if (args.Length() != 1) { @@ -2492,22 +2477,24 @@ static Handle<Value> DebugProcess(const Arguments& args) { if (GetDebugSignalHandlerMappingName(pid, mapping_name, - sizeof mapping_name) < 0) { + ARRAY_SIZE(mapping_name)) < 0) { rv = ThrowException(ErrnoException(errno, "sprintf")); goto out; } - mapping = OpenFileMapping(FILE_MAP_READ, FALSE, mapping_name); + mapping = OpenFileMappingW(FILE_MAP_READ, FALSE, mapping_name); if (mapping == NULL) { - rv = ThrowException(WinapiErrnoException(GetLastError(), "sprintf")); + rv = ThrowException(WinapiErrnoException(GetLastError(), + "OpenFileMappingW")); goto out; } - handler = (LPTHREAD_START_ROUTINE*) MapViewOfFile(mapping, - FILE_MAP_READ, - 0, - 0, - sizeof *handler); + handler = reinterpret_cast<LPTHREAD_START_ROUTINE*>( + MapViewOfFile(mapping, + FILE_MAP_READ, + 0, + 0, + sizeof *handler)); if (handler == NULL || *handler == NULL) { rv = ThrowException(WinapiErrnoException(GetLastError(), "MapViewOfFile")); goto out; diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 41bb2bd40e..b011d52061 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -908,6 +908,8 @@ Handle<Value> Connection::New(const Arguments& args) { SSL_set_app_data(p->ssl_, p); + if (is_server) SSL_set_info_callback(p->ssl_, SSLInfoCallback); + #ifdef OPENSSL_NPN_NEGOTIATED if (is_server) { // 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) { HandleScope scope; diff --git a/src/node_crypto.h b/src/node_crypto.h index 4cde964da4..87a5340147 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -190,6 +190,8 @@ class Connection : ObjectWrap { } private: + static void SSLInfoCallback(const SSL *ssl, int where, int ret); + BIO *bio_read_; BIO *bio_write_; SSL *ssl_; diff --git a/src/node_main.cc b/src/node_main.cc index 3100149868..dba8b69203 100644 --- a/src/node_main.cc +++ b/src/node_main.cc @@ -21,6 +21,47 @@ #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[]) { return node::Start(argc, argv); } +#endif diff --git a/src/pipe_wrap.cc b/src/pipe_wrap.cc index 6c3887d84f..c99fe47397 100644 --- a/src/pipe_wrap.cc +++ b/src/pipe_wrap.cc @@ -204,10 +204,7 @@ void PipeWrap::OnConnection(uv_stream_t* handle, int status) { PipeWrap* client_wrap = static_cast<PipeWrap*>(client_obj->GetPointerFromInternalField(0)); - int r = uv_accept(handle, (uv_stream_t*)&client_wrap->handle_); - - // uv_accept should always work. - assert(r == 0); + if (uv_accept(handle, (uv_stream_t*)&client_wrap->handle_)) return; // Successful accept. Call the onconnection callback in JavaScript land. Local<Value> argv[1] = { client_obj }; diff --git a/src/tcp_wrap.cc b/src/tcp_wrap.cc index 688d7d7190..14e6d3ed66 100644 --- a/src/tcp_wrap.cc +++ b/src/tcp_wrap.cc @@ -366,10 +366,7 @@ void TCPWrap::OnConnection(uv_stream_t* handle, int status) { TCPWrap* client_wrap = static_cast<TCPWrap*>(client_obj->GetPointerFromInternalField(0)); - int r = uv_accept(handle, (uv_stream_t*)&client_wrap->handle_); - - // uv_accept should always work. - assert(r == 0); + if (uv_accept(handle, (uv_stream_t*)&client_wrap->handle_)) return; // Successful accept. Call the onconnection callback in JavaScript land. argv[0] = client_obj; diff --git a/test/pummel/test-tls-ci-reneg-attack.js b/test/pummel/test-tls-ci-reneg-attack.js new file mode 100644 index 0000000000..5d04a6b6f3 --- /dev/null +++ b/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); + } + }); +} diff --git a/test/simple/test-cluster-bind-twice.js b/test/simple/test-cluster-bind-twice.js deleted file mode 100644 index 068842fa53..0000000000 --- a/test/simple/test-cluster-bind-twice.js +++ /dev/null @@ -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 -} diff --git a/test/simple/test-dgram-close.js b/test/simple/test-dgram-close.js new file mode 100644 index 0000000000..58d7aa786c --- /dev/null +++ b/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(); diff --git a/test/simple/test-http-server-multiheaders.js b/test/simple/test-http-server-multiheaders.js index 9917dd2bba..a94ea27321 100644 --- a/test/simple/test-http-server-multiheaders.js +++ b/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['x-foo'], 'bingo'); 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.end('EOF'); @@ -57,7 +59,12 @@ srv.listen(common.PORT, function() { ['WWW-AUTHENTICATE', 'baz'], ['x-foo', 'bingo'], ['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'] ] }); }); diff --git a/test/simple/test-repl-tab-complete.js b/test/simple/test-repl-tab-complete.js index ba511046b5..0bc43ab196 100644 --- a/test/simple/test-repl-tab-complete.js +++ b/test/simple/test-repl-tab-complete.js @@ -180,3 +180,12 @@ testMe.complete('inner.o', function(error, data) { 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' ]); +}); diff --git a/test/simple/test-tls-over-http-tunnel.js b/test/simple/test-tls-over-http-tunnel.js new file mode 100644 index 0000000000..d2b8257e92 --- /dev/null +++ b/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); +}); diff --git a/vcbuild.bat b/vcbuild.bat index aa0b83664d..54f579dce1 100644 --- a/vcbuild.bat +++ b/vcbuild.bat @@ -26,26 +26,27 @@ set upload= :next-arg if "%1"=="" goto args-done -if /i "%1"=="debug" set config=Debug&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"=="ia32" 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"=="noprojgen" set noprojgen=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"=="nosnapshot" set nosnapshot=1&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-pummel" set test=test-pummel&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-all" set test=test-all&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"=="upload" set upload=1&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"=="clean" set target=Clean&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"=="x64" set target_arch=x64&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"=="nosign" set nosign=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-internet" set test=test-internet&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-message" set test=test-message&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"=="msi" set msi=1&goto arg-ok +if /i "%1"=="upload" set upload=1&goto arg-ok + +echo Warning: ignoring invalid command line option `%1`. :arg-ok shift |