@httptoolkit/httpolyglot 2.2.2 → 3.0.1

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
@@ -1,62 +1,56 @@
1
- /// <reference types="node" />
2
- /// <reference types="node" />
3
- /// <reference types="node" />
4
- /// <reference types="node" />
5
- /// <reference types="node" />
6
- /// <reference types="node" />
7
1
  import * as net from 'net';
8
2
  import * as tls from 'tls';
9
3
  import * as http from 'http';
10
4
  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;
5
+ export interface HttpolyglotOptions {
25
6
  /**
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.
7
+ * Enable support for incoming TLS connections. If a TLS configuration is provided, this will
8
+ * be used to instantiate a TLS server to handle the connections. If a TLS server is provided,
9
+ * then all incoming TLS connections will be emitted as 'connection' events on the given
10
+ * TLS server, and all 'secureConnection' events coming from the TLS server will be handled
11
+ * according to the connection type detected on that socket.
28
12
  */
29
- constructor(requestListener: http.RequestListener);
13
+ tls?: https.ServerOptions | tls.Server;
30
14
  /**
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.
15
+ * A SOCKS server instance. This will allow incoming SOCKS connections, which will
16
+ * be emitted as 'connection' events on this server.
33
17
  */
34
- constructor(config: https.ServerOptions, requestListener: http.RequestListener);
18
+ socks?: net.Server;
35
19
  /**
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.
20
+ * A custom handler for unknown protocols. If provided, any unrecognized protocol sockets will
21
+ * be emitted on this server as 'connection' events. If not provided, the default behavior is to
22
+ * pass the socket to the HTTP server, which will typically reject the connection as
23
+ * unparseable, with a 400 response.
40
24
  */
41
- constructor(tlsServer: tls.Server, requestListener: http.RequestListener);
25
+ unknownProtocol?: net.Server;
26
+ }
27
+ declare class Server extends net.Server {
28
+ private _httpServer;
29
+ private _http2Server;
30
+ private _tlsServer;
31
+ private _socksHandler;
32
+ private _unknownProtocolHandler;
33
+ constructor(config: HttpolyglotOptions, requestListener: http.RequestListener);
42
34
  private connectionListener;
43
35
  private tlsListener;
44
36
  private http2Listener;
45
37
  }
38
+ export type { Server };
46
39
  /**
47
40
  * Create an Httpolyglot instance with just a request listener to support plain-text
48
41
  * HTTP & HTTP/2 on the same port, with incoming TLS connections being closed immediately.
49
42
  */
50
43
  export declare function createServer(requestListener: http.RequestListener): Server;
51
44
  /**
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.
45
+ * Create an Httpolyglot server with advanced configuration, to enable TLS and/or SOCKS
46
+ * connections containing HTTP, accepting all protocols on the same port.
47
+ *
48
+ * The options can contain:
49
+ * - `tls`: A TLS configuration object, or an existing TLS server. If a TLS server is provided,
50
+ * all incoming TLS connections will be emitted as 'connection' events on the given TLS server,
51
+ * and all 'secureConnection' events coming from the TLS server will be handled according to
52
+ * the connection type (HTTP/1 or HTTP/2) detected on that socket.
53
+ * - `socks`: A SOCKS server instance. This will allow incoming SOCKS connections, which will
54
+ * be emitted as 'connection' events on this server.
54
55
  */
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;
56
+ 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 = createServer;
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,58 +119,62 @@ 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
  }
102
147
  }
103
148
  }
104
149
  tlsListener(tlsSocket) {
105
- if (tlsSocket.alpnProtocol === false || // Old non-ALPN client
106
- tlsSocket.alpnProtocol === 'http/1.1' || // Modern HTTP/1.1 ALPN client
150
+ // We trust recognized ALPN protocols if explicitly provided by the client:
151
+ if (tlsSocket.alpnProtocol === 'http/1.1' || // Modern HTTP/1.1 ALPN client
107
152
  tlsSocket.alpnProtocol === 'http 1.1' // Broken ALPN client (e.g. https-proxy-agent)
108
153
  ) {
109
154
  this._httpServer.emit('connection', tlsSocket);
110
155
  }
111
- else {
156
+ else if (tlsSocket.alpnProtocol === 'h2') {
112
157
  this._http2Server.emit('connection', tlsSocket);
113
158
  }
159
+ else {
160
+ // If not provided, we unwrap & sniff again:
161
+ this.connectionListener(tlsSocket);
162
+ }
114
163
  }
115
164
  http2Listener(socket, pastData) {
116
165
  const h1Server = this._httpServer;
117
166
  const h2Server = this._http2Server;
118
167
  const newData = socket.read() || Buffer.from([]);
119
168
  const data = pastData ? Buffer.concat([pastData, newData]) : newData;
120
- if (data.length >= HTTP2_PREFACE_BUFFER.length) {
169
+ if (data.length >= HTTP2_PREFACE.length) {
121
170
  socket.unshift(data);
122
- if (data.slice(0, HTTP2_PREFACE_BUFFER.length).equals(HTTP2_PREFACE_BUFFER)) {
171
+ if (isHttp2(data)) {
123
172
  // We have a full match for the preface - it's definitely HTTP/2.
124
173
  // 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
- }
174
+ // Setting isStreamBase to false is an effective workaround for this in Node 14+
175
+ const socketWithInternals = socket;
176
+ if (socketWithInternals._handle) {
177
+ socketWithInternals._handle.isStreamBase = false;
136
178
  }
137
179
  h2Server.emit('connection', socket);
138
180
  return;
@@ -142,7 +184,7 @@ class Server extends net.Server {
142
184
  return;
143
185
  }
144
186
  }
145
- else if (!data.equals(HTTP2_PREFACE_BUFFER.slice(0, data.length))) {
187
+ else if (!data.equals(HTTP2_PREFACE.slice(0, data.length))) {
146
188
  socket.unshift(data);
147
189
  // Haven't finished the preface length, but something doesn't match already
148
190
  h1Server.emit('connection', socket);
@@ -156,10 +198,18 @@ class Server extends net.Server {
156
198
  });
157
199
  }
158
200
  }
159
- exports.Server = Server;
160
- function createServer(configOrServerOrListener, listener) {
161
- return new Server(configOrServerOrListener, listener);
201
+ function createServer(configOrListener, listener) {
202
+ let config;
203
+ let requestListener;
204
+ if (typeof configOrListener === 'function') {
205
+ config = {};
206
+ requestListener = configOrListener;
207
+ }
208
+ else {
209
+ config = configOrListener;
210
+ requestListener = listener;
211
+ }
212
+ return new Server(config, requestListener);
162
213
  }
163
- exports.createServer = createServer;
164
214
  ;
165
215
  //# sourceMappingURL=index.js.map
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.1",
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"
@@ -43,9 +38,10 @@
43
38
  "@types/chai": "^4.2.21",
44
39
  "@types/mocha": "^9.0.0",
45
40
  "chai": "^4.3.4",
46
- "mocha": "^9.1.1",
41
+ "mocha": "^11.7.4",
47
42
  "rimraf": "^3.0.2",
43
+ "socks": "^2.8.4",
48
44
  "ts-node": "^10.2.1",
49
- "typescript": "^4.4.2"
45
+ "typescript": "^5.9.3"
50
46
  }
51
47
  }
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
  }
@@ -153,14 +194,17 @@ export class Server extends net.Server {
153
194
  }
154
195
 
155
196
  private tlsListener(tlsSocket: tls.TLSSocket) {
197
+ // We trust recognized ALPN protocols if explicitly provided by the client:
156
198
  if (
157
- tlsSocket.alpnProtocol === false || // Old non-ALPN client
158
199
  tlsSocket.alpnProtocol === 'http/1.1' || // Modern HTTP/1.1 ALPN client
159
200
  tlsSocket.alpnProtocol === 'http 1.1' // Broken ALPN client (e.g. https-proxy-agent)
160
201
  ) {
161
202
  this._httpServer.emit('connection', tlsSocket);
162
- } else {
203
+ } else if (tlsSocket.alpnProtocol === 'h2') {
163
204
  this._http2Server.emit('connection', tlsSocket);
205
+ } else {
206
+ // If not provided, we unwrap & sniff again:
207
+ this.connectionListener(tlsSocket);
164
208
  }
165
209
  }
166
210
 
@@ -171,22 +215,16 @@ export class Server extends net.Server {
171
215
  const newData: Buffer = socket.read() || Buffer.from([]);
172
216
  const data = pastData ? Buffer.concat([pastData, newData]) : newData;
173
217
 
174
- if (data.length >= HTTP2_PREFACE_BUFFER.length) {
218
+ if (data.length >= HTTP2_PREFACE.length) {
175
219
  socket.unshift(data);
176
- if (data.slice(0, HTTP2_PREFACE_BUFFER.length).equals(HTTP2_PREFACE_BUFFER)) {
220
+ if (isHttp2(data)) {
177
221
  // We have a full match for the preface - it's definitely HTTP/2.
178
222
 
179
223
  // 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
- }
224
+ // Setting isStreamBase to false is an effective workaround for this in Node 14+
225
+ const socketWithInternals = socket as { _handle?: { isStreamBase?: boolean } };
226
+ if (socketWithInternals._handle) {
227
+ socketWithInternals._handle.isStreamBase = false;
190
228
  }
191
229
 
192
230
  h2Server.emit('connection', socket);
@@ -195,7 +233,7 @@ export class Server extends net.Server {
195
233
  h1Server.emit('connection', socket);
196
234
  return;
197
235
  }
198
- } else if (!data.equals(HTTP2_PREFACE_BUFFER.slice(0, data.length))) {
236
+ } else if (!data.equals(HTTP2_PREFACE.slice(0, data.length))) {
199
237
  socket.unshift(data);
200
238
  // Haven't finished the preface length, but something doesn't match already
201
239
  h1Server.emit('connection', socket);
@@ -211,28 +249,41 @@ export class Server extends net.Server {
211
249
  }
212
250
  }
213
251
 
252
+ export type { Server };
253
+
214
254
  /**
215
255
  * Create an Httpolyglot instance with just a request listener to support plain-text
216
256
  * HTTP & HTTP/2 on the same port, with incoming TLS connections being closed immediately.
217
257
  */
218
258
  export function createServer(requestListener: http.RequestListener): Server;
219
259
  /**
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.
260
+ * Create an Httpolyglot server with advanced configuration, to enable TLS and/or SOCKS
261
+ * connections containing HTTP, accepting all protocols on the same port.
262
+ *
263
+ * The options can contain:
264
+ * - `tls`: A TLS configuration object, or an existing TLS server. If a TLS server is provided,
265
+ * all incoming TLS connections will be emitted as 'connection' events on the given TLS server,
266
+ * and all 'secureConnection' events coming from the TLS server will be handled according to
267
+ * the connection type (HTTP/1 or HTTP/2) detected on that socket.
268
+ * - `socks`: A SOCKS server instance. This will allow incoming SOCKS connections, which will
269
+ * be emitted as 'connection' events on this server.
222
270
  */
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,
271
+ export function createServer(config: HttpolyglotOptions, requestListener: http.RequestListener): Server;
272
+ export function createServer(configOrListener:
273
+ | HttpolyglotOptions
274
+ | http.RequestListener,
235
275
  listener?: http.RequestListener
236
276
  ) {
237
- return new Server(configOrServerOrListener as any, listener as any);
277
+ let config: HttpolyglotOptions;
278
+ let requestListener: http.RequestListener;
279
+
280
+ if (typeof configOrListener === 'function') {
281
+ config = {}
282
+ requestListener = configOrListener;
283
+ } else {
284
+ config = configOrListener;
285
+ requestListener = listener!;
286
+ }
287
+
288
+ return new Server(config, requestListener);
238
289
  };