forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			1284 lines
		
	
	
	
		
			35 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1284 lines
		
	
	
	
		
			35 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^ws$" }] */
 | |
| 
 | |
| 'use strict';
 | |
| 
 | |
| const assert = require('assert');
 | |
| const crypto = require('crypto');
 | |
| const https = require('https');
 | |
| const http = require('http');
 | |
| const path = require('path');
 | |
| const net = require('net');
 | |
| const fs = require('fs');
 | |
| const os = require('os');
 | |
| 
 | |
| const Sender = require('../lib/sender');
 | |
| const WebSocket = require('..');
 | |
| const { NOOP } = require('../lib/constants');
 | |
| 
 | |
| describe('WebSocketServer', () => {
 | |
|   describe('#ctor', () => {
 | |
|     it('throws an error if no option object is passed', () => {
 | |
|       assert.throws(
 | |
|         () => new WebSocket.Server(),
 | |
|         new RegExp(
 | |
|           '^TypeError: One and only one of the "port", "server", or ' +
 | |
|             '"noServer" options must be specified$'
 | |
|         )
 | |
|       );
 | |
|     });
 | |
| 
 | |
|     describe('options', () => {
 | |
|       it('throws an error if required options are not specified', () => {
 | |
|         assert.throws(
 | |
|           () => new WebSocket.Server({}),
 | |
|           new RegExp(
 | |
|             '^TypeError: One and only one of the "port", "server", or ' +
 | |
|               '"noServer" options must be specified$'
 | |
|           )
 | |
|         );
 | |
|       });
 | |
| 
 | |
|       it('throws an error if mutually exclusive options are specified', () => {
 | |
|         const server = http.createServer();
 | |
|         const variants = [
 | |
|           { port: 0, noServer: true, server },
 | |
|           { port: 0, noServer: true },
 | |
|           { port: 0, server },
 | |
|           { noServer: true, server }
 | |
|         ];
 | |
| 
 | |
|         for (const options of variants) {
 | |
|           assert.throws(
 | |
|             () => new WebSocket.Server(options),
 | |
|             new RegExp(
 | |
|               '^TypeError: One and only one of the "port", "server", or ' +
 | |
|                 '"noServer" options must be specified$'
 | |
|             )
 | |
|           );
 | |
|         }
 | |
|       });
 | |
| 
 | |
|       it('exposes options passed to constructor', (done) => {
 | |
|         const wss = new WebSocket.Server({ port: 0 }, () => {
 | |
|           assert.strictEqual(wss.options.port, 0);
 | |
|           wss.close(done);
 | |
|         });
 | |
|       });
 | |
| 
 | |
|       it('accepts the `maxPayload` option', (done) => {
 | |
|         const maxPayload = 20480;
 | |
|         const wss = new WebSocket.Server(
 | |
|           {
 | |
|             perMessageDeflate: true,
 | |
|             maxPayload,
 | |
|             port: 0
 | |
|           },
 | |
|           () => {
 | |
|             const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
 | |
| 
 | |
|             ws.on('open', ws.close);
 | |
|           }
 | |
|         );
 | |
| 
 | |
|         wss.on('connection', (ws) => {
 | |
|           assert.strictEqual(ws._receiver._maxPayload, maxPayload);
 | |
|           assert.strictEqual(
 | |
|             ws._receiver._extensions['permessage-deflate']._maxPayload,
 | |
|             maxPayload
 | |
|           );
 | |
|           wss.close(done);
 | |
|         });
 | |
|       });
 | |
| 
 | |
|       it('honors the `WebSocket` option', (done) => {
 | |
|         class CustomWebSocket extends WebSocket.WebSocket {
 | |
|           get foo() {
 | |
|             return 'foo';
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         const wss = new WebSocket.Server(
 | |
|           {
 | |
|             port: 0,
 | |
|             WebSocket: CustomWebSocket
 | |
|           },
 | |
|           () => {
 | |
|             const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
 | |
| 
 | |
|             ws.on('open', ws.close);
 | |
|           }
 | |
|         );
 | |
| 
 | |
|         wss.on('connection', (ws) => {
 | |
|           assert.ok(ws instanceof CustomWebSocket);
 | |
|           assert.strictEqual(ws.foo, 'foo');
 | |
|           wss.close(done);
 | |
|         });
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it('emits an error if http server bind fails', (done) => {
 | |
|       const wss1 = new WebSocket.Server({ port: 0 }, () => {
 | |
|         const wss2 = new WebSocket.Server({
 | |
|           port: wss1.address().port
 | |
|         });
 | |
| 
 | |
|         wss2.on('error', () => wss1.close(done));
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it('starts a server on a given port', (done) => {
 | |
|       const port = 1337;
 | |
|       const wss = new WebSocket.Server({ port }, () => {
 | |
|         const ws = new WebSocket(`ws://localhost:${port}`);
 | |
| 
 | |
|         ws.on('open', ws.close);
 | |
|       });
 | |
| 
 | |
|       wss.on('connection', () => wss.close(done));
 | |
|     });
 | |
| 
 | |
|     it('binds the server on any IPv6 address when available', (done) => {
 | |
|       const wss = new WebSocket.Server({ port: 0 }, () => {
 | |
|         assert.strictEqual(wss._server.address().address, '::');
 | |
|         wss.close(done);
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it('uses a precreated http server', (done) => {
 | |
|       const server = http.createServer();
 | |
| 
 | |
|       server.listen(0, () => {
 | |
|         const wss = new WebSocket.Server({ server });
 | |
| 
 | |
|         wss.on('connection', () => {
 | |
|           server.close(done);
 | |
|         });
 | |
| 
 | |
|         const ws = new WebSocket(`ws://localhost:${server.address().port}`);
 | |
| 
 | |
|         ws.on('open', ws.close);
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it('426s for non-Upgrade requests', (done) => {
 | |
|       const wss = new WebSocket.Server({ port: 0 }, () => {
 | |
|         http.get(`http://localhost:${wss.address().port}`, (res) => {
 | |
|           let body = '';
 | |
| 
 | |
|           assert.strictEqual(res.statusCode, 426);
 | |
|           res.on('data', (chunk) => {
 | |
|             body += chunk;
 | |
|           });
 | |
|           res.on('end', () => {
 | |
|             assert.strictEqual(body, http.STATUS_CODES[426]);
 | |
|             wss.close(done);
 | |
|           });
 | |
|         });
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it('uses a precreated http server listening on unix socket', function (done) {
 | |
|       //
 | |
|       // Skip this test on Windows. The URL parser:
 | |
|       //
 | |
|       // - Throws an error if the named pipe uses backward slashes.
 | |
|       // - Incorrectly parses the path if the named pipe uses forward slashes.
 | |
|       //
 | |
|       if (process.platform === 'win32') return this.skip();
 | |
| 
 | |
|       const server = http.createServer();
 | |
|       const sockPath = path.join(
 | |
|         os.tmpdir(),
 | |
|         `ws.${crypto.randomBytes(16).toString('hex')}.sock`
 | |
|       );
 | |
| 
 | |
|       server.listen(sockPath, () => {
 | |
|         const wss = new WebSocket.Server({ server });
 | |
| 
 | |
|         wss.on('connection', (ws, req) => {
 | |
|           if (wss.clients.size === 1) {
 | |
|             assert.strictEqual(req.url, '/foo?bar=bar');
 | |
|           } else {
 | |
|             assert.strictEqual(req.url, '/');
 | |
| 
 | |
|             for (const client of wss.clients) {
 | |
|               client.close();
 | |
|             }
 | |
| 
 | |
|             server.close(done);
 | |
|           }
 | |
|         });
 | |
| 
 | |
|         const ws = new WebSocket(`ws+unix://${sockPath}:/foo?bar=bar`);
 | |
|         ws.on('open', () => new WebSocket(`ws+unix://${sockPath}`));
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('#address', () => {
 | |
|     it('returns the address of the server', (done) => {
 | |
|       const wss = new WebSocket.Server({ port: 0 }, () => {
 | |
|         const addr = wss.address();
 | |
| 
 | |
|         assert.deepStrictEqual(addr, wss._server.address());
 | |
|         wss.close(done);
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it('throws an error when operating in "noServer" mode', () => {
 | |
|       const wss = new WebSocket.Server({ noServer: true });
 | |
| 
 | |
|       assert.throws(() => {
 | |
|         wss.address();
 | |
|       }, /^Error: The server is operating in "noServer" mode$/);
 | |
|     });
 | |
| 
 | |
|     it('returns `null` if called after close', (done) => {
 | |
|       const wss = new WebSocket.Server({ port: 0 }, () => {
 | |
|         wss.close(() => {
 | |
|           assert.strictEqual(wss.address(), null);
 | |
|           done();
 | |
|         });
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('#close', () => {
 | |
|     it('does not throw if called multiple times', (done) => {
 | |
|       const wss = new WebSocket.Server({ port: 0 }, () => {
 | |
|         wss.on('close', done);
 | |
| 
 | |
|         wss.close();
 | |
|         wss.close();
 | |
|         wss.close();
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it("doesn't close a precreated server", (done) => {
 | |
|       const server = http.createServer();
 | |
|       const realClose = server.close;
 | |
| 
 | |
|       server.close = () => {
 | |
|         done(new Error('Must not close pre-created server'));
 | |
|       };
 | |
| 
 | |
|       const wss = new WebSocket.Server({ server });
 | |
| 
 | |
|       wss.on('connection', () => {
 | |
|         wss.close();
 | |
|         server.close = realClose;
 | |
|         server.close(done);
 | |
|       });
 | |
| 
 | |
|       server.listen(0, () => {
 | |
|         const ws = new WebSocket(`ws://localhost:${server.address().port}`);
 | |
| 
 | |
|         ws.on('open', ws.close);
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it('invokes the callback in noServer mode', (done) => {
 | |
|       const wss = new WebSocket.Server({ noServer: true });
 | |
| 
 | |
|       wss.close(done);
 | |
|     });
 | |
| 
 | |
|     it('cleans event handlers on precreated server', (done) => {
 | |
|       const server = http.createServer();
 | |
|       const wss = new WebSocket.Server({ server });
 | |
| 
 | |
|       server.listen(0, () => {
 | |
|         wss.close(() => {
 | |
|           assert.strictEqual(server.listenerCount('listening'), 0);
 | |
|           assert.strictEqual(server.listenerCount('upgrade'), 0);
 | |
|           assert.strictEqual(server.listenerCount('error'), 0);
 | |
| 
 | |
|           server.close(done);
 | |
|         });
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it("emits the 'close' event after the server closes", (done) => {
 | |
|       let serverCloseEventEmitted = false;
 | |
| 
 | |
|       const wss = new WebSocket.Server({ port: 0 }, () => {
 | |
|         net.createConnection({ port: wss.address().port });
 | |
|       });
 | |
| 
 | |
|       wss._server.on('connection', (socket) => {
 | |
|         wss.close();
 | |
| 
 | |
|         //
 | |
|         // The server is closing. Ensure this does not emit a `'close'`
 | |
|         // event before the server is actually closed.
 | |
|         //
 | |
|         wss.close();
 | |
| 
 | |
|         process.nextTick(() => {
 | |
|           socket.end();
 | |
|         });
 | |
|       });
 | |
| 
 | |
|       wss._server.on('close', () => {
 | |
|         serverCloseEventEmitted = true;
 | |
|       });
 | |
| 
 | |
|       wss.on('close', () => {
 | |
|         assert.ok(serverCloseEventEmitted);
 | |
|         done();
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it("emits the 'close' event if client tracking is disabled", (done) => {
 | |
|       const wss = new WebSocket.Server({
 | |
|         noServer: true,
 | |
|         clientTracking: false
 | |
|       });
 | |
| 
 | |
|       wss.on('close', done);
 | |
|       wss.close();
 | |
|     });
 | |
| 
 | |
|     it('calls the callback if the server is already closed', (done) => {
 | |
|       const wss = new WebSocket.Server({ port: 0 }, () => {
 | |
|         wss.close(() => {
 | |
|           assert.strictEqual(wss._state, 2);
 | |
| 
 | |
|           wss.close((err) => {
 | |
|             assert.ok(err instanceof Error);
 | |
|             assert.strictEqual(err.message, 'The server is not running');
 | |
|             done();
 | |
|           });
 | |
|         });
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it("emits the 'close' event if the server is already closed", (done) => {
 | |
|       const wss = new WebSocket.Server({ port: 0 }, () => {
 | |
|         wss.close(() => {
 | |
|           assert.strictEqual(wss._state, 2);
 | |
| 
 | |
|           wss.on('close', done);
 | |
|           wss.close();
 | |
|         });
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('#clients', () => {
 | |
|     it('returns a list of connected clients', (done) => {
 | |
|       const wss = new WebSocket.Server({ port: 0 }, () => {
 | |
|         assert.strictEqual(wss.clients.size, 0);
 | |
| 
 | |
|         const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
 | |
| 
 | |
|         ws.on('open', ws.close);
 | |
|       });
 | |
| 
 | |
|       wss.on('connection', () => {
 | |
|         assert.strictEqual(wss.clients.size, 1);
 | |
|         wss.close(done);
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it('can be disabled', (done) => {
 | |
|       const wss = new WebSocket.Server(
 | |
|         { port: 0, clientTracking: false },
 | |
|         () => {
 | |
|           assert.strictEqual(wss.clients, undefined);
 | |
|           const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
 | |
| 
 | |
|           ws.on('open', () => ws.close());
 | |
|         }
 | |
|       );
 | |
| 
 | |
|       wss.on('connection', (ws) => {
 | |
|         assert.strictEqual(wss.clients, undefined);
 | |
|         ws.on('close', () => wss.close(done));
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it('is updated when client terminates the connection', (done) => {
 | |
|       const wss = new WebSocket.Server({ port: 0 }, () => {
 | |
|         const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
 | |
| 
 | |
|         ws.on('open', () => ws.terminate());
 | |
|       });
 | |
| 
 | |
|       wss.on('connection', (ws) => {
 | |
|         ws.on('close', () => {
 | |
|           assert.strictEqual(wss.clients.size, 0);
 | |
|           wss.close(done);
 | |
|         });
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it('is updated when client closes the connection', (done) => {
 | |
|       const wss = new WebSocket.Server({ port: 0 }, () => {
 | |
|         const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
 | |
| 
 | |
|         ws.on('open', () => ws.close());
 | |
|       });
 | |
| 
 | |
|       wss.on('connection', (ws) => {
 | |
|         ws.on('close', () => {
 | |
|           assert.strictEqual(wss.clients.size, 0);
 | |
|           wss.close(done);
 | |
|         });
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('#shouldHandle', () => {
 | |
|     it('returns true when the path matches', () => {
 | |
|       const wss = new WebSocket.Server({ noServer: true, path: '/foo' });
 | |
| 
 | |
|       assert.strictEqual(wss.shouldHandle({ url: '/foo' }), true);
 | |
|       assert.strictEqual(wss.shouldHandle({ url: '/foo?bar=baz' }), true);
 | |
|     });
 | |
| 
 | |
|     it("returns false when the path doesn't match", () => {
 | |
|       const wss = new WebSocket.Server({ noServer: true, path: '/foo' });
 | |
| 
 | |
|       assert.strictEqual(wss.shouldHandle({ url: '/bar' }), false);
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('#handleUpgrade', () => {
 | |
|     it('can be used for a pre-existing server', (done) => {
 | |
|       const server = http.createServer();
 | |
| 
 | |
|       server.listen(0, () => {
 | |
|         const wss = new WebSocket.Server({ noServer: true });
 | |
| 
 | |
|         server.on('upgrade', (req, socket, head) => {
 | |
|           wss.handleUpgrade(req, socket, head, (ws) => {
 | |
|             ws.send('hello');
 | |
|             ws.close();
 | |
|           });
 | |
|         });
 | |
| 
 | |
|         const ws = new WebSocket(`ws://localhost:${server.address().port}`);
 | |
| 
 | |
|         ws.on('message', (message, isBinary) => {
 | |
|           assert.deepStrictEqual(message, Buffer.from('hello'));
 | |
|           assert.ok(!isBinary);
 | |
|           server.close(done);
 | |
|         });
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it("closes the connection when path doesn't match", (done) => {
 | |
|       const wss = new WebSocket.Server({ port: 0, path: '/ws' }, () => {
 | |
|         const req = http.get({
 | |
|           port: wss.address().port,
 | |
|           headers: {
 | |
|             Connection: 'Upgrade',
 | |
|             Upgrade: 'websocket',
 | |
|             'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==',
 | |
|             'Sec-WebSocket-Version': 13
 | |
|           }
 | |
|         });
 | |
| 
 | |
|         req.on('response', (res) => {
 | |
|           assert.strictEqual(res.statusCode, 400);
 | |
|           wss.close(done);
 | |
|         });
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it('closes the connection when protocol version is Hixie-76', (done) => {
 | |
|       const wss = new WebSocket.Server({ port: 0 }, () => {
 | |
|         const req = http.get({
 | |
|           port: wss.address().port,
 | |
|           headers: {
 | |
|             Connection: 'Upgrade',
 | |
|             Upgrade: 'WebSocket',
 | |
|             'Sec-WebSocket-Key1': '4 @1  46546xW%0l 1 5',
 | |
|             'Sec-WebSocket-Key2': '12998 5 Y3 1  .P00',
 | |
|             'Sec-WebSocket-Protocol': 'sample'
 | |
|           }
 | |
|         });
 | |
| 
 | |
|         req.on('response', (res) => {
 | |
|           assert.strictEqual(res.statusCode, 400);
 | |
| 
 | |
|           const chunks = [];
 | |
| 
 | |
|           res.on('data', (chunk) => {
 | |
|             chunks.push(chunk);
 | |
|           });
 | |
| 
 | |
|           res.on('end', () => {
 | |
|             assert.strictEqual(
 | |
|               Buffer.concat(chunks).toString(),
 | |
|               'Missing or invalid Sec-WebSocket-Key header'
 | |
|             );
 | |
|             wss.close(done);
 | |
|           });
 | |
|         });
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('#completeUpgrade', () => {
 | |
|     it('throws an error if called twice with the same socket', (done) => {
 | |
|       const server = http.createServer();
 | |
| 
 | |
|       server.listen(0, () => {
 | |
|         const wss = new WebSocket.Server({ noServer: true });
 | |
| 
 | |
|         server.on('upgrade', (req, socket, head) => {
 | |
|           wss.handleUpgrade(req, socket, head, (ws) => {
 | |
|             ws.close();
 | |
|           });
 | |
|           assert.throws(
 | |
|             () => wss.handleUpgrade(req, socket, head, NOOP),
 | |
|             (err) => {
 | |
|               assert.ok(err instanceof Error);
 | |
|               assert.strictEqual(
 | |
|                 err.message,
 | |
|                 'server.handleUpgrade() was called more than once with the ' +
 | |
|                   'same socket, possibly due to a misconfiguration'
 | |
|               );
 | |
|               return true;
 | |
|             }
 | |
|           );
 | |
|         });
 | |
| 
 | |
|         const ws = new WebSocket(`ws://localhost:${server.address().port}`);
 | |
| 
 | |
|         ws.on('open', () => {
 | |
|           ws.on('close', () => {
 | |
|             server.close(done);
 | |
|           });
 | |
|         });
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('Connection establishing', () => {
 | |
|     it('fails if the HTTP method is not GET', (done) => {
 | |
|       const wss = new WebSocket.Server({ port: 0 }, () => {
 | |
|         const req = http.request({
 | |
|           method: 'POST',
 | |
|           port: wss.address().port,
 | |
|           headers: {
 | |
|             Connection: 'Upgrade',
 | |
|             Upgrade: 'websocket'
 | |
|           }
 | |
|         });
 | |
| 
 | |
|         req.on('response', (res) => {
 | |
|           assert.strictEqual(res.statusCode, 405);
 | |
| 
 | |
|           const chunks = [];
 | |
| 
 | |
|           res.on('data', (chunk) => {
 | |
|             chunks.push(chunk);
 | |
|           });
 | |
| 
 | |
|           res.on('end', () => {
 | |
|             assert.strictEqual(
 | |
|               Buffer.concat(chunks).toString(),
 | |
|               'Invalid HTTP method'
 | |
|             );
 | |
|             wss.close(done);
 | |
|           });
 | |
|         });
 | |
| 
 | |
|         req.end();
 | |
|       });
 | |
| 
 | |
|       wss.on('connection', () => {
 | |
|         done(new Error("Unexpected 'connection' event"));
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it('fails if the Upgrade header field value is not "websocket"', (done) => {
 | |
|       const wss = new WebSocket.Server({ port: 0 }, () => {
 | |
|         const req = http.get({
 | |
|           port: wss.address().port,
 | |
|           headers: {
 | |
|             Connection: 'Upgrade',
 | |
|             Upgrade: 'foo'
 | |
|           }
 | |
|         });
 | |
| 
 | |
|         req.on('response', (res) => {
 | |
|           assert.strictEqual(res.statusCode, 400);
 | |
| 
 | |
|           const chunks = [];
 | |
| 
 | |
|           res.on('data', (chunk) => {
 | |
|             chunks.push(chunk);
 | |
|           });
 | |
| 
 | |
|           res.on('end', () => {
 | |
|             assert.strictEqual(
 | |
|               Buffer.concat(chunks).toString(),
 | |
|               'Invalid Upgrade header'
 | |
|             );
 | |
|             wss.close(done);
 | |
|           });
 | |
|         });
 | |
|       });
 | |
| 
 | |
|       wss.on('connection', () => {
 | |
|         done(new Error("Unexpected 'connection' event"));
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it('fails if the Sec-WebSocket-Key header is invalid (1/2)', (done) => {
 | |
|       const wss = new WebSocket.Server({ port: 0 }, () => {
 | |
|         const req = http.get({
 | |
|           port: wss.address().port,
 | |
|           headers: {
 | |
|             Connection: 'Upgrade',
 | |
|             Upgrade: 'websocket'
 | |
|           }
 | |
|         });
 | |
| 
 | |
|         req.on('response', (res) => {
 | |
|           assert.strictEqual(res.statusCode, 400);
 | |
| 
 | |
|           const chunks = [];
 | |
| 
 | |
|           res.on('data', (chunk) => {
 | |
|             chunks.push(chunk);
 | |
|           });
 | |
| 
 | |
|           res.on('end', () => {
 | |
|             assert.strictEqual(
 | |
|               Buffer.concat(chunks).toString(),
 | |
|               'Missing or invalid Sec-WebSocket-Key header'
 | |
|             );
 | |
|             wss.close(done);
 | |
|           });
 | |
|         });
 | |
|       });
 | |
| 
 | |
|       wss.on('connection', () => {
 | |
|         done(new Error("Unexpected 'connection' event"));
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it('fails if the Sec-WebSocket-Key header is invalid (2/2)', (done) => {
 | |
|       const wss = new WebSocket.Server({ port: 0 }, () => {
 | |
|         const req = http.get({
 | |
|           port: wss.address().port,
 | |
|           headers: {
 | |
|             Connection: 'Upgrade',
 | |
|             Upgrade: 'websocket',
 | |
|             'Sec-WebSocket-Key': 'P5l8BJcZwRc='
 | |
|           }
 | |
|         });
 | |
| 
 | |
|         req.on('response', (res) => {
 | |
|           assert.strictEqual(res.statusCode, 400);
 | |
| 
 | |
|           const chunks = [];
 | |
| 
 | |
|           res.on('data', (chunk) => {
 | |
|             chunks.push(chunk);
 | |
|           });
 | |
| 
 | |
|           res.on('end', () => {
 | |
|             assert.strictEqual(
 | |
|               Buffer.concat(chunks).toString(),
 | |
|               'Missing or invalid Sec-WebSocket-Key header'
 | |
|             );
 | |
|             wss.close(done);
 | |
|           });
 | |
|         });
 | |
|       });
 | |
| 
 | |
|       wss.on('connection', () => {
 | |
|         done(new Error("Unexpected 'connection' event"));
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it('fails if the Sec-WebSocket-Version header is invalid (1/2)', (done) => {
 | |
|       const wss = new WebSocket.Server({ port: 0 }, () => {
 | |
|         const req = http.get({
 | |
|           port: wss.address().port,
 | |
|           headers: {
 | |
|             Connection: 'Upgrade',
 | |
|             Upgrade: 'websocket',
 | |
|             'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ=='
 | |
|           }
 | |
|         });
 | |
| 
 | |
|         req.on('response', (res) => {
 | |
|           assert.strictEqual(res.statusCode, 400);
 | |
| 
 | |
|           const chunks = [];
 | |
| 
 | |
|           res.on('data', (chunk) => {
 | |
|             chunks.push(chunk);
 | |
|           });
 | |
| 
 | |
|           res.on('end', () => {
 | |
|             assert.strictEqual(
 | |
|               Buffer.concat(chunks).toString(),
 | |
|               'Missing or invalid Sec-WebSocket-Version header'
 | |
|             );
 | |
|             wss.close(done);
 | |
|           });
 | |
|         });
 | |
|       });
 | |
| 
 | |
|       wss.on('connection', () => {
 | |
|         done(new Error("Unexpected 'connection' event"));
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it('fails if the Sec-WebSocket-Version header is invalid (2/2)', (done) => {
 | |
|       const wss = new WebSocket.Server({ port: 0 }, () => {
 | |
|         const req = http.get({
 | |
|           port: wss.address().port,
 | |
|           headers: {
 | |
|             Connection: 'Upgrade',
 | |
|             Upgrade: 'websocket',
 | |
|             'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==',
 | |
|             'Sec-WebSocket-Version': 12
 | |
|           }
 | |
|         });
 | |
| 
 | |
|         req.on('response', (res) => {
 | |
|           assert.strictEqual(res.statusCode, 400);
 | |
| 
 | |
|           const chunks = [];
 | |
| 
 | |
|           res.on('data', (chunk) => {
 | |
|             chunks.push(chunk);
 | |
|           });
 | |
| 
 | |
|           res.on('end', () => {
 | |
|             assert.strictEqual(
 | |
|               Buffer.concat(chunks).toString(),
 | |
|               'Missing or invalid Sec-WebSocket-Version header'
 | |
|             );
 | |
|             wss.close(done);
 | |
|           });
 | |
|         });
 | |
|       });
 | |
| 
 | |
|       wss.on('connection', () => {
 | |
|         done(new Error("Unexpected 'connection' event"));
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it('fails is the Sec-WebSocket-Protocol header is invalid', (done) => {
 | |
|       const wss = new WebSocket.Server({ port: 0 }, () => {
 | |
|         const req = http.get({
 | |
|           port: wss.address().port,
 | |
|           headers: {
 | |
|             Connection: 'Upgrade',
 | |
|             Upgrade: 'websocket',
 | |
|             'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==',
 | |
|             'Sec-WebSocket-Version': 13,
 | |
|             'Sec-WebSocket-Protocol': 'foo;bar'
 | |
|           }
 | |
|         });
 | |
| 
 | |
|         req.on('response', (res) => {
 | |
|           assert.strictEqual(res.statusCode, 400);
 | |
| 
 | |
|           const chunks = [];
 | |
| 
 | |
|           res.on('data', (chunk) => {
 | |
|             chunks.push(chunk);
 | |
|           });
 | |
| 
 | |
|           res.on('end', () => {
 | |
|             assert.strictEqual(
 | |
|               Buffer.concat(chunks).toString(),
 | |
|               'Invalid Sec-WebSocket-Protocol header'
 | |
|             );
 | |
|             wss.close(done);
 | |
|           });
 | |
|         });
 | |
|       });
 | |
| 
 | |
|       wss.on('connection', () => {
 | |
|         done(new Error("Unexpected 'connection' event"));
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it('fails if the Sec-WebSocket-Extensions header is invalid', (done) => {
 | |
|       const wss = new WebSocket.Server(
 | |
|         {
 | |
|           perMessageDeflate: true,
 | |
|           port: 0
 | |
|         },
 | |
|         () => {
 | |
|           const req = http.get({
 | |
|             port: wss.address().port,
 | |
|             headers: {
 | |
|               Connection: 'Upgrade',
 | |
|               Upgrade: 'websocket',
 | |
|               'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==',
 | |
|               'Sec-WebSocket-Version': 13,
 | |
|               'Sec-WebSocket-Extensions':
 | |
|                 'permessage-deflate; server_max_window_bits=foo'
 | |
|             }
 | |
|           });
 | |
| 
 | |
|           req.on('response', (res) => {
 | |
|             assert.strictEqual(res.statusCode, 400);
 | |
| 
 | |
|             const chunks = [];
 | |
| 
 | |
|             res.on('data', (chunk) => {
 | |
|               chunks.push(chunk);
 | |
|             });
 | |
| 
 | |
|             res.on('end', () => {
 | |
|               assert.strictEqual(
 | |
|                 Buffer.concat(chunks).toString(),
 | |
|                 'Invalid or unacceptable Sec-WebSocket-Extensions header'
 | |
|               );
 | |
|               wss.close(done);
 | |
|             });
 | |
|           });
 | |
|         }
 | |
|       );
 | |
| 
 | |
|       wss.on('connection', () => {
 | |
|         done(new Error("Unexpected 'connection' event"));
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it("emits the 'wsClientError' event", (done) => {
 | |
|       const wss = new WebSocket.Server({ port: 0 }, () => {
 | |
|         const req = http.request({
 | |
|           method: 'POST',
 | |
|           port: wss.address().port,
 | |
|           headers: {
 | |
|             Connection: 'Upgrade',
 | |
|             Upgrade: 'websocket'
 | |
|           }
 | |
|         });
 | |
| 
 | |
|         req.on('response', (res) => {
 | |
|           assert.strictEqual(res.statusCode, 400);
 | |
|           wss.close(done);
 | |
|         });
 | |
| 
 | |
|         req.end();
 | |
|       });
 | |
| 
 | |
|       wss.on('wsClientError', (err, socket, request) => {
 | |
|         assert.ok(err instanceof Error);
 | |
|         assert.strictEqual(err.message, 'Invalid HTTP method');
 | |
| 
 | |
|         assert.ok(request instanceof http.IncomingMessage);
 | |
|         assert.strictEqual(request.method, 'POST');
 | |
| 
 | |
|         socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
 | |
|       });
 | |
| 
 | |
|       wss.on('connection', () => {
 | |
|         done(new Error("Unexpected 'connection' event"));
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it('fails if the WebSocket server is closing or closed', (done) => {
 | |
|       const server = http.createServer();
 | |
|       const wss = new WebSocket.Server({ noServer: true });
 | |
| 
 | |
|       server.on('upgrade', (req, socket, head) => {
 | |
|         wss.close();
 | |
|         wss.handleUpgrade(req, socket, head, () => {
 | |
|           done(new Error('Unexpected callback invocation'));
 | |
|         });
 | |
|       });
 | |
| 
 | |
|       server.listen(0, () => {
 | |
|         const ws = new WebSocket(`ws://localhost:${server.address().port}`);
 | |
| 
 | |
|         ws.on('unexpected-response', (req, res) => {
 | |
|           assert.strictEqual(res.statusCode, 503);
 | |
|           res.resume();
 | |
|           server.close(done);
 | |
|         });
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it('handles unsupported extensions', (done) => {
 | |
|       const wss = new WebSocket.Server(
 | |
|         {
 | |
|           perMessageDeflate: true,
 | |
|           port: 0
 | |
|         },
 | |
|         () => {
 | |
|           const req = http.get({
 | |
|             port: wss.address().port,
 | |
|             headers: {
 | |
|               Connection: 'Upgrade',
 | |
|               Upgrade: 'websocket',
 | |
|               'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==',
 | |
|               'Sec-WebSocket-Version': 13,
 | |
|               'Sec-WebSocket-Extensions': 'foo; bar'
 | |
|             }
 | |
|           });
 | |
| 
 | |
|           req.on('upgrade', (res, socket, head) => {
 | |
|             if (head.length) socket.unshift(head);
 | |
| 
 | |
|             socket.once('data', (chunk) => {
 | |
|               assert.strictEqual(chunk[0], 0x88);
 | |
|               socket.destroy();
 | |
|               wss.close(done);
 | |
|             });
 | |
|           });
 | |
|         }
 | |
|       );
 | |
| 
 | |
|       wss.on('connection', (ws) => {
 | |
|         assert.strictEqual(ws.extensions, '');
 | |
|         ws.close();
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     describe('`verifyClient`', () => {
 | |
|       it('can reject client synchronously', (done) => {
 | |
|         const wss = new WebSocket.Server(
 | |
|           {
 | |
|             verifyClient: () => false,
 | |
|             port: 0
 | |
|           },
 | |
|           () => {
 | |
|             const req = http.get({
 | |
|               port: wss.address().port,
 | |
|               headers: {
 | |
|                 Connection: 'Upgrade',
 | |
|                 Upgrade: 'websocket',
 | |
|                 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==',
 | |
|                 'Sec-WebSocket-Version': 8
 | |
|               }
 | |
|             });
 | |
| 
 | |
|             req.on('response', (res) => {
 | |
|               assert.strictEqual(res.statusCode, 401);
 | |
|               wss.close(done);
 | |
|             });
 | |
|           }
 | |
|         );
 | |
| 
 | |
|         wss.on('connection', () => {
 | |
|           done(new Error("Unexpected 'connection' event"));
 | |
|         });
 | |
|       });
 | |
| 
 | |
|       it('can accept client synchronously', (done) => {
 | |
|         const server = https.createServer({
 | |
|           cert: fs.readFileSync('test/fixtures/certificate.pem'),
 | |
|           key: fs.readFileSync('test/fixtures/key.pem')
 | |
|         });
 | |
| 
 | |
|         const wss = new WebSocket.Server({
 | |
|           verifyClient: (info) => {
 | |
|             assert.strictEqual(info.origin, 'https://example.com');
 | |
|             assert.strictEqual(info.req.headers.foo, 'bar');
 | |
|             assert.ok(info.secure, true);
 | |
|             return true;
 | |
|           },
 | |
|           server
 | |
|         });
 | |
| 
 | |
|         wss.on('connection', () => {
 | |
|           server.close(done);
 | |
|         });
 | |
| 
 | |
|         server.listen(0, () => {
 | |
|           const ws = new WebSocket(`wss://localhost:${server.address().port}`, {
 | |
|             headers: { Origin: 'https://example.com', foo: 'bar' },
 | |
|             rejectUnauthorized: false
 | |
|           });
 | |
| 
 | |
|           ws.on('open', ws.close);
 | |
|         });
 | |
|       });
 | |
| 
 | |
|       it('can accept client asynchronously', (done) => {
 | |
|         const wss = new WebSocket.Server(
 | |
|           {
 | |
|             verifyClient: (o, cb) => process.nextTick(cb, true),
 | |
|             port: 0
 | |
|           },
 | |
|           () => {
 | |
|             const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
 | |
| 
 | |
|             ws.on('open', ws.close);
 | |
|           }
 | |
|         );
 | |
| 
 | |
|         wss.on('connection', () => wss.close(done));
 | |
|       });
 | |
| 
 | |
|       it('can reject client asynchronously', (done) => {
 | |
|         const wss = new WebSocket.Server(
 | |
|           {
 | |
|             verifyClient: (info, cb) => process.nextTick(cb, false),
 | |
|             port: 0
 | |
|           },
 | |
|           () => {
 | |
|             const req = http.get({
 | |
|               port: wss.address().port,
 | |
|               headers: {
 | |
|                 Connection: 'Upgrade',
 | |
|                 Upgrade: 'websocket',
 | |
|                 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==',
 | |
|                 'Sec-WebSocket-Version': 8
 | |
|               }
 | |
|             });
 | |
| 
 | |
|             req.on('response', (res) => {
 | |
|               assert.strictEqual(res.statusCode, 401);
 | |
|               wss.close(done);
 | |
|             });
 | |
|           }
 | |
|         );
 | |
| 
 | |
|         wss.on('connection', () => {
 | |
|           done(new Error("Unexpected 'connection' event"));
 | |
|         });
 | |
|       });
 | |
| 
 | |
|       it('can reject client asynchronously w/ status code', (done) => {
 | |
|         const wss = new WebSocket.Server(
 | |
|           {
 | |
|             verifyClient: (info, cb) => process.nextTick(cb, false, 404),
 | |
|             port: 0
 | |
|           },
 | |
|           () => {
 | |
|             const req = http.get({
 | |
|               port: wss.address().port,
 | |
|               headers: {
 | |
|                 Connection: 'Upgrade',
 | |
|                 Upgrade: 'websocket',
 | |
|                 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==',
 | |
|                 'Sec-WebSocket-Version': 8
 | |
|               }
 | |
|             });
 | |
| 
 | |
|             req.on('response', (res) => {
 | |
|               assert.strictEqual(res.statusCode, 404);
 | |
|               wss.close(done);
 | |
|             });
 | |
|           }
 | |
|         );
 | |
| 
 | |
|         wss.on('connection', () => {
 | |
|           done(new Error("Unexpected 'connection' event"));
 | |
|         });
 | |
|       });
 | |
| 
 | |
|       it('can reject client asynchronously w/ custom headers', (done) => {
 | |
|         const wss = new WebSocket.Server(
 | |
|           {
 | |
|             verifyClient: (info, cb) => {
 | |
|               process.nextTick(cb, false, 503, '', { 'Retry-After': 120 });
 | |
|             },
 | |
|             port: 0
 | |
|           },
 | |
|           () => {
 | |
|             const req = http.get({
 | |
|               port: wss.address().port,
 | |
|               headers: {
 | |
|                 Connection: 'Upgrade',
 | |
|                 Upgrade: 'websocket',
 | |
|                 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==',
 | |
|                 'Sec-WebSocket-Version': 8
 | |
|               }
 | |
|             });
 | |
| 
 | |
|             req.on('response', (res) => {
 | |
|               assert.strictEqual(res.statusCode, 503);
 | |
|               assert.strictEqual(res.headers['retry-after'], '120');
 | |
|               wss.close(done);
 | |
|             });
 | |
|           }
 | |
|         );
 | |
| 
 | |
|         wss.on('connection', () => {
 | |
|           done(new Error("Unexpected 'connection' event"));
 | |
|         });
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it("doesn't emit the 'connection' event if socket is closed prematurely", (done) => {
 | |
|       const server = http.createServer();
 | |
| 
 | |
|       server.listen(0, () => {
 | |
|         const wss = new WebSocket.Server({
 | |
|           verifyClient: ({ req: { socket } }, cb) => {
 | |
|             assert.strictEqual(socket.readable, true);
 | |
|             assert.strictEqual(socket.writable, true);
 | |
| 
 | |
|             socket.on('end', () => {
 | |
|               assert.strictEqual(socket.readable, false);
 | |
|               assert.strictEqual(socket.writable, true);
 | |
|               cb(true);
 | |
|             });
 | |
|           },
 | |
|           server
 | |
|         });
 | |
| 
 | |
|         wss.on('connection', () => {
 | |
|           done(new Error("Unexpected 'connection' event"));
 | |
|         });
 | |
| 
 | |
|         const socket = net.connect(
 | |
|           {
 | |
|             port: server.address().port,
 | |
|             allowHalfOpen: true
 | |
|           },
 | |
|           () => {
 | |
|             socket.end(
 | |
|               [
 | |
|                 'GET / HTTP/1.1',
 | |
|                 'Host: localhost',
 | |
|                 'Upgrade: websocket',
 | |
|                 'Connection: Upgrade',
 | |
|                 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==',
 | |
|                 'Sec-WebSocket-Version: 13',
 | |
|                 '\r\n'
 | |
|               ].join('\r\n')
 | |
|             );
 | |
|           }
 | |
|         );
 | |
| 
 | |
|         socket.on('end', () => {
 | |
|           wss.close();
 | |
|           server.close(done);
 | |
|         });
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it('handles data passed along with the upgrade request', (done) => {
 | |
|       const wss = new WebSocket.Server({ port: 0 }, () => {
 | |
|         const req = http.request({
 | |
|           port: wss.address().port,
 | |
|           headers: {
 | |
|             Connection: 'Upgrade',
 | |
|             Upgrade: 'websocket',
 | |
|             'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==',
 | |
|             'Sec-WebSocket-Version': 13
 | |
|           }
 | |
|         });
 | |
| 
 | |
|         const list = Sender.frame(Buffer.from('Hello'), {
 | |
|           fin: true,
 | |
|           rsv1: false,
 | |
|           opcode: 0x01,
 | |
|           mask: true,
 | |
|           readOnly: false
 | |
|         });
 | |
| 
 | |
|         req.write(Buffer.concat(list));
 | |
|         req.end();
 | |
|       });
 | |
| 
 | |
|       wss.on('connection', (ws) => {
 | |
|         ws.on('message', (data, isBinary) => {
 | |
|           assert.deepStrictEqual(data, Buffer.from('Hello'));
 | |
|           assert.ok(!isBinary);
 | |
|           wss.close(done);
 | |
|         });
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     describe('`handleProtocols`', () => {
 | |
|       it('allows to select a subprotocol', (done) => {
 | |
|         const handleProtocols = (protocols, request) => {
 | |
|           assert.ok(request instanceof http.IncomingMessage);
 | |
|           assert.strictEqual(request.url, '/');
 | |
|           return Array.from(protocols).pop();
 | |
|         };
 | |
|         const wss = new WebSocket.Server({ handleProtocols, port: 0 }, () => {
 | |
|           const ws = new WebSocket(`ws://localhost:${wss.address().port}`, [
 | |
|             'foo',
 | |
|             'bar'
 | |
|           ]);
 | |
| 
 | |
|           ws.on('open', () => {
 | |
|             assert.strictEqual(ws.protocol, 'bar');
 | |
|             wss.close(done);
 | |
|           });
 | |
|         });
 | |
| 
 | |
|         wss.on('connection', (ws) => {
 | |
|           ws.close();
 | |
|         });
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it("emits the 'headers' event", (done) => {
 | |
|       const wss = new WebSocket.Server({ port: 0 }, () => {
 | |
|         const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
 | |
| 
 | |
|         ws.on('open', ws.close);
 | |
|       });
 | |
| 
 | |
|       wss.on('headers', (headers, request) => {
 | |
|         assert.deepStrictEqual(headers.slice(0, 3), [
 | |
|           'HTTP/1.1 101 Switching Protocols',
 | |
|           'Upgrade: websocket',
 | |
|           'Connection: Upgrade'
 | |
|         ]);
 | |
|         assert.ok(request instanceof http.IncomingMessage);
 | |
|         assert.strictEqual(request.url, '/');
 | |
| 
 | |
|         wss.on('connection', () => wss.close(done));
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('permessage-deflate', () => {
 | |
|     it('is disabled by default', (done) => {
 | |
|       const wss = new WebSocket.Server({ port: 0 }, () => {
 | |
|         const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
 | |
| 
 | |
|         ws.on('open', ws.close);
 | |
|       });
 | |
| 
 | |
|       wss.on('connection', (ws, req) => {
 | |
|         assert.strictEqual(
 | |
|           req.headers['sec-websocket-extensions'],
 | |
|           'permessage-deflate; client_max_window_bits'
 | |
|         );
 | |
|         assert.strictEqual(ws.extensions, '');
 | |
|         wss.close(done);
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it('uses configuration options', (done) => {
 | |
|       const wss = new WebSocket.Server(
 | |
|         {
 | |
|           perMessageDeflate: { clientMaxWindowBits: 8 },
 | |
|           port: 0
 | |
|         },
 | |
|         () => {
 | |
|           const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
 | |
| 
 | |
|           ws.on('upgrade', (res) => {
 | |
|             assert.strictEqual(
 | |
|               res.headers['sec-websocket-extensions'],
 | |
|               'permessage-deflate; client_max_window_bits=8'
 | |
|             );
 | |
| 
 | |
|             wss.close(done);
 | |
|           });
 | |
|         }
 | |
|       );
 | |
| 
 | |
|       wss.on('connection', (ws) => {
 | |
|         ws.close();
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| });
 | 
