@httptoolkit/httpolyglot 2.2.2 → 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,61 +2,59 @@
2
2
  /// <reference types="node" />
3
3
  /// <reference types="node" />
4
4
  /// <reference types="node" />
5
- /// <reference types="node" />
6
- /// <reference types="node" />
7
5
  import * as net from 'net';
8
6
  import * as tls from 'tls';
9
7
  import * as http from 'http';
10
8
  import * as https from 'https';
11
- declare module 'net' {
12
- interface Socket {
13
- /**
14
- * Only preserved for types backward compat - always undefined in new releases.
15
- *
16
- * @deprecated
17
- */
18
- __httpPeekedData?: Buffer;
19
- }
20
- }
21
- export declare class Server extends net.Server {
22
- private _httpServer;
23
- private _http2Server;
24
- private _tlsServer;
9
+ export interface HttpolyglotOptions {
25
10
  /**
26
- * Create an Httpolyglot instance with just a request listener to support plain-text
27
- * 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.
28
16
  */
29
- constructor(requestListener: http.RequestListener);
17
+ tls?: https.ServerOptions | tls.Server;
30
18
  /**
31
- * Call with a full TLS configuration to create a TLS+HTTP+HTTP/2 server, which can
32
- * 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.
33
21
  */
34
- constructor(config: https.ServerOptions, requestListener: http.RequestListener);
22
+ socks?: net.Server;
35
23
  /**
36
- * Pass an existing TLS server, instead of TLS configuration, to create a TLS+HTTP+HTTP/2
37
- * server. All incoming TLS requests will be emitted as 'connection' events on the given
38
- * TLS server, and all 'secureConnection' events coming from the TLS server will be
39
- * 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.
40
28
  */
41
- 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);
42
38
  private connectionListener;
43
39
  private tlsListener;
44
40
  private http2Listener;
45
41
  }
42
+ export type { Server };
46
43
  /**
47
44
  * Create an Httpolyglot instance with just a request listener to support plain-text
48
45
  * HTTP & HTTP/2 on the same port, with incoming TLS connections being closed immediately.
49
46
  */
50
47
  export declare function createServer(requestListener: http.RequestListener): Server;
51
48
  /**
52
- * Create an instance with a full TLS configuration to create a TLS+HTTP+HTTP/2 server, which can
53
- * 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.
54
59
  */
55
- export declare function createServer(tlsConfig: https.ServerOptions, requestListener: http.RequestListener): Server;
56
- /**
57
- * Create an instance around an existing TLS server, instead of TLS configuration, to create a
58
- * TLS+HTTP+HTTP/2 server with custom TLS handling. All incoming TLS requests will be emitted as
59
- * 'connection' events on the given TLS server, and all 'secureConnection' events coming from the
60
- * TLS server will be handled according to the connection type detected on that socket.
61
- */
62
- 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,61 +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
- // Use `Function.prototype.bind` directly as frameworks like Express generate
66
+ // Use `Function.prototype.bind` directly as frameworks like Express generate
37
67
  // methods from `http.METHODS`, and `BIND` is an included HTTP method.
38
68
  const boundListener = Function.prototype.bind.call(requestListener, this);
39
69
  // Create subservers for each supported protocol:
40
70
  this._httpServer = new http.Server(boundListener);
41
71
  this._http2Server = http2.createServer({}, boundListener);
42
- if (tlsServer) {
43
- // If we've been given a preconfigured TLS server, we use that directly, and
44
- // subscribe to connections there
45
- this._tlsServer = tlsServer;
46
- this._tlsServer.on('secureConnection', this.tlsListener.bind(this));
47
- }
48
- else if (typeof tlsConfig === 'object') {
49
- // If we have TLS config, create a TLS server, which will pass sockets to
50
- // the relevant subserver once the TLS connection is set up.
51
- 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
+ }
52
84
  }
53
85
  else {
54
- // Fake server that rejects all connections:
86
+ // Fake TLS server that rejects all connections:
55
87
  this._tlsServer = new events_1.EventEmitter();
56
88
  this._tlsServer.on('connection', (socket) => socket.destroy());
57
89
  }
90
+ this._socksHandler = config.socks;
91
+ this._unknownProtocolHandler = config.unknownProtocol;
58
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);
59
97
  // Proxy all event listeners setup onto the subservers, so any
60
98
  // subscriptions on this server are fed from all the subservers
61
99
  this.on('newListener', function (eventName, listener) {
@@ -81,21 +119,28 @@ class Server extends net.Server {
81
119
  else {
82
120
  socket.removeListener('error', onError);
83
121
  // Put the peeked data back into the socket
84
- const firstByte = data[0];
85
122
  socket.unshift(data);
86
123
  // Pass the socket to the correct subserver:
87
- if (firstByte === TLS_HANDSHAKE_BYTE) {
124
+ if (isTls(data)) {
88
125
  // TLS sockets don't allow half open
89
126
  socket.allowHalfOpen = false;
90
127
  this._tlsServer.emit('connection', socket);
91
128
  }
129
+ else if (this._socksHandler && isSocks(data)) {
130
+ this._socksHandler.emit('connection', socket);
131
+ }
92
132
  else {
93
- if (firstByte === HTTP2_PREFACE_BUFFER[0]) {
133
+ if (couldBeHttp2(data)) {
94
134
  // The connection _might_ be HTTP/2. To confirm, we need to keep
95
135
  // reading until we get the whole stream:
96
136
  this.http2Listener(socket);
97
137
  }
138
+ else if (this._unknownProtocolHandler && !couldBeHttp1(data)) {
139
+ this._unknownProtocolHandler.emit('connection', socket);
140
+ }
98
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.
99
144
  this._httpServer.emit('connection', socket);
100
145
  }
101
146
  }
@@ -117,22 +162,15 @@ class Server extends net.Server {
117
162
  const h2Server = this._http2Server;
118
163
  const newData = socket.read() || Buffer.from([]);
119
164
  const data = pastData ? Buffer.concat([pastData, newData]) : newData;
120
- if (data.length >= HTTP2_PREFACE_BUFFER.length) {
165
+ if (data.length >= HTTP2_PREFACE.length) {
121
166
  socket.unshift(data);
122
- if (data.slice(0, HTTP2_PREFACE_BUFFER.length).equals(HTTP2_PREFACE_BUFFER)) {
167
+ if (isHttp2(data)) {
123
168
  // We have a full match for the preface - it's definitely HTTP/2.
124
169
  // For HTTP/2 we hit issues when passing non-socket streams (like H2 streams for proxying H2-over-H2).
125
- if (NODE_MAJOR_VERSION <= 12) {
126
- // For Node 12 and older, we need a (later deprecated) stream wrapper:
127
- const StreamWrapper = require('_stream_wrap');
128
- socket = new StreamWrapper(socket);
129
- }
130
- else {
131
- // For newer node, we can fix this with a quick patch here:
132
- const socketWithInternals = socket;
133
- if (socketWithInternals._handle) {
134
- socketWithInternals._handle.isStreamBase = false;
135
- }
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;
136
174
  }
137
175
  h2Server.emit('connection', socket);
138
176
  return;
@@ -142,7 +180,7 @@ class Server extends net.Server {
142
180
  return;
143
181
  }
144
182
  }
145
- else if (!data.equals(HTTP2_PREFACE_BUFFER.slice(0, data.length))) {
183
+ else if (!data.equals(HTTP2_PREFACE.slice(0, data.length))) {
146
184
  socket.unshift(data);
147
185
  // Haven't finished the preface length, but something doesn't match already
148
186
  h1Server.emit('connection', socket);
@@ -156,9 +194,18 @@ class Server extends net.Server {
156
194
  });
157
195
  }
158
196
  }
159
- exports.Server = Server;
160
- function createServer(configOrServerOrListener, listener) {
161
- 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);
162
209
  }
163
210
  exports.createServer = createServer;
164
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.2",
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,24 +95,19 @@ 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
- // Use `Function.prototype.bind` directly as frameworks like Express generate
110
+ // Use `Function.prototype.bind` directly as frameworks like Express generate
82
111
  // methods from `http.METHODS`, and `BIND` is an included HTTP method.
83
112
  const boundListener = Function.prototype.bind.call(requestListener, this);
84
113
 
@@ -86,22 +115,29 @@ export class Server extends net.Server {
86
115
  this._httpServer = new http.Server(boundListener);
87
116
  this._http2Server = http2.createServer({}, boundListener as any as Http2Listener);
88
117
 
89
- if (tlsServer) {
90
- // If we've been given a preconfigured TLS server, we use that directly, and
91
- // subscribe to connections there
92
- this._tlsServer = tlsServer;
93
- this._tlsServer.on('secureConnection', this.tlsListener.bind(this));
94
- } else if (typeof tlsConfig === 'object') {
95
- // If we have TLS config, create a TLS server, which will pass sockets to
96
- // the relevant subserver once the TLS connection is set up.
97
- 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
+ }
98
129
  } else {
99
- // Fake server that rejects all connections:
130
+ // Fake TLS server that rejects all connections:
100
131
  this._tlsServer = new EventEmitter();
101
132
  this._tlsServer.on('connection', (socket) => socket.destroy());
102
133
  }
103
134
 
135
+ this._socksHandler = config.socks;
136
+ this._unknownProtocolHandler = config.unknownProtocol;
137
+
104
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);
105
141
 
106
142
  // Proxy all event listeners setup onto the subservers, so any
107
143
  // subscriptions on this server are fed from all the subservers
@@ -132,20 +168,25 @@ export class Server extends net.Server {
132
168
  socket.removeListener('error', onError);
133
169
 
134
170
  // Put the peeked data back into the socket
135
- const firstByte = data[0];
136
171
  socket.unshift(data);
137
172
 
138
173
  // Pass the socket to the correct subserver:
139
- if (firstByte === TLS_HANDSHAKE_BYTE) {
174
+ if (isTls(data)) {
140
175
  // TLS sockets don't allow half open
141
176
  socket.allowHalfOpen = false;
142
177
  this._tlsServer.emit('connection', socket);
178
+ } else if (this._socksHandler && isSocks(data)) {
179
+ this._socksHandler.emit('connection', socket);
143
180
  } else {
144
- if (firstByte === HTTP2_PREFACE_BUFFER[0]) {
181
+ if (couldBeHttp2(data)) {
145
182
  // The connection _might_ be HTTP/2. To confirm, we need to keep
146
183
  // reading until we get the whole stream:
147
184
  this.http2Listener(socket);
185
+ } else if (this._unknownProtocolHandler && !couldBeHttp1(data)) {
186
+ this._unknownProtocolHandler.emit('connection', socket);
148
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.
149
190
  this._httpServer.emit('connection', socket);
150
191
  }
151
192
  }
@@ -171,22 +212,16 @@ export class Server extends net.Server {
171
212
  const newData: Buffer = socket.read() || Buffer.from([]);
172
213
  const data = pastData ? Buffer.concat([pastData, newData]) : newData;
173
214
 
174
- if (data.length >= HTTP2_PREFACE_BUFFER.length) {
215
+ if (data.length >= HTTP2_PREFACE.length) {
175
216
  socket.unshift(data);
176
- if (data.slice(0, HTTP2_PREFACE_BUFFER.length).equals(HTTP2_PREFACE_BUFFER)) {
217
+ if (isHttp2(data)) {
177
218
  // We have a full match for the preface - it's definitely HTTP/2.
178
219
 
179
220
  // For HTTP/2 we hit issues when passing non-socket streams (like H2 streams for proxying H2-over-H2).
180
- if (NODE_MAJOR_VERSION <= 12) {
181
- // For Node 12 and older, we need a (later deprecated) stream wrapper:
182
- const StreamWrapper = require('_stream_wrap');
183
- socket = new StreamWrapper(socket);
184
- } else {
185
- // For newer node, we can fix this with a quick patch here:
186
- const socketWithInternals = socket as { _handle?: { isStreamBase?: boolean } };
187
- if (socketWithInternals._handle) {
188
- socketWithInternals._handle.isStreamBase = false;
189
- }
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;
190
225
  }
191
226
 
192
227
  h2Server.emit('connection', socket);
@@ -195,7 +230,7 @@ export class Server extends net.Server {
195
230
  h1Server.emit('connection', socket);
196
231
  return;
197
232
  }
198
- } else if (!data.equals(HTTP2_PREFACE_BUFFER.slice(0, data.length))) {
233
+ } else if (!data.equals(HTTP2_PREFACE.slice(0, data.length))) {
199
234
  socket.unshift(data);
200
235
  // Haven't finished the preface length, but something doesn't match already
201
236
  h1Server.emit('connection', socket);
@@ -211,28 +246,41 @@ export class Server extends net.Server {
211
246
  }
212
247
  }
213
248
 
249
+ export type { Server };
250
+
214
251
  /**
215
252
  * Create an Httpolyglot instance with just a request listener to support plain-text
216
253
  * HTTP & HTTP/2 on the same port, with incoming TLS connections being closed immediately.
217
254
  */
218
255
  export function createServer(requestListener: http.RequestListener): Server;
219
256
  /**
220
- * Create an instance with a full TLS configuration to create a TLS+HTTP+HTTP/2 server, which can
221
- * 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.
222
267
  */
223
- export function createServer(tlsConfig: https.ServerOptions, requestListener: http.RequestListener): Server;
224
- /**
225
- * Create an instance around an existing TLS server, instead of TLS configuration, to create a
226
- * TLS+HTTP+HTTP/2 server with custom TLS handling. All incoming TLS requests will be emitted as
227
- * 'connection' events on the given TLS server, and all 'secureConnection' events coming from the
228
- * TLS server will be handled according to the connection type detected on that socket.
229
- */
230
- export function createServer(tlsServer: tls.Server, requestListener: http.RequestListener): Server;
231
- export function createServer(configOrServerOrListener:
232
- | https.ServerOptions
233
- | http.RequestListener
234
- | tls.Server,
268
+ export function createServer(config: HttpolyglotOptions, requestListener: http.RequestListener): Server;
269
+ export function createServer(configOrListener:
270
+ | HttpolyglotOptions
271
+ | http.RequestListener,
235
272
  listener?: http.RequestListener
236
273
  ) {
237
- 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);
238
286
  };