@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 +3 -0
- package/README.md +23 -14
- package/dist/index.d.ts +35 -41
- package/dist/index.js +102 -52
- package/dist/index.js.map +1 -1
- package/package.json +7 -11
- package/src/index.ts +138 -87
package/LICENSE
CHANGED
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/) --
|
|
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
|
-
|
|
35
|
-
|
|
36
|
+
import * as httpolyglot from '@httptoolkit/httpolyglot';
|
|
37
|
+
import * as fs from 'fs';
|
|
36
38
|
|
|
37
39
|
httpolyglot.createServer({
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
53
|
-
|
|
56
|
+
import * as httpolyglot from '@httptoolkit/httpolyglot';
|
|
57
|
+
import * as fs from 'fs';
|
|
54
58
|
|
|
55
59
|
httpolyglot.createServer({
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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
|
-
|
|
85
|
+
If a config is provided, it can contain:
|
|
79
86
|
|
|
80
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
*
|
|
27
|
-
*
|
|
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
|
-
|
|
13
|
+
tls?: https.ServerOptions | tls.Server;
|
|
30
14
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
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
|
-
|
|
18
|
+
socks?: net.Server;
|
|
35
19
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
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
|
-
|
|
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
|
|
53
|
-
*
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
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
|
|
20
|
-
let tlsServer;
|
|
55
|
+
let config = {};
|
|
21
56
|
let requestListener;
|
|
22
|
-
if (typeof
|
|
23
|
-
requestListener =
|
|
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
|
-
|
|
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 (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
106
|
-
|
|
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 >=
|
|
169
|
+
if (data.length >= HTTP2_PREFACE.length) {
|
|
121
170
|
socket.unshift(data);
|
|
122
|
-
if (data
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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(
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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":"
|
|
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": "
|
|
4
|
-
"author": "Tim Perry <
|
|
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": ">=
|
|
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
|
-
"
|
|
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": "^
|
|
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": "^
|
|
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
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
const
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
50
|
+
function onError(err: any) {}
|
|
31
51
|
|
|
32
|
-
|
|
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
|
-
*
|
|
38
|
-
*
|
|
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
|
-
|
|
62
|
+
tls?: https.ServerOptions | tls.Server;
|
|
63
|
+
|
|
41
64
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
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
|
-
|
|
68
|
+
socks?: net.Server;
|
|
69
|
+
|
|
46
70
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
55
|
-
|
|
|
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
|
|
65
|
-
let tlsServer: tls.Server | undefined;
|
|
98
|
+
let config: HttpolyglotOptions = {};
|
|
66
99
|
let requestListener: http.RequestListener;
|
|
67
100
|
|
|
68
|
-
if (typeof
|
|
69
|
-
requestListener =
|
|
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
|
-
|
|
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 (
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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 (
|
|
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 (
|
|
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 >=
|
|
218
|
+
if (data.length >= HTTP2_PREFACE.length) {
|
|
175
219
|
socket.unshift(data);
|
|
176
|
-
if (data
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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(
|
|
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
|
|
221
|
-
*
|
|
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(
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
-
|
|
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
|
};
|