@httptoolkit/httpolyglot 2.2.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,4 +1,7 @@
1
+ MIT License
2
+
1
3
  Copyright 2014 Brian White. All rights reserved.
4
+ Copyright (c) 2019-2025 HTTP Toolkit
2
5
 
3
6
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
7
  of this software and associated documentation files (the "Software"), to
package/README.md CHANGED
@@ -12,11 +12,13 @@ Forked from the original [`httpolyglot`](https://github.com/mscdex/httpolyglot)
12
12
  * Dropping support for very old versions of Node (and thereby simplifying the code somewhat)
13
13
  * Converting to TypeScript
14
14
  * Event subscription support (subscribe to `server.on(x, ...)` to hear about `x` from _all_ internal servers - HTTP/2, HTTP/1, TLS and net)
15
+ * Adding support for SOCKS connections
16
+ * Adding support for custom handling of unknown protocols
15
17
 
16
18
  Requirements
17
19
  ============
18
20
 
19
- * [node.js](http://nodejs.org/) -- v12.0.0 or newer
21
+ * [node.js](http://nodejs.org/) -- v20 or newer
20
22
 
21
23
 
22
24
  Install
@@ -31,12 +33,14 @@ Examples
31
33
  * Simple usage:
32
34
 
33
35
  ```javascript
34
- const httpolyglot = require('@httptoolkit/httpolyglot');
35
- const fs = require('fs');
36
+ import * as httpolyglot from '@httptoolkit/httpolyglot';
37
+ import * as fs from 'fs';
36
38
 
37
39
  httpolyglot.createServer({
38
- key: fs.readFileSync('server.key'),
39
- cert: fs.readFileSync('server.crt')
40
+ tls: {
41
+ key: fs.readFileSync('server.key'),
42
+ cert: fs.readFileSync('server.crt')
43
+ }
40
44
  }, function(req, res) {
41
45
  res.writeHead(200, { 'Content-Type': 'text/plain' });
42
46
  res.end((req.socket.encrypted ? 'HTTPS' : 'HTTP') + ' Connection!');
@@ -49,12 +53,14 @@ httpolyglot.createServer({
49
53
  * Simple redirect of all http connections to https:
50
54
 
51
55
  ```javascript
52
- const httpolyglot = require('@httptoolkit/httpolyglot');
53
- const fs = require('fs');
56
+ import * as httpolyglot from '@httptoolkit/httpolyglot';
57
+ import * as fs from 'fs';
54
58
 
55
59
  httpolyglot.createServer({
56
- key: fs.readFileSync('server.key'),
57
- cert: fs.readFileSync('server.crt')
60
+ tls: {
61
+ key: fs.readFileSync('server.key'),
62
+ cert: fs.readFileSync('server.crt')
63
+ }
58
64
  }, function(req, res) {
59
65
  if (!req.socket.encrypted) {
60
66
  res.writeHead(301, { 'Location': 'https://localhost:9000' });
@@ -72,14 +78,17 @@ httpolyglot.createServer({
72
78
  API
73
79
  ===
74
80
 
75
- Exports
76
- -------
81
+ * **createServer**([< _object_ >config, ]< _function_ >requestListener) - _Server_ - Creates and returns a new Server instance.
82
+
83
+ If no config is provided, this server handles HTTP/1 & HTTP/2 in plain text on the same port.
77
84
 
78
- * **Server** - A class similar to https.Server (except instances have `setTimeout()` from http.Server).
85
+ If a config is provided, it can contain:
79
86
 
80
- * **createServer**(< _object_ >tlsConfig[, < _function_ >requestListener]) - _Server_ - Creates and returns a new Server instance.
87
+ - `tls` - Either TLS configuration options for [`tls.createServer`](https://nodejs.org/api/tls.html#tlscreateserveroptions-secureconnectionlistener), or an existing `tls.Server` instance. Setting this option enables TLS, so that HTTP/1 & HTTP/2 are accepted over both plain-text & encrypted (HTTPS) connections on the same port. If configuration options are provided, Httpolyglot will handle TLS automatically. If a server is provided, the connection will be passed to it (by emitting the `connection` event) and Httpolyglot will wait for the server to emit `secureConnection` (the default TLS server event) to handle the content within.
88
+ - `socks` - A `net.Server` instance, which will handle any incoming SOCKS connections. If this is provided, SOCKSv4 and SOCKSv5 connections will be emitted as `connection` events on this server. If not, all SOCKS connections will be treated like any other unknown protocol.
89
+ - `unknownProtocol` - A `net.Server` instance, which will handle any unknown protocols. If this is provided, unrecognized protocols will be emitted as `connection` events on this server. If it's not provided, these connections will be passed to the HTTP server by default, which will typically result in a `clientError` event and a 400 HTTP response being sent to the client.
81
90
 
82
91
  How it Works
83
92
  ============
84
93
 
85
- TLS and HTTP connections are easy to distinguish based on the first byte sent by clients trying to connect. See [this comment](https://github.com/mscdex/httpolyglot/issues/3#issuecomment-173680155) for more information.
94
+ TLS, HTTP, HTTP/2, SOCKS and other connections are easy to distinguish based on the first byte sent by clients trying to connect. See [this comment](https://github.com/mscdex/httpolyglot/issues/3#issuecomment-173680155) for more information.
package/dist/index.d.ts CHANGED
@@ -2,60 +2,59 @@
2
2
  /// <reference types="node" />
3
3
  /// <reference types="node" />
4
4
  /// <reference types="node" />
5
- /// <reference types="node" />
6
5
  import * as net from 'net';
7
6
  import * as tls from 'tls';
8
7
  import * as http from 'http';
9
8
  import * as https from 'https';
10
- declare module 'net' {
11
- interface Socket {
12
- /**
13
- * Only preserved for types backward compat - always undefined in new releases.
14
- *
15
- * @deprecated
16
- */
17
- __httpPeekedData?: Buffer;
18
- }
19
- }
20
- export declare class Server extends net.Server {
21
- private _httpServer;
22
- private _http2Server;
23
- private _tlsServer;
9
+ export interface HttpolyglotOptions {
24
10
  /**
25
- * Create an Httpolyglot instance with just a request listener to support plain-text
26
- * HTTP & HTTP/2 on the same port, with incoming TLS connections being closed immediately.
11
+ * Enable support for incoming TLS connections. If a TLS configuration is provided, this will
12
+ * be used to instantiate a TLS server to handle the connections. If a TLS server is provided,
13
+ * then all incoming TLS connections will be emitted as 'connection' events on the given
14
+ * TLS server, and all 'secureConnection' events coming from the TLS server will be handled
15
+ * according to the connection type detected on that socket.
27
16
  */
28
- constructor(requestListener: http.RequestListener);
17
+ tls?: https.ServerOptions | tls.Server;
29
18
  /**
30
- * Call with a full TLS configuration to create a TLS+HTTP+HTTP/2 server, which can
31
- * support all protocols on the same port.
19
+ * A SOCKS server instance. This will allow incoming SOCKS connections, which will
20
+ * be emitted as 'connection' events on this server.
32
21
  */
33
- constructor(config: https.ServerOptions, requestListener: http.RequestListener);
22
+ socks?: net.Server;
34
23
  /**
35
- * Pass an existing TLS server, instead of TLS configuration, to create a TLS+HTTP+HTTP/2
36
- * server. All incoming TLS requests will be emitted as 'connection' events on the given
37
- * TLS server, and all 'secureConnection' events coming from the TLS server will be
38
- * handled according to the connection type detected on that socket.
24
+ * A custom handler for unknown protocols. If provided, any unrecognized protocol sockets will
25
+ * be emitted on this server as 'connection' events. If not provided, the default behavior is to
26
+ * pass the socket to the HTTP server, which will typically reject the connection as
27
+ * unparseable, with a 400 response.
39
28
  */
40
- constructor(tlsServer: tls.Server, requestListener: http.RequestListener);
29
+ unknownProtocol?: net.Server;
30
+ }
31
+ declare class Server extends net.Server {
32
+ private _httpServer;
33
+ private _http2Server;
34
+ private _tlsServer;
35
+ private _socksHandler;
36
+ private _unknownProtocolHandler;
37
+ constructor(config: HttpolyglotOptions, requestListener: http.RequestListener);
41
38
  private connectionListener;
42
39
  private tlsListener;
43
40
  private http2Listener;
44
41
  }
42
+ export type { Server };
45
43
  /**
46
44
  * Create an Httpolyglot instance with just a request listener to support plain-text
47
45
  * HTTP & HTTP/2 on the same port, with incoming TLS connections being closed immediately.
48
46
  */
49
47
  export declare function createServer(requestListener: http.RequestListener): Server;
50
48
  /**
51
- * Create an instance with a full TLS configuration to create a TLS+HTTP+HTTP/2 server, which can
52
- * support all protocols on the same port.
49
+ * Create an Httpolyglot server with advanced configuration, to enable TLS and/or SOCKS
50
+ * connections containing HTTP, accepting all protocols on the same port.
51
+ *
52
+ * The options can contain:
53
+ * - `tls`: A TLS configuration object, or an existing TLS server. If a TLS server is provided,
54
+ * all incoming TLS connections will be emitted as 'connection' events on the given TLS server,
55
+ * and all 'secureConnection' events coming from the TLS server will be handled according to
56
+ * the connection type (HTTP/1 or HTTP/2) detected on that socket.
57
+ * - `socks`: A SOCKS server instance. This will allow incoming SOCKS connections, which will
58
+ * be emitted as 'connection' events on this server.
53
59
  */
54
- export declare function createServer(tlsConfig: https.ServerOptions, requestListener: http.RequestListener): Server;
55
- /**
56
- * Create an instance around an existing TLS server, instead of TLS configuration, to create a
57
- * TLS+HTTP+HTTP/2 server with custom TLS handling. All incoming TLS requests will be emitted as
58
- * 'connection' events on the given TLS server, and all 'secureConnection' events coming from the
59
- * TLS server will be handled according to the connection type detected on that socket.
60
- */
61
- export declare function createServer(tlsServer: tls.Server, requestListener: http.RequestListener): Server;
60
+ export declare function createServer(config: HttpolyglotOptions, requestListener: http.RequestListener): Server;
package/dist/index.js CHANGED
@@ -1,59 +1,99 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createServer = exports.Server = void 0;
3
+ exports.createServer = void 0;
4
4
  const net = require("net");
5
5
  const tls = require("tls");
6
6
  const http = require("http");
7
7
  const http2 = require("http2");
8
8
  const events_1 = require("events");
9
+ // 0x16 first byte is very unlikely to be a false positive as TLS is so widely used & intermediated
10
+ const isTls = (data) => data[0] === 0x16; // SSLv3+ or TLS handshake
11
+ // H2 hello is effectively guaranteed not to be a false positive, as it's very so specific, but we
12
+ // need to wait for the full message to confirm this.
13
+ const HTTP2_PREFACE = Buffer.from('PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n');
14
+ const couldBeHttp2 = (data) => data[0] === HTTP2_PREFACE[0];
15
+ const isHttp2 = (data) => data.subarray(0, HTTP2_PREFACE.length).equals(HTTP2_PREFACE);
16
+ const H1_METHOD_PREFIXES = http.METHODS.map(m => m + ' ');
17
+ const H1_LONGEST_PREFIX = Math.max(...H1_METHOD_PREFIXES.map(m => m.length));
18
+ const couldBeHttp1 = (initialData) => {
19
+ const initialString = initialData.subarray(0, H1_LONGEST_PREFIX).toString('utf8');
20
+ for (let method of H1_METHOD_PREFIXES) {
21
+ const comparisonLength = Math.min(method.length, initialString.length);
22
+ if (initialString.slice(0, comparisonLength) === method.slice(0, comparisonLength)) {
23
+ return true;
24
+ }
25
+ }
26
+ return false;
27
+ };
28
+ // [0x4, 0x1|0x2] for SOCKS v4 seems fairly reliable. Some other protocols (Cassandra's CQL) use
29
+ // the first byte for versions similarly, but shouldn't conflict in practice AFAICT, and no
30
+ // other popular protocols actively match 0x4 that I can see.
31
+ const isSocksV4 = (data) => {
32
+ return data[0] === 0x04 && // Start of SOCKS v4 client hello
33
+ (data.byteLength > 1 && (data[1] === 0x1 || data[1] === 0x2)); // Any command sent is valid
34
+ };
35
+ // [0x5, len, len-bytes-of-auth-methods]. 0x5 doesn't have any visible conflicts either, and
36
+ // using len as a max-expected-length check should keep that fairly tight too.
37
+ const isSocksV5 = (data) => {
38
+ return data[0] === 0x05 && // Start of SOCKS v5 client hello
39
+ (data.byteLength > 1 && // If auth method length is present, it's non-zero and could match
40
+ data[1] > 0 && // (i.e. message isn't longer than it should be)
41
+ data.byteLength <= 2 + data[1]);
42
+ };
43
+ const isSocks = (data) => isSocksV4(data) || isSocksV5(data);
9
44
  function onError(err) { }
10
- const TLS_HANDSHAKE_BYTE = 0x16; // SSLv3+ or TLS handshake
11
- const HTTP2_PREFACE = 'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n';
12
- const HTTP2_PREFACE_BUFFER = Buffer.from(HTTP2_PREFACE);
13
- const NODE_MAJOR_VERSION = parseInt(process.version.slice(1).split('.')[0], 10);
14
45
  class Server extends net.Server {
15
- constructor(configOrServerOrListener, listener) {
46
+ _httpServer;
47
+ _http2Server;
48
+ _tlsServer;
49
+ _socksHandler;
50
+ _unknownProtocolHandler;
51
+ constructor(configOrListener, listener) {
16
52
  // We just act as a plain TCP server, accepting and examing
17
53
  // each connection, then passing it to the right subserver.
18
54
  super((socket) => this.connectionListener(socket));
19
- let tlsConfig;
20
- let tlsServer;
55
+ let config = {};
21
56
  let requestListener;
22
- if (typeof configOrServerOrListener === 'function') {
23
- requestListener = configOrServerOrListener;
24
- tlsConfig = undefined;
25
- }
26
- else if (configOrServerOrListener instanceof tls.Server) {
27
- tlsServer = configOrServerOrListener;
28
- requestListener = listener;
57
+ if (typeof configOrListener === 'function') {
58
+ requestListener = configOrListener;
29
59
  }
30
60
  else {
31
- tlsConfig = configOrServerOrListener;
61
+ config = configOrListener;
32
62
  requestListener = listener;
33
63
  }
34
64
  // We bind the request listener, so 'this' always refers to us, not each subserver.
35
65
  // This means 'this' is consistent (and this.close() works).
36
- const boundListener = requestListener.bind(this);
66
+ // Use `Function.prototype.bind` directly as frameworks like Express generate
67
+ // methods from `http.METHODS`, and `BIND` is an included HTTP method.
68
+ const boundListener = Function.prototype.bind.call(requestListener, this);
37
69
  // Create subservers for each supported protocol:
38
70
  this._httpServer = new http.Server(boundListener);
39
71
  this._http2Server = http2.createServer({}, boundListener);
40
- if (tlsServer) {
41
- // If we've been given a preconfigured TLS server, we use that directly, and
42
- // subscribe to connections there
43
- this._tlsServer = tlsServer;
44
- this._tlsServer.on('secureConnection', this.tlsListener.bind(this));
45
- }
46
- else if (typeof tlsConfig === 'object') {
47
- // If we have TLS config, create a TLS server, which will pass sockets to
48
- // the relevant subserver once the TLS connection is set up.
49
- this._tlsServer = new tls.Server(tlsConfig, this.tlsListener.bind(this));
72
+ if (config.tls) {
73
+ if (config.tls instanceof tls.Server) {
74
+ // If we've been given a preconfigured TLS server, we use that directly, and
75
+ // subscribe to connections there
76
+ this._tlsServer = config.tls;
77
+ this._tlsServer.on('secureConnection', this.tlsListener.bind(this));
78
+ }
79
+ else {
80
+ // If we have TLS config, create a TLS server, which will pass sockets to
81
+ // the relevant subserver once the TLS connection is set up.
82
+ this._tlsServer = new tls.Server(config.tls, this.tlsListener.bind(this));
83
+ }
50
84
  }
51
85
  else {
52
- // Fake server that rejects all connections:
86
+ // Fake TLS server that rejects all connections:
53
87
  this._tlsServer = new events_1.EventEmitter();
54
88
  this._tlsServer.on('connection', (socket) => socket.destroy());
55
89
  }
90
+ this._socksHandler = config.socks;
91
+ this._unknownProtocolHandler = config.unknownProtocol;
56
92
  const subServers = [this._httpServer, this._http2Server, this._tlsServer];
93
+ if (this._socksHandler)
94
+ subServers.push(this._socksHandler);
95
+ if (this._unknownProtocolHandler)
96
+ subServers.push(this._unknownProtocolHandler);
57
97
  // Proxy all event listeners setup onto the subservers, so any
58
98
  // subscriptions on this server are fed from all the subservers
59
99
  this.on('newListener', function (eventName, listener) {
@@ -79,21 +119,28 @@ class Server extends net.Server {
79
119
  else {
80
120
  socket.removeListener('error', onError);
81
121
  // Put the peeked data back into the socket
82
- const firstByte = data[0];
83
122
  socket.unshift(data);
84
123
  // Pass the socket to the correct subserver:
85
- if (firstByte === TLS_HANDSHAKE_BYTE) {
124
+ if (isTls(data)) {
86
125
  // TLS sockets don't allow half open
87
126
  socket.allowHalfOpen = false;
88
127
  this._tlsServer.emit('connection', socket);
89
128
  }
129
+ else if (this._socksHandler && isSocks(data)) {
130
+ this._socksHandler.emit('connection', socket);
131
+ }
90
132
  else {
91
- if (firstByte === HTTP2_PREFACE_BUFFER[0]) {
133
+ if (couldBeHttp2(data)) {
92
134
  // The connection _might_ be HTTP/2. To confirm, we need to keep
93
135
  // reading until we get the whole stream:
94
136
  this.http2Listener(socket);
95
137
  }
138
+ else if (this._unknownProtocolHandler && !couldBeHttp1(data)) {
139
+ this._unknownProtocolHandler.emit('connection', socket);
140
+ }
96
141
  else {
142
+ // We pass everything else to the HTTP server, which can handle requests
143
+ // and/or reject unparseable client-error connections as it sees fit.
97
144
  this._httpServer.emit('connection', socket);
98
145
  }
99
146
  }
@@ -115,22 +162,15 @@ class Server extends net.Server {
115
162
  const h2Server = this._http2Server;
116
163
  const newData = socket.read() || Buffer.from([]);
117
164
  const data = pastData ? Buffer.concat([pastData, newData]) : newData;
118
- if (data.length >= HTTP2_PREFACE_BUFFER.length) {
165
+ if (data.length >= HTTP2_PREFACE.length) {
119
166
  socket.unshift(data);
120
- if (data.slice(0, HTTP2_PREFACE_BUFFER.length).equals(HTTP2_PREFACE_BUFFER)) {
167
+ if (isHttp2(data)) {
121
168
  // We have a full match for the preface - it's definitely HTTP/2.
122
169
  // For HTTP/2 we hit issues when passing non-socket streams (like H2 streams for proxying H2-over-H2).
123
- if (NODE_MAJOR_VERSION <= 12) {
124
- // For Node 12 and older, we need a (later deprecated) stream wrapper:
125
- const StreamWrapper = require('_stream_wrap');
126
- socket = new StreamWrapper(socket);
127
- }
128
- else {
129
- // For newer node, we can fix this with a quick patch here:
130
- const socketWithInternals = socket;
131
- if (socketWithInternals._handle) {
132
- socketWithInternals._handle.isStreamBase = false;
133
- }
170
+ // Setting isStreamBase to false is an effective workaround for this in Node 14+
171
+ const socketWithInternals = socket;
172
+ if (socketWithInternals._handle) {
173
+ socketWithInternals._handle.isStreamBase = false;
134
174
  }
135
175
  h2Server.emit('connection', socket);
136
176
  return;
@@ -140,7 +180,7 @@ class Server extends net.Server {
140
180
  return;
141
181
  }
142
182
  }
143
- else if (!data.equals(HTTP2_PREFACE_BUFFER.slice(0, data.length))) {
183
+ else if (!data.equals(HTTP2_PREFACE.slice(0, data.length))) {
144
184
  socket.unshift(data);
145
185
  // Haven't finished the preface length, but something doesn't match already
146
186
  h1Server.emit('connection', socket);
@@ -154,9 +194,18 @@ class Server extends net.Server {
154
194
  });
155
195
  }
156
196
  }
157
- exports.Server = Server;
158
- function createServer(configOrServerOrListener, listener) {
159
- return new Server(configOrServerOrListener, listener);
197
+ function createServer(configOrListener, listener) {
198
+ let config;
199
+ let requestListener;
200
+ if (typeof configOrListener === 'function') {
201
+ config = {};
202
+ requestListener = configOrListener;
203
+ }
204
+ else {
205
+ config = configOrListener;
206
+ requestListener = listener;
207
+ }
208
+ return new Server(config, requestListener);
160
209
  }
161
210
  exports.createServer = createServer;
162
211
  ;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@httptoolkit/httpolyglot",
3
- "version": "2.2.1",
4
- "author": "Tim Perry <pimterry@gmail.com>",
3
+ "version": "3.0.0",
4
+ "author": "Tim Perry <tim@httptoolkit.com>",
5
5
  "description": "Serve http and https connections over the same port with node.js",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -17,7 +17,7 @@
17
17
  "test": "mocha -r ts-node/register 'test/**/*.spec.ts'"
18
18
  },
19
19
  "engines": {
20
- "node": ">=12.0.0"
20
+ "node": ">=20.0.0"
21
21
  },
22
22
  "keywords": [
23
23
  "http",
@@ -26,12 +26,7 @@
26
26
  "multiplex",
27
27
  "polyglot"
28
28
  ],
29
- "licenses": [
30
- {
31
- "type": "MIT",
32
- "url": "http://github.com/httptoolkit/httpolyglot/raw/master/LICENSE"
33
- }
34
- ],
29
+ "license": "MIT",
35
30
  "repository": {
36
31
  "type": "git",
37
32
  "url": "http://github.com/httptoolkit/httpolyglot.git"
@@ -45,6 +40,7 @@
45
40
  "chai": "^4.3.4",
46
41
  "mocha": "^9.1.1",
47
42
  "rimraf": "^3.0.2",
43
+ "socks": "^2.8.4",
48
44
  "ts-node": "^10.2.1",
49
45
  "typescript": "^4.4.2"
50
46
  }
package/src/index.ts CHANGED
@@ -6,54 +6,88 @@ import * as http2 from 'http2';
6
6
 
7
7
  import { EventEmitter } from 'events';
8
8
 
9
- declare module 'net' {
10
- interface Socket {
11
- /**
12
- * Only preserved for types backward compat - always undefined in new releases.
13
- *
14
- * @deprecated
15
- */
16
- __httpPeekedData?: Buffer;
17
- }
18
- }
19
9
 
20
- function onError(err: any) {}
10
+ // 0x16 first byte is very unlikely to be a false positive as TLS is so widely used & intermediated
11
+ const isTls = (data: Buffer) => data[0] === 0x16; // SSLv3+ or TLS handshake
21
12
 
22
- const TLS_HANDSHAKE_BYTE = 0x16; // SSLv3+ or TLS handshake
23
- const HTTP2_PREFACE = 'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n';
24
- const HTTP2_PREFACE_BUFFER = Buffer.from(HTTP2_PREFACE);
13
+ // H2 hello is effectively guaranteed not to be a false positive, as it's very so specific, but we
14
+ // need to wait for the full message to confirm this.
15
+ const HTTP2_PREFACE = Buffer.from('PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n');
16
+ const couldBeHttp2 = (data: Buffer) => data[0] === HTTP2_PREFACE[0];
17
+ const isHttp2 = (data: Buffer) => data.subarray(0, HTTP2_PREFACE.length).equals(HTTP2_PREFACE);
25
18
 
26
- const NODE_MAJOR_VERSION = parseInt(process.version.slice(1).split('.')[0], 10);
19
+ const H1_METHOD_PREFIXES = http.METHODS.map(m => m + ' ');
20
+ const H1_LONGEST_PREFIX = Math.max(...H1_METHOD_PREFIXES.map(m => m.length));
21
+ const couldBeHttp1 = (initialData: Buffer) => {
22
+ const initialString = initialData.subarray(0, H1_LONGEST_PREFIX).toString('utf8');
23
+ for (let method of H1_METHOD_PREFIXES) {
24
+ const comparisonLength = Math.min(method.length, initialString.length);
25
+ if (initialString.slice(0, comparisonLength) === method.slice(0, comparisonLength)) {
26
+ return true;
27
+ }
28
+ }
29
+ return false;
30
+ };
27
31
 
28
- type Http2Listener = (request: http2.Http2ServerRequest, response: http2.Http2ServerResponse) => void;
32
+ // [0x4, 0x1|0x2] for SOCKS v4 seems fairly reliable. Some other protocols (Cassandra's CQL) use
33
+ // the first byte for versions similarly, but shouldn't conflict in practice AFAICT, and no
34
+ // other popular protocols actively match 0x4 that I can see.
35
+ const isSocksV4 = (data: Buffer) => {
36
+ return data[0] === 0x04 && // Start of SOCKS v4 client hello
37
+ (data.byteLength > 1 && (data[1] === 0x1 || data[1] === 0x2)); // Any command sent is valid
38
+ }
39
+ // [0x5, len, len-bytes-of-auth-methods]. 0x5 doesn't have any visible conflicts either, and
40
+ // using len as a max-expected-length check should keep that fairly tight too.
41
+ const isSocksV5 = (data: Buffer) => {
42
+ return data[0] === 0x05 && // Start of SOCKS v5 client hello
43
+ (data.byteLength > 1 && // If auth method length is present, it's non-zero and could match
44
+ data[1] > 0 && // (i.e. message isn't longer than it should be)
45
+ data.byteLength <= 2 + data[1]
46
+ );
47
+ }
48
+ const isSocks = (data: Buffer) => isSocksV4(data) || isSocksV5(data);
29
49
 
30
- export class Server extends net.Server {
50
+ function onError(err: any) {}
31
51
 
32
- private _httpServer: http.Server;
33
- private _http2Server: http2.Http2Server;
34
- private _tlsServer: EventEmitter;
52
+ type Http2Listener = (request: http2.Http2ServerRequest, response: http2.Http2ServerResponse) => void;
35
53
 
54
+ export interface HttpolyglotOptions {
36
55
  /**
37
- * Create an Httpolyglot instance with just a request listener to support plain-text
38
- * HTTP & HTTP/2 on the same port, with incoming TLS connections being closed immediately.
56
+ * Enable support for incoming TLS connections. If a TLS configuration is provided, this will
57
+ * be used to instantiate a TLS server to handle the connections. If a TLS server is provided,
58
+ * then all incoming TLS connections will be emitted as 'connection' events on the given
59
+ * TLS server, and all 'secureConnection' events coming from the TLS server will be handled
60
+ * according to the connection type detected on that socket.
39
61
  */
40
- constructor(requestListener: http.RequestListener);
62
+ tls?: https.ServerOptions | tls.Server;
63
+
41
64
  /**
42
- * Call with a full TLS configuration to create a TLS+HTTP+HTTP/2 server, which can
43
- * support all protocols on the same port.
65
+ * A SOCKS server instance. This will allow incoming SOCKS connections, which will
66
+ * be emitted as 'connection' events on this server.
44
67
  */
45
- constructor(config: https.ServerOptions, requestListener: http.RequestListener);
68
+ socks?: net.Server;
69
+
46
70
  /**
47
- * Pass an existing TLS server, instead of TLS configuration, to create a TLS+HTTP+HTTP/2
48
- * server. All incoming TLS requests will be emitted as 'connection' events on the given
49
- * TLS server, and all 'secureConnection' events coming from the TLS server will be
50
- * handled according to the connection type detected on that socket.
71
+ * A custom handler for unknown protocols. If provided, any unrecognized protocol sockets will
72
+ * be emitted on this server as 'connection' events. If not provided, the default behavior is to
73
+ * pass the socket to the HTTP server, which will typically reject the connection as
74
+ * unparseable, with a 400 response.
51
75
  */
52
- constructor(tlsServer: tls.Server, requestListener: http.RequestListener);
76
+ unknownProtocol?: net.Server;
77
+ }
78
+
79
+ class Server extends net.Server {
80
+
81
+ private _httpServer: http.Server;
82
+ private _http2Server: http2.Http2Server;
83
+ private _tlsServer: EventEmitter;
84
+ private _socksHandler: EventEmitter | undefined;
85
+ private _unknownProtocolHandler: EventEmitter | undefined;
86
+
87
+ constructor(config: HttpolyglotOptions, requestListener: http.RequestListener);
53
88
  constructor(
54
- configOrServerOrListener:
55
- | https.ServerOptions
56
- | tls.Server
89
+ configOrListener:
90
+ | HttpolyglotOptions
57
91
  | http.RequestListener,
58
92
  listener?: http.RequestListener
59
93
  ) {
@@ -61,45 +95,49 @@ export class Server extends net.Server {
61
95
  // each connection, then passing it to the right subserver.
62
96
  super((socket) => this.connectionListener(socket));
63
97
 
64
- let tlsConfig: https.ServerOptions | undefined;
65
- let tlsServer: tls.Server | undefined;
98
+ let config: HttpolyglotOptions = {};
66
99
  let requestListener: http.RequestListener;
67
100
 
68
- if (typeof configOrServerOrListener === 'function') {
69
- requestListener = configOrServerOrListener;
70
- tlsConfig = undefined;
71
- } else if (configOrServerOrListener instanceof tls.Server) {
72
- tlsServer = configOrServerOrListener;
73
- requestListener = listener!;
101
+ if (typeof configOrListener === 'function') {
102
+ requestListener = configOrListener;
74
103
  } else {
75
- tlsConfig = configOrServerOrListener;
104
+ config = configOrListener;
76
105
  requestListener = listener!;
77
106
  }
78
107
 
79
108
  // We bind the request listener, so 'this' always refers to us, not each subserver.
80
109
  // This means 'this' is consistent (and this.close() works).
81
- const boundListener = requestListener.bind(this);
110
+ // Use `Function.prototype.bind` directly as frameworks like Express generate
111
+ // methods from `http.METHODS`, and `BIND` is an included HTTP method.
112
+ const boundListener = Function.prototype.bind.call(requestListener, this);
82
113
 
83
114
  // Create subservers for each supported protocol:
84
115
  this._httpServer = new http.Server(boundListener);
85
116
  this._http2Server = http2.createServer({}, boundListener as any as Http2Listener);
86
117
 
87
- if (tlsServer) {
88
- // If we've been given a preconfigured TLS server, we use that directly, and
89
- // subscribe to connections there
90
- this._tlsServer = tlsServer;
91
- this._tlsServer.on('secureConnection', this.tlsListener.bind(this));
92
- } else if (typeof tlsConfig === 'object') {
93
- // If we have TLS config, create a TLS server, which will pass sockets to
94
- // the relevant subserver once the TLS connection is set up.
95
- this._tlsServer = new tls.Server(tlsConfig, this.tlsListener.bind(this));
118
+ if (config.tls) {
119
+ if (config.tls instanceof tls.Server) {
120
+ // If we've been given a preconfigured TLS server, we use that directly, and
121
+ // subscribe to connections there
122
+ this._tlsServer = config.tls;
123
+ this._tlsServer.on('secureConnection', this.tlsListener.bind(this));
124
+ } else {
125
+ // If we have TLS config, create a TLS server, which will pass sockets to
126
+ // the relevant subserver once the TLS connection is set up.
127
+ this._tlsServer = new tls.Server(config.tls, this.tlsListener.bind(this));
128
+ }
96
129
  } else {
97
- // Fake server that rejects all connections:
130
+ // Fake TLS server that rejects all connections:
98
131
  this._tlsServer = new EventEmitter();
99
132
  this._tlsServer.on('connection', (socket) => socket.destroy());
100
133
  }
101
134
 
135
+ this._socksHandler = config.socks;
136
+ this._unknownProtocolHandler = config.unknownProtocol;
137
+
102
138
  const subServers = [this._httpServer, this._http2Server, this._tlsServer];
139
+ if (this._socksHandler) subServers.push(this._socksHandler);
140
+ if (this._unknownProtocolHandler) subServers.push(this._unknownProtocolHandler);
103
141
 
104
142
  // Proxy all event listeners setup onto the subservers, so any
105
143
  // subscriptions on this server are fed from all the subservers
@@ -130,20 +168,25 @@ export class Server extends net.Server {
130
168
  socket.removeListener('error', onError);
131
169
 
132
170
  // Put the peeked data back into the socket
133
- const firstByte = data[0];
134
171
  socket.unshift(data);
135
172
 
136
173
  // Pass the socket to the correct subserver:
137
- if (firstByte === TLS_HANDSHAKE_BYTE) {
174
+ if (isTls(data)) {
138
175
  // TLS sockets don't allow half open
139
176
  socket.allowHalfOpen = false;
140
177
  this._tlsServer.emit('connection', socket);
178
+ } else if (this._socksHandler && isSocks(data)) {
179
+ this._socksHandler.emit('connection', socket);
141
180
  } else {
142
- if (firstByte === HTTP2_PREFACE_BUFFER[0]) {
181
+ if (couldBeHttp2(data)) {
143
182
  // The connection _might_ be HTTP/2. To confirm, we need to keep
144
183
  // reading until we get the whole stream:
145
184
  this.http2Listener(socket);
185
+ } else if (this._unknownProtocolHandler && !couldBeHttp1(data)) {
186
+ this._unknownProtocolHandler.emit('connection', socket);
146
187
  } else {
188
+ // We pass everything else to the HTTP server, which can handle requests
189
+ // and/or reject unparseable client-error connections as it sees fit.
147
190
  this._httpServer.emit('connection', socket);
148
191
  }
149
192
  }
@@ -169,22 +212,16 @@ export class Server extends net.Server {
169
212
  const newData: Buffer = socket.read() || Buffer.from([]);
170
213
  const data = pastData ? Buffer.concat([pastData, newData]) : newData;
171
214
 
172
- if (data.length >= HTTP2_PREFACE_BUFFER.length) {
215
+ if (data.length >= HTTP2_PREFACE.length) {
173
216
  socket.unshift(data);
174
- if (data.slice(0, HTTP2_PREFACE_BUFFER.length).equals(HTTP2_PREFACE_BUFFER)) {
217
+ if (isHttp2(data)) {
175
218
  // We have a full match for the preface - it's definitely HTTP/2.
176
219
 
177
220
  // For HTTP/2 we hit issues when passing non-socket streams (like H2 streams for proxying H2-over-H2).
178
- if (NODE_MAJOR_VERSION <= 12) {
179
- // For Node 12 and older, we need a (later deprecated) stream wrapper:
180
- const StreamWrapper = require('_stream_wrap');
181
- socket = new StreamWrapper(socket);
182
- } else {
183
- // For newer node, we can fix this with a quick patch here:
184
- const socketWithInternals = socket as { _handle?: { isStreamBase?: boolean } };
185
- if (socketWithInternals._handle) {
186
- socketWithInternals._handle.isStreamBase = false;
187
- }
221
+ // Setting isStreamBase to false is an effective workaround for this in Node 14+
222
+ const socketWithInternals = socket as { _handle?: { isStreamBase?: boolean } };
223
+ if (socketWithInternals._handle) {
224
+ socketWithInternals._handle.isStreamBase = false;
188
225
  }
189
226
 
190
227
  h2Server.emit('connection', socket);
@@ -193,7 +230,7 @@ export class Server extends net.Server {
193
230
  h1Server.emit('connection', socket);
194
231
  return;
195
232
  }
196
- } else if (!data.equals(HTTP2_PREFACE_BUFFER.slice(0, data.length))) {
233
+ } else if (!data.equals(HTTP2_PREFACE.slice(0, data.length))) {
197
234
  socket.unshift(data);
198
235
  // Haven't finished the preface length, but something doesn't match already
199
236
  h1Server.emit('connection', socket);
@@ -209,28 +246,41 @@ export class Server extends net.Server {
209
246
  }
210
247
  }
211
248
 
249
+ export type { Server };
250
+
212
251
  /**
213
252
  * Create an Httpolyglot instance with just a request listener to support plain-text
214
253
  * HTTP & HTTP/2 on the same port, with incoming TLS connections being closed immediately.
215
254
  */
216
255
  export function createServer(requestListener: http.RequestListener): Server;
217
256
  /**
218
- * Create an instance with a full TLS configuration to create a TLS+HTTP+HTTP/2 server, which can
219
- * support all protocols on the same port.
257
+ * Create an Httpolyglot server with advanced configuration, to enable TLS and/or SOCKS
258
+ * connections containing HTTP, accepting all protocols on the same port.
259
+ *
260
+ * The options can contain:
261
+ * - `tls`: A TLS configuration object, or an existing TLS server. If a TLS server is provided,
262
+ * all incoming TLS connections will be emitted as 'connection' events on the given TLS server,
263
+ * and all 'secureConnection' events coming from the TLS server will be handled according to
264
+ * the connection type (HTTP/1 or HTTP/2) detected on that socket.
265
+ * - `socks`: A SOCKS server instance. This will allow incoming SOCKS connections, which will
266
+ * be emitted as 'connection' events on this server.
220
267
  */
221
- export function createServer(tlsConfig: https.ServerOptions, requestListener: http.RequestListener): Server;
222
- /**
223
- * Create an instance around an existing TLS server, instead of TLS configuration, to create a
224
- * TLS+HTTP+HTTP/2 server with custom TLS handling. All incoming TLS requests will be emitted as
225
- * 'connection' events on the given TLS server, and all 'secureConnection' events coming from the
226
- * TLS server will be handled according to the connection type detected on that socket.
227
- */
228
- export function createServer(tlsServer: tls.Server, requestListener: http.RequestListener): Server;
229
- export function createServer(configOrServerOrListener:
230
- | https.ServerOptions
231
- | http.RequestListener
232
- | tls.Server,
268
+ export function createServer(config: HttpolyglotOptions, requestListener: http.RequestListener): Server;
269
+ export function createServer(configOrListener:
270
+ | HttpolyglotOptions
271
+ | http.RequestListener,
233
272
  listener?: http.RequestListener
234
273
  ) {
235
- return new Server(configOrServerOrListener as any, listener as any);
274
+ let config: HttpolyglotOptions;
275
+ let requestListener: http.RequestListener;
276
+
277
+ if (typeof configOrListener === 'function') {
278
+ config = {}
279
+ requestListener = configOrListener;
280
+ } else {
281
+ config = configOrListener;
282
+ requestListener = listener!;
283
+ }
284
+
285
+ return new Server(config, requestListener);
236
286
  };