diff --git a/AUTHORS b/AUTHORS index 509e8be9f7e..e5c82011f09 100644 --- a/AUTHORS +++ b/AUTHORS @@ -263,3 +263,6 @@ Dan VerWeire Matthew Fitzsimmons Philip Tellis Christopher Jeffrey +Paddy Byers +Seth Fitzsimmons +Einar Otto Stangvik diff --git a/ChangeLog b/ChangeLog index 6ef1ef1a1b0..b5306ae9cc3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -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 18a892168fb..c1429660eb8 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 bc7cfdf0bfa..1922f2039b9 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 f2bfb67f512..087cd48b80e 100644 --- a/doc/about/index.html +++ b/doc/about/index.html @@ -1,5 +1,5 @@ - + - - - - - + + - node.js @@ -88,10 +85,7 @@

Copyright Joyent, Inc., Node.js is a trademark of Joyent, Inc., View License

- - - - - - - + + diff --git a/lib/dgram.js b/lib/dgram.js index b5b0473de83..8a88364bb55 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 a0745f0b99e..d800895a5ad 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 ff04cc67623..0ca51e84f38 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 aeb51110eba..7bbc6d1448f 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 430ef2f259c..d951ef358b0 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; - self.encrypted.writable = self.encrypted.readable = false; - self.cleartext.writable = self.cleartext.readable = false; + if (this.ssl.timer) { + clearTimeout(this.ssl.timer); + this.ssl.timer = null; + } - process.nextTick(function() { - self.cleartext.emit('end'); - self.encrypted.emit('close'); - self.cleartext.emit('close'); - }); + this.ssl.error = null; + this.ssl.close(); + this.ssl = null; + + 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'); + }); + } }; diff --git a/node.gyp b/node.gyp index 474de0e931d..24f579e054e 100644 --- a/node.gyp +++ b/node.gyp @@ -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 9a2d8faeb1b..42a2f8c0d71 100644 --- a/src/node.cc +++ b/src/node.cc @@ -1290,76 +1290,6 @@ static Handle Cwd(const Arguments& args) { } -#ifdef _WIN32 -static Handle CwdForDrive(const Arguments& args) { - HandleScope scope; - - if (args.Length() < 1) { - Local exception = Exception::Error( - String::New("process._cwdForDrive takes exactly 1 argument.")); - return ThrowException(exception); - } - - Local driveLetter = args[0]->ToString(); - if (driveLetter->Length() != 1) { - Local 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 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 cwd = String::Concat(String::New(&drive, 1), - String::New(":\\")); - return scope.Close(cwd); - - } else if (len == 0) { - // Error - Local exception = Exception::Error( - String::New(winapi_strerror(GetLastError()))); - return ThrowException(exception); - } - - WCHAR* buffer = new WCHAR[len]; - if (buffer == NULL) { - Local 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 exception = Exception::Error( - String::New(winapi_strerror(GetLastError()))); - return ThrowException(exception); - } - - Local cwd = String::New(reinterpret_cast(buffer), len2); - delete[] buffer; - return scope.Close(cwd); -} -#endif - - static Handle Umask(const Arguments& args) { HandleScope scope; unsigned int old; @@ -1883,12 +1813,28 @@ static void ProcessTitleSetter(Local property, static Handle EnvGetter(Local 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(*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(buffer), result)); + } +#endif + // Not found return Undefined(); } @@ -1897,66 +1843,82 @@ static Handle EnvSetter(Local property, Local 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(*key); + // Environment variables that start with '=' are read-only. + if (key_ptr[0] != L'=') { + SetEnvironmentVariableW(key_ptr, reinterpret_cast(*val)); } - delete [] pair; #endif - - return value; + // Whether it worked or not, always return rval. + return scope.Close(value); } static Handle EnvQuery(Local 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(); +#else // _WIN32 + String::Value key(property); + WCHAR* key_ptr = reinterpret_cast(*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()); } static Handle EnvDeleter(Local 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(*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 EnvEnumerator(const AccessorInfo& info) { HandleScope scope; - +#ifdef __POSIX__ int size = 0; while (environ[size]) size++; @@ -1968,7 +1930,32 @@ static Handle 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()); + } + Local 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(p), s - p)); + p = s + wcslen(s) + 1; + } + FreeEnvironmentStringsW(environment); +#endif return scope.Close(env); } @@ -2127,10 +2114,6 @@ Handle 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( + 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 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 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( + 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 41bb2bd40ea..b011d520613 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -908,6 +908,8 @@ Handle 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 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(SSL_get_app_data(ssl)); + MakeCallback(c->handle_, "onhandshakestart", 0, NULL); + } + if (where & SSL_CB_HANDSHAKE_DONE) { + HandleScope scope; + Connection* c = static_cast(SSL_get_app_data(ssl)); + MakeCallback(c->handle_, "onhandshakedone", 0, NULL); + } +} + + Handle Connection::EncIn(const Arguments& args) { HandleScope scope; diff --git a/src/node_crypto.h b/src/node_crypto.h index 4cde964da46..87a5340147c 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 31001498683..dba8b69203b 100644 --- a/src/node_main.cc +++ b/src/node_main.cc @@ -21,6 +21,47 @@ #include +#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 6c3887d84f6..c99fe473976 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(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 argv[1] = { client_obj }; diff --git a/src/tcp_wrap.cc b/src/tcp_wrap.cc index 688d7d71902..14e6d3ed66b 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(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 00000000000..5d04a6b6f36 --- /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 068842fa535..00000000000 --- 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 00000000000..58d7aa786c9 --- /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 9917dd2bba7..a94ea27321e 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 ba511046b51..0bc43ab196a 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 00000000000..d2b8257e92e --- /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 aa0b83664d2..54f579dce1c 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