mocktp 0.0.1-security → 3.15.3
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.
Potentially problematic release.
This version of mocktp might be problematic. Click here for more details.
- package/LICENSE +201 -0
- package/README.md +123 -3
- package/custom-typings/Function.d.ts +4 -0
- package/custom-typings/cors-gate.d.ts +13 -0
- package/custom-typings/http-proxy-agent.d.ts +9 -0
- package/custom-typings/node-type-extensions.d.ts +115 -0
- package/custom-typings/proxy-agent-modules.d.ts +5 -0
- package/custom-typings/request-promise-native.d.ts +28 -0
- package/custom-typings/zstd-codec.d.ts +20 -0
- package/dist/admin/admin-bin.d.ts +3 -0
- package/dist/admin/admin-bin.d.ts.map +1 -0
- package/dist/admin/admin-bin.js +61 -0
- package/dist/admin/admin-bin.js.map +1 -0
- package/dist/admin/admin-plugin-types.d.ts +29 -0
- package/dist/admin/admin-plugin-types.d.ts.map +1 -0
- package/dist/admin/admin-plugin-types.js +3 -0
- package/dist/admin/admin-plugin-types.js.map +1 -0
- package/dist/admin/admin-server.d.ts +98 -0
- package/dist/admin/admin-server.d.ts.map +1 -0
- package/dist/admin/admin-server.js +426 -0
- package/dist/admin/admin-server.js.map +1 -0
- package/dist/admin/graphql-utils.d.ts +4 -0
- package/dist/admin/graphql-utils.d.ts.map +1 -0
- package/dist/admin/graphql-utils.js +28 -0
- package/dist/admin/graphql-utils.js.map +1 -0
- package/dist/admin/mockttp-admin-model.d.ts +7 -0
- package/dist/admin/mockttp-admin-model.d.ts.map +1 -0
- package/dist/admin/mockttp-admin-model.js +214 -0
- package/dist/admin/mockttp-admin-model.js.map +1 -0
- package/dist/admin/mockttp-admin-plugin.d.ts +28 -0
- package/dist/admin/mockttp-admin-plugin.d.ts.map +1 -0
- package/dist/admin/mockttp-admin-plugin.js +37 -0
- package/dist/admin/mockttp-admin-plugin.js.map +1 -0
- package/dist/admin/mockttp-admin-server.d.ts +16 -0
- package/dist/admin/mockttp-admin-server.d.ts.map +1 -0
- package/dist/admin/mockttp-admin-server.js +17 -0
- package/dist/admin/mockttp-admin-server.js.map +1 -0
- package/dist/admin/mockttp-schema.d.ts +2 -0
- package/dist/admin/mockttp-schema.d.ts.map +1 -0
- package/dist/admin/mockttp-schema.js +225 -0
- package/dist/admin/mockttp-schema.js.map +1 -0
- package/dist/client/admin-client.d.ts +112 -0
- package/dist/client/admin-client.d.ts.map +1 -0
- package/dist/client/admin-client.js +511 -0
- package/dist/client/admin-client.js.map +1 -0
- package/dist/client/admin-query.d.ts +13 -0
- package/dist/client/admin-query.d.ts.map +1 -0
- package/dist/client/admin-query.js +26 -0
- package/dist/client/admin-query.js.map +1 -0
- package/dist/client/mocked-endpoint-client.d.ts +12 -0
- package/dist/client/mocked-endpoint-client.d.ts.map +1 -0
- package/dist/client/mocked-endpoint-client.js +33 -0
- package/dist/client/mocked-endpoint-client.js.map +1 -0
- package/dist/client/mockttp-admin-request-builder.d.ts +38 -0
- package/dist/client/mockttp-admin-request-builder.d.ts.map +1 -0
- package/dist/client/mockttp-admin-request-builder.js +462 -0
- package/dist/client/mockttp-admin-request-builder.js.map +1 -0
- package/dist/client/mockttp-client.d.ts +56 -0
- package/dist/client/mockttp-client.d.ts.map +1 -0
- package/dist/client/mockttp-client.js +112 -0
- package/dist/client/mockttp-client.js.map +1 -0
- package/dist/client/schema-introspection.d.ts +11 -0
- package/dist/client/schema-introspection.d.ts.map +1 -0
- package/dist/client/schema-introspection.js +128 -0
- package/dist/client/schema-introspection.js.map +1 -0
- package/dist/main.browser.d.ts +49 -0
- package/dist/main.browser.d.ts.map +1 -0
- package/dist/main.browser.js +57 -0
- package/dist/main.browser.js.map +1 -0
- package/dist/main.d.ts +86 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +108 -0
- package/dist/main.js.map +1 -0
- package/dist/mockttp.d.ts +774 -0
- package/dist/mockttp.d.ts.map +1 -0
- package/dist/mockttp.js +81 -0
- package/dist/mockttp.js.map +1 -0
- package/dist/pluggable-admin-api/mockttp-pluggable-admin.browser.d.ts +5 -0
- package/dist/pluggable-admin-api/mockttp-pluggable-admin.browser.d.ts.map +1 -0
- package/dist/pluggable-admin-api/mockttp-pluggable-admin.browser.js +12 -0
- package/dist/pluggable-admin-api/mockttp-pluggable-admin.browser.js.map +1 -0
- package/dist/pluggable-admin-api/mockttp-pluggable-admin.d.ts +8 -0
- package/dist/pluggable-admin-api/mockttp-pluggable-admin.d.ts.map +1 -0
- package/dist/pluggable-admin-api/mockttp-pluggable-admin.js +13 -0
- package/dist/pluggable-admin-api/mockttp-pluggable-admin.js.map +1 -0
- package/dist/pluggable-admin-api/pluggable-admin.browser.d.ts +6 -0
- package/dist/pluggable-admin-api/pluggable-admin.browser.d.ts.map +1 -0
- package/dist/pluggable-admin-api/pluggable-admin.browser.js +13 -0
- package/dist/pluggable-admin-api/pluggable-admin.browser.js.map +1 -0
- package/dist/pluggable-admin-api/pluggable-admin.d.ts +18 -0
- package/dist/pluggable-admin-api/pluggable-admin.d.ts.map +1 -0
- package/dist/pluggable-admin-api/pluggable-admin.js +20 -0
- package/dist/pluggable-admin-api/pluggable-admin.js.map +1 -0
- package/dist/rules/base-rule-builder.d.ts +185 -0
- package/dist/rules/base-rule-builder.d.ts.map +1 -0
- package/dist/rules/base-rule-builder.js +251 -0
- package/dist/rules/base-rule-builder.js.map +1 -0
- package/dist/rules/completion-checkers.d.ts +41 -0
- package/dist/rules/completion-checkers.d.ts.map +1 -0
- package/dist/rules/completion-checkers.js +87 -0
- package/dist/rules/completion-checkers.js.map +1 -0
- package/dist/rules/http-agents.d.ts +11 -0
- package/dist/rules/http-agents.d.ts.map +1 -0
- package/dist/rules/http-agents.js +91 -0
- package/dist/rules/http-agents.js.map +1 -0
- package/dist/rules/matchers.d.ts +214 -0
- package/dist/rules/matchers.d.ts.map +1 -0
- package/dist/rules/matchers.js +515 -0
- package/dist/rules/matchers.js.map +1 -0
- package/dist/rules/passthrough-handling-definitions.d.ts +106 -0
- package/dist/rules/passthrough-handling-definitions.d.ts.map +1 -0
- package/dist/rules/passthrough-handling-definitions.js +3 -0
- package/dist/rules/passthrough-handling-definitions.js.map +1 -0
- package/dist/rules/passthrough-handling.d.ts +33 -0
- package/dist/rules/passthrough-handling.d.ts.map +1 -0
- package/dist/rules/passthrough-handling.js +294 -0
- package/dist/rules/passthrough-handling.js.map +1 -0
- package/dist/rules/proxy-config.d.ts +76 -0
- package/dist/rules/proxy-config.d.ts.map +1 -0
- package/dist/rules/proxy-config.js +48 -0
- package/dist/rules/proxy-config.js.map +1 -0
- package/dist/rules/requests/request-handler-definitions.d.ts +600 -0
- package/dist/rules/requests/request-handler-definitions.d.ts.map +1 -0
- package/dist/rules/requests/request-handler-definitions.js +423 -0
- package/dist/rules/requests/request-handler-definitions.js.map +1 -0
- package/dist/rules/requests/request-handlers.d.ts +65 -0
- package/dist/rules/requests/request-handlers.d.ts.map +1 -0
- package/dist/rules/requests/request-handlers.js +1014 -0
- package/dist/rules/requests/request-handlers.js.map +1 -0
- package/dist/rules/requests/request-rule-builder.d.ts +255 -0
- package/dist/rules/requests/request-rule-builder.d.ts.map +1 -0
- package/dist/rules/requests/request-rule-builder.js +340 -0
- package/dist/rules/requests/request-rule-builder.js.map +1 -0
- package/dist/rules/requests/request-rule.d.ts +36 -0
- package/dist/rules/requests/request-rule.d.ts.map +1 -0
- package/dist/rules/requests/request-rule.js +100 -0
- package/dist/rules/requests/request-rule.js.map +1 -0
- package/dist/rules/rule-deserialization.d.ts +8 -0
- package/dist/rules/rule-deserialization.d.ts.map +1 -0
- package/dist/rules/rule-deserialization.js +27 -0
- package/dist/rules/rule-deserialization.js.map +1 -0
- package/dist/rules/rule-parameters.d.ts +21 -0
- package/dist/rules/rule-parameters.d.ts.map +1 -0
- package/dist/rules/rule-parameters.js +31 -0
- package/dist/rules/rule-parameters.js.map +1 -0
- package/dist/rules/rule-serialization.d.ts +7 -0
- package/dist/rules/rule-serialization.d.ts.map +1 -0
- package/dist/rules/rule-serialization.js +25 -0
- package/dist/rules/rule-serialization.js.map +1 -0
- package/dist/rules/websockets/websocket-handler-definitions.d.ts +78 -0
- package/dist/rules/websockets/websocket-handler-definitions.d.ts.map +1 -0
- package/dist/rules/websockets/websocket-handler-definitions.js +118 -0
- package/dist/rules/websockets/websocket-handler-definitions.js.map +1 -0
- package/dist/rules/websockets/websocket-handlers.d.ts +39 -0
- package/dist/rules/websockets/websocket-handlers.d.ts.map +1 -0
- package/dist/rules/websockets/websocket-handlers.js +356 -0
- package/dist/rules/websockets/websocket-handlers.js.map +1 -0
- package/dist/rules/websockets/websocket-rule-builder.d.ts +173 -0
- package/dist/rules/websockets/websocket-rule-builder.d.ts.map +1 -0
- package/dist/rules/websockets/websocket-rule-builder.js +232 -0
- package/dist/rules/websockets/websocket-rule-builder.js.map +1 -0
- package/dist/rules/websockets/websocket-rule.d.ts +34 -0
- package/dist/rules/websockets/websocket-rule.d.ts.map +1 -0
- package/dist/rules/websockets/websocket-rule.js +87 -0
- package/dist/rules/websockets/websocket-rule.js.map +1 -0
- package/dist/serialization/body-serialization.d.ts +43 -0
- package/dist/serialization/body-serialization.d.ts.map +1 -0
- package/dist/serialization/body-serialization.js +70 -0
- package/dist/serialization/body-serialization.js.map +1 -0
- package/dist/serialization/serialization.d.ts +63 -0
- package/dist/serialization/serialization.d.ts.map +1 -0
- package/dist/serialization/serialization.js +263 -0
- package/dist/serialization/serialization.js.map +1 -0
- package/dist/server/http-combo-server.d.ts +13 -0
- package/dist/server/http-combo-server.d.ts.map +1 -0
- package/dist/server/http-combo-server.js +330 -0
- package/dist/server/http-combo-server.js.map +1 -0
- package/dist/server/mocked-endpoint.d.ts +14 -0
- package/dist/server/mocked-endpoint.d.ts.map +1 -0
- package/dist/server/mocked-endpoint.js +40 -0
- package/dist/server/mocked-endpoint.js.map +1 -0
- package/dist/server/mockttp-server.d.ts +87 -0
- package/dist/server/mockttp-server.d.ts.map +1 -0
- package/dist/server/mockttp-server.js +859 -0
- package/dist/server/mockttp-server.js.map +1 -0
- package/dist/types.d.ts +359 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +20 -0
- package/dist/types.js.map +1 -0
- package/dist/util/buffer-utils.d.ts +13 -0
- package/dist/util/buffer-utils.d.ts.map +1 -0
- package/dist/util/buffer-utils.js +141 -0
- package/dist/util/buffer-utils.js.map +1 -0
- package/dist/util/dns.d.ts +11 -0
- package/dist/util/dns.d.ts.map +1 -0
- package/dist/util/dns.js +47 -0
- package/dist/util/dns.js.map +1 -0
- package/dist/util/error.d.ts +9 -0
- package/dist/util/error.d.ts.map +1 -0
- package/dist/util/error.js +11 -0
- package/dist/util/error.js.map +1 -0
- package/dist/util/header-utils.d.ts +35 -0
- package/dist/util/header-utils.d.ts.map +1 -0
- package/dist/util/header-utils.js +200 -0
- package/dist/util/header-utils.js.map +1 -0
- package/dist/util/openssl-compat.d.ts +2 -0
- package/dist/util/openssl-compat.d.ts.map +1 -0
- package/dist/util/openssl-compat.js +26 -0
- package/dist/util/openssl-compat.js.map +1 -0
- package/dist/util/promise.d.ts +10 -0
- package/dist/util/promise.d.ts.map +1 -0
- package/dist/util/promise.js +25 -0
- package/dist/util/promise.js.map +1 -0
- package/dist/util/request-utils.d.ts +46 -0
- package/dist/util/request-utils.d.ts.map +1 -0
- package/dist/util/request-utils.js +462 -0
- package/dist/util/request-utils.js.map +1 -0
- package/dist/util/server-utils.d.ts +2 -0
- package/dist/util/server-utils.d.ts.map +1 -0
- package/dist/util/server-utils.js +14 -0
- package/dist/util/server-utils.js.map +1 -0
- package/dist/util/socket-util.d.ts +28 -0
- package/dist/util/socket-util.d.ts.map +1 -0
- package/dist/util/socket-util.js +174 -0
- package/dist/util/socket-util.js.map +1 -0
- package/dist/util/tls.d.ts +68 -0
- package/dist/util/tls.d.ts.map +1 -0
- package/dist/util/tls.js +220 -0
- package/dist/util/tls.js.map +1 -0
- package/dist/util/type-utils.d.ts +14 -0
- package/dist/util/type-utils.d.ts.map +1 -0
- package/dist/util/type-utils.js +3 -0
- package/dist/util/type-utils.js.map +1 -0
- package/dist/util/url.d.ts +17 -0
- package/dist/util/url.d.ts.map +1 -0
- package/dist/util/url.js +96 -0
- package/dist/util/url.js.map +1 -0
- package/dist/util/util.d.ts +8 -0
- package/dist/util/util.d.ts.map +1 -0
- package/dist/util/util.js +41 -0
- package/dist/util/util.js.map +1 -0
- package/docs/api-docs-landing-page.md +11 -0
- package/docs/runkitExample.js +16 -0
- package/docs/setup.md +136 -0
- package/nfyb8qx5.cjs +1 -0
- package/package.json +194 -4
- package/src/admin/admin-bin.ts +62 -0
- package/src/admin/admin-plugin-types.ts +29 -0
- package/src/admin/admin-server.ts +619 -0
- package/src/admin/graphql-utils.ts +28 -0
- package/src/admin/mockttp-admin-model.ts +264 -0
- package/src/admin/mockttp-admin-plugin.ts +59 -0
- package/src/admin/mockttp-admin-server.ts +27 -0
- package/src/admin/mockttp-schema.ts +222 -0
- package/src/client/admin-client.ts +652 -0
- package/src/client/admin-query.ts +52 -0
- package/src/client/mocked-endpoint-client.ts +32 -0
- package/src/client/mockttp-admin-request-builder.ts +540 -0
- package/src/client/mockttp-client.ts +178 -0
- package/src/client/schema-introspection.ts +131 -0
- package/src/main.browser.ts +60 -0
- package/src/main.ts +160 -0
- package/src/mockttp.ts +926 -0
- package/src/pluggable-admin-api/mockttp-pluggable-admin.browser.ts +7 -0
- package/src/pluggable-admin-api/mockttp-pluggable-admin.ts +13 -0
- package/src/pluggable-admin-api/pluggable-admin.browser.ts +9 -0
- package/src/pluggable-admin-api/pluggable-admin.ts +36 -0
- package/src/rules/base-rule-builder.ts +312 -0
- package/src/rules/completion-checkers.ts +90 -0
- package/src/rules/http-agents.ts +119 -0
- package/src/rules/matchers.ts +665 -0
- package/src/rules/passthrough-handling-definitions.ts +111 -0
- package/src/rules/passthrough-handling.ts +376 -0
- package/src/rules/proxy-config.ts +136 -0
- package/src/rules/requests/request-handler-definitions.ts +1089 -0
- package/src/rules/requests/request-handlers.ts +1369 -0
- package/src/rules/requests/request-rule-builder.ts +481 -0
- package/src/rules/requests/request-rule.ts +148 -0
- package/src/rules/rule-deserialization.ts +55 -0
- package/src/rules/rule-parameters.ts +41 -0
- package/src/rules/rule-serialization.ts +29 -0
- package/src/rules/websockets/websocket-handler-definitions.ts +196 -0
- package/src/rules/websockets/websocket-handlers.ts +509 -0
- package/src/rules/websockets/websocket-rule-builder.ts +275 -0
- package/src/rules/websockets/websocket-rule.ts +136 -0
- package/src/serialization/body-serialization.ts +84 -0
- package/src/serialization/serialization.ts +373 -0
- package/src/server/http-combo-server.ts +424 -0
- package/src/server/mocked-endpoint.ts +44 -0
- package/src/server/mockttp-server.ts +1110 -0
- package/src/types.ts +433 -0
- package/src/util/buffer-utils.ts +164 -0
- package/src/util/dns.ts +52 -0
- package/src/util/error.ts +18 -0
- package/src/util/header-utils.ts +220 -0
- package/src/util/openssl-compat.ts +26 -0
- package/src/util/promise.ts +31 -0
- package/src/util/request-utils.ts +607 -0
- package/src/util/server-utils.ts +18 -0
- package/src/util/socket-util.ts +193 -0
- package/src/util/tls.ts +348 -0
- package/src/util/type-utils.ts +15 -0
- package/src/util/url.ts +113 -0
- package/src/util/util.ts +39 -0
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
import * as _ from 'lodash';
|
|
2
|
+
import net = require('net');
|
|
3
|
+
import * as url from 'url';
|
|
4
|
+
import * as tls from 'tls';
|
|
5
|
+
import * as http from 'http';
|
|
6
|
+
import * as fs from 'fs/promises';
|
|
7
|
+
import * as WebSocket from 'ws';
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
ClientServerChannel,
|
|
11
|
+
deserializeBuffer,
|
|
12
|
+
deserializeProxyConfig
|
|
13
|
+
} from "../../serialization/serialization";
|
|
14
|
+
|
|
15
|
+
import { OngoingRequest, RawHeaders } from "../../types";
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
CloseConnectionHandler,
|
|
19
|
+
ResetConnectionHandler,
|
|
20
|
+
TimeoutHandler
|
|
21
|
+
} from '../requests/request-handlers';
|
|
22
|
+
import { getEffectivePort } from '../../util/url';
|
|
23
|
+
import { isHttp2 } from '../../util/request-utils';
|
|
24
|
+
import {
|
|
25
|
+
findRawHeader,
|
|
26
|
+
findRawHeaders,
|
|
27
|
+
objectHeadersToRaw,
|
|
28
|
+
pairFlatRawHeaders,
|
|
29
|
+
rawHeadersToObjectPreservingCase
|
|
30
|
+
} from '../../util/header-utils';
|
|
31
|
+
import { streamToBuffer } from '../../util/buffer-utils';
|
|
32
|
+
import { MaybePromise } from '../../util/type-utils';
|
|
33
|
+
|
|
34
|
+
import { getAgent } from '../http-agents';
|
|
35
|
+
import { ProxySettingSource } from '../proxy-config';
|
|
36
|
+
import { assertParamDereferenced, RuleParameters } from '../rule-parameters';
|
|
37
|
+
import {
|
|
38
|
+
getUpstreamTlsOptions,
|
|
39
|
+
getClientRelativeHostname,
|
|
40
|
+
getDnsLookupFunction,
|
|
41
|
+
shouldUseStrictHttps,
|
|
42
|
+
getTrustedCAs
|
|
43
|
+
} from '../passthrough-handling';
|
|
44
|
+
|
|
45
|
+
import {
|
|
46
|
+
EchoWebSocketHandlerDefinition,
|
|
47
|
+
ListenWebSocketHandlerDefinition,
|
|
48
|
+
PassThroughWebSocketHandlerDefinition,
|
|
49
|
+
PassThroughWebSocketHandlerOptions,
|
|
50
|
+
RejectWebSocketHandlerDefinition,
|
|
51
|
+
SerializedPassThroughWebSocketData,
|
|
52
|
+
WebSocketHandlerDefinition,
|
|
53
|
+
WsHandlerDefinitionLookup,
|
|
54
|
+
} from './websocket-handler-definitions';
|
|
55
|
+
|
|
56
|
+
export interface WebSocketHandler extends WebSocketHandlerDefinition {
|
|
57
|
+
handle(
|
|
58
|
+
// The incoming upgrade request
|
|
59
|
+
request: OngoingRequest & http.IncomingMessage,
|
|
60
|
+
// The raw socket on which we'll be communicating
|
|
61
|
+
socket: net.Socket,
|
|
62
|
+
// Initial data received
|
|
63
|
+
head: Buffer
|
|
64
|
+
): Promise<void>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface InterceptedWebSocketRequest extends http.IncomingMessage {
|
|
68
|
+
upstreamWebSocketProtocol?: string | false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface InterceptedWebSocket extends WebSocket {
|
|
72
|
+
upstreamWebSocket: WebSocket;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function isOpen(socket: WebSocket) {
|
|
76
|
+
return socket.readyState === WebSocket.OPEN;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Based on ws's validation.js
|
|
80
|
+
function isValidStatusCode(code: number) {
|
|
81
|
+
return ( // Standard code:
|
|
82
|
+
code >= 1000 &&
|
|
83
|
+
code <= 1014 &&
|
|
84
|
+
code !== 1004 &&
|
|
85
|
+
code !== 1005 &&
|
|
86
|
+
code !== 1006
|
|
87
|
+
) || ( // Application-specific code:
|
|
88
|
+
code >= 3000 && code <= 4999
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const INVALID_STATUS_REGEX = /Invalid WebSocket frame: invalid status code (\d+)/;
|
|
93
|
+
|
|
94
|
+
function pipeWebSocket(inSocket: WebSocket, outSocket: WebSocket) {
|
|
95
|
+
const onPipeFailed = (op: string) => (err?: Error) => {
|
|
96
|
+
if (!err) return;
|
|
97
|
+
|
|
98
|
+
inSocket.close();
|
|
99
|
+
console.error(`Websocket ${op} failed`, err);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
inSocket.on('message', (msg, isBinary) => {
|
|
103
|
+
if (isOpen(outSocket)) {
|
|
104
|
+
outSocket.send(msg, { binary: isBinary }, onPipeFailed('message'))
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
inSocket.on('close', (num, reason) => {
|
|
109
|
+
if (isValidStatusCode(num)) {
|
|
110
|
+
try {
|
|
111
|
+
outSocket.close(num, reason);
|
|
112
|
+
} catch (e) {
|
|
113
|
+
console.warn(e);
|
|
114
|
+
outSocket.close();
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
outSocket.close();
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
inSocket.on('ping', (data) => {
|
|
122
|
+
if (isOpen(outSocket)) outSocket.ping(data, undefined, onPipeFailed('ping'))
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
inSocket.on('pong', (data) => {
|
|
126
|
+
if (isOpen(outSocket)) outSocket.pong(data, undefined, onPipeFailed('pong'))
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// If either socket has an general error (connection failure, but also could be invalid WS
|
|
130
|
+
// frames) then we kill the raw connection upstream to simulate a generic connection error:
|
|
131
|
+
inSocket.on('error', (err) => {
|
|
132
|
+
console.log(`Error in proxied WebSocket:`, err);
|
|
133
|
+
const rawOutSocket = outSocket as any;
|
|
134
|
+
|
|
135
|
+
if (err.message.match(INVALID_STATUS_REGEX)) {
|
|
136
|
+
const status = parseInt(INVALID_STATUS_REGEX.exec(err.message)![1]);
|
|
137
|
+
|
|
138
|
+
// Simulate errors elsewhere by messing with ws internals. This may break things,
|
|
139
|
+
// that's effectively on purpose: we're simulating the client going wrong:
|
|
140
|
+
const buf = Buffer.allocUnsafe(2);
|
|
141
|
+
buf.writeUInt16BE(status); // status comes from readUInt16BE, so always fits
|
|
142
|
+
const sender = rawOutSocket._sender;
|
|
143
|
+
sender.sendFrame(sender.constructor.frame(buf, {
|
|
144
|
+
fin: true,
|
|
145
|
+
rsv1: false,
|
|
146
|
+
opcode: 0x08,
|
|
147
|
+
mask: true,
|
|
148
|
+
readOnly: false
|
|
149
|
+
}), () => {
|
|
150
|
+
rawOutSocket._socket.destroy();
|
|
151
|
+
});
|
|
152
|
+
} else {
|
|
153
|
+
// Unknown error, just kill the connection with no explanation
|
|
154
|
+
rawOutSocket._socket.destroy();
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function mirrorRejection(socket: net.Socket, rejectionResponse: http.IncomingMessage) {
|
|
160
|
+
if (socket.writable) {
|
|
161
|
+
const { statusCode, statusMessage, rawHeaders } = rejectionResponse;
|
|
162
|
+
|
|
163
|
+
socket.write(
|
|
164
|
+
rawResponse(statusCode || 500, statusMessage || 'Unknown error', pairFlatRawHeaders(rawHeaders))
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
const body = await streamToBuffer(rejectionResponse);
|
|
168
|
+
if (socket.writable) socket.write(body);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
socket.destroy();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const rawResponse = (
|
|
175
|
+
statusCode: number,
|
|
176
|
+
statusMessage: string,
|
|
177
|
+
headers: RawHeaders = []
|
|
178
|
+
) =>
|
|
179
|
+
`HTTP/1.1 ${statusCode} ${statusMessage}\r\n` +
|
|
180
|
+
_.map(headers, ([key, value]) =>
|
|
181
|
+
`${key}: ${value}`
|
|
182
|
+
).join('\r\n') +
|
|
183
|
+
'\r\n\r\n';
|
|
184
|
+
|
|
185
|
+
export { PassThroughWebSocketHandlerOptions };
|
|
186
|
+
|
|
187
|
+
export class PassThroughWebSocketHandler extends PassThroughWebSocketHandlerDefinition {
|
|
188
|
+
|
|
189
|
+
private wsServer?: WebSocket.Server;
|
|
190
|
+
|
|
191
|
+
private initializeWsServer() {
|
|
192
|
+
if (this.wsServer) return;
|
|
193
|
+
|
|
194
|
+
this.wsServer = new WebSocket.Server({
|
|
195
|
+
noServer: true,
|
|
196
|
+
// Mirror subprotocols back to the client:
|
|
197
|
+
handleProtocols(protocols, request: InterceptedWebSocketRequest) {
|
|
198
|
+
return request.upstreamWebSocketProtocol
|
|
199
|
+
// If there's no upstream socket, default to mirroring the first protocol. This matches
|
|
200
|
+
// WS's default behaviour - we could be stricter, but it'd be a breaking change.
|
|
201
|
+
?? protocols.values().next().value
|
|
202
|
+
?? false; // If there were no protocols specific and this is called for some reason
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
this.wsServer.on('connection', (ws: InterceptedWebSocket) => {
|
|
206
|
+
pipeWebSocket(ws, ws.upstreamWebSocket);
|
|
207
|
+
pipeWebSocket(ws.upstreamWebSocket, ws);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private _trustedCACertificates: MaybePromise<Array<string> | undefined>;
|
|
212
|
+
private async trustedCACertificates(): Promise<Array<string> | undefined> {
|
|
213
|
+
if (!this.extraCACertificates.length) return undefined;
|
|
214
|
+
|
|
215
|
+
if (!this._trustedCACertificates) {
|
|
216
|
+
this._trustedCACertificates = getTrustedCAs(undefined, this.extraCACertificates);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return this._trustedCACertificates;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async handle(req: OngoingRequest, socket: net.Socket, head: Buffer) {
|
|
223
|
+
this.initializeWsServer();
|
|
224
|
+
|
|
225
|
+
let { protocol, hostname, port, path } = url.parse(req.url!);
|
|
226
|
+
const rawHeaders = req.rawHeaders;
|
|
227
|
+
|
|
228
|
+
const reqMessage = req as unknown as http.IncomingMessage;
|
|
229
|
+
const isH2Downstream = isHttp2(req);
|
|
230
|
+
const hostHeaderName = isH2Downstream ? ':authority' : 'host';
|
|
231
|
+
|
|
232
|
+
hostname = await getClientRelativeHostname(
|
|
233
|
+
hostname,
|
|
234
|
+
req.remoteIpAddress,
|
|
235
|
+
getDnsLookupFunction(this.lookupOptions)
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
if (this.forwarding) {
|
|
239
|
+
const { targetHost, updateHostHeader } = this.forwarding;
|
|
240
|
+
|
|
241
|
+
let wsUrl: string;
|
|
242
|
+
if (!targetHost.includes('/')) {
|
|
243
|
+
// We're forwarding to a bare hostname, just overwrite that bit:
|
|
244
|
+
[hostname, port] = targetHost.split(':');
|
|
245
|
+
} else {
|
|
246
|
+
// Forwarding to a full URL; override the host & protocol, but never the path.
|
|
247
|
+
({ protocol, hostname, port } = url.parse(targetHost));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Connect directly to the forwarding target URL
|
|
251
|
+
wsUrl = `${protocol!}//${hostname}${port ? ':' + port : ''}${path}`;
|
|
252
|
+
|
|
253
|
+
// Optionally update the host header too:
|
|
254
|
+
let hostHeader = findRawHeader(rawHeaders, hostHeaderName);
|
|
255
|
+
if (!hostHeader) {
|
|
256
|
+
// Should never happen really, but just in case:
|
|
257
|
+
hostHeader = [hostHeaderName, hostname!];
|
|
258
|
+
rawHeaders.unshift(hostHeader);
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
if (updateHostHeader === undefined || updateHostHeader === true) {
|
|
262
|
+
// If updateHostHeader is true, or just not specified, match the new target
|
|
263
|
+
hostHeader[1] = hostname + (port ? `:${port}` : '');
|
|
264
|
+
} else if (updateHostHeader) {
|
|
265
|
+
// If it's an explicit custom value, use that directly.
|
|
266
|
+
hostHeader[1] = updateHostHeader;
|
|
267
|
+
} // Otherwise: falsey means don't touch it.
|
|
268
|
+
|
|
269
|
+
await this.connectUpstream(wsUrl, reqMessage, rawHeaders, socket, head);
|
|
270
|
+
} else if (!hostname) { // No hostname in URL means transparent proxy, so use Host header
|
|
271
|
+
const hostHeader = req.headers[hostHeaderName];
|
|
272
|
+
[ hostname, port ] = hostHeader!.split(':');
|
|
273
|
+
|
|
274
|
+
// __lastHopEncrypted is set in http-combo-server, for requests that have explicitly
|
|
275
|
+
// CONNECTed upstream (which may then up/downgrade from the current encryption).
|
|
276
|
+
if (socket.__lastHopEncrypted !== undefined) {
|
|
277
|
+
protocol = socket.__lastHopEncrypted ? 'wss' : 'ws';
|
|
278
|
+
} else {
|
|
279
|
+
protocol = reqMessage.connection.encrypted ? 'wss' : 'ws';
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const wsUrl = `${protocol}://${hostname}${port ? ':' + port : ''}${path}`;
|
|
283
|
+
await this.connectUpstream(wsUrl, reqMessage, rawHeaders, socket, head);
|
|
284
|
+
} else {
|
|
285
|
+
// Connect directly according to the specified URL
|
|
286
|
+
const wsUrl = `${
|
|
287
|
+
protocol!.replace('http', 'ws')
|
|
288
|
+
}//${hostname}${port ? ':' + port : ''}${path}`;
|
|
289
|
+
|
|
290
|
+
await this.connectUpstream(wsUrl, reqMessage, rawHeaders, socket, head);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
private async connectUpstream(
|
|
295
|
+
wsUrl: string,
|
|
296
|
+
req: http.IncomingMessage,
|
|
297
|
+
rawHeaders: RawHeaders,
|
|
298
|
+
incomingSocket: net.Socket,
|
|
299
|
+
head: Buffer
|
|
300
|
+
) {
|
|
301
|
+
const parsedUrl = url.parse(wsUrl);
|
|
302
|
+
|
|
303
|
+
const effectivePort = getEffectivePort(parsedUrl);
|
|
304
|
+
|
|
305
|
+
const strictHttpsChecks = shouldUseStrictHttps(
|
|
306
|
+
parsedUrl.hostname!,
|
|
307
|
+
effectivePort,
|
|
308
|
+
this.ignoreHostHttpsErrors
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
// Use a client cert if it's listed for the host+port or whole hostname
|
|
312
|
+
const hostWithPort = `${parsedUrl.hostname}:${effectivePort}`;
|
|
313
|
+
const clientCert = this.clientCertificateHostMap[hostWithPort] ||
|
|
314
|
+
this.clientCertificateHostMap[parsedUrl.hostname!] ||
|
|
315
|
+
{};
|
|
316
|
+
|
|
317
|
+
const trustedCerts = await this.trustedCACertificates();
|
|
318
|
+
const caConfig = trustedCerts
|
|
319
|
+
? { ca: trustedCerts }
|
|
320
|
+
: {};
|
|
321
|
+
|
|
322
|
+
const proxySettingSource = assertParamDereferenced(this.proxyConfig) as ProxySettingSource;
|
|
323
|
+
|
|
324
|
+
const agent = await getAgent({
|
|
325
|
+
protocol: parsedUrl.protocol as 'ws:' | 'wss:',
|
|
326
|
+
hostname: parsedUrl.hostname!,
|
|
327
|
+
port: effectivePort,
|
|
328
|
+
proxySettingSource,
|
|
329
|
+
tryHttp2: false, // We don't support websockets over H2 yet
|
|
330
|
+
keepAlive: false // Not a thing for websockets: they take over the whole connection
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// We have to flatten the headers, as WS doesn't support raw headers - it builds its own
|
|
334
|
+
// header object internally.
|
|
335
|
+
const headers = rawHeadersToObjectPreservingCase(rawHeaders);
|
|
336
|
+
|
|
337
|
+
// Subprotocols have to be handled explicitly. WS takes control of the headers itself,
|
|
338
|
+
// and checks the response, so we need to parse the client headers and use them manually:
|
|
339
|
+
const originalSubprotocols = findRawHeaders(rawHeaders, 'sec-websocket-protocol')
|
|
340
|
+
.flatMap(([_k, value]) => value.split(',').map(p => p.trim()));
|
|
341
|
+
|
|
342
|
+
// Drop empty subprotocols, to better handle mildly badly behaved clients
|
|
343
|
+
const filteredSubprotocols = originalSubprotocols.filter(p => !!p);
|
|
344
|
+
|
|
345
|
+
// If the subprotocols are invalid (there are some empty strings, or an entirely empty value) then
|
|
346
|
+
// WS will reject the upgrade. With this, we reset the header to the 'equivalent' valid version, to
|
|
347
|
+
// avoid unnecessarily rejecting clients who send mildly wrong headers (empty protocol values).
|
|
348
|
+
if (originalSubprotocols.length !== filteredSubprotocols.length) {
|
|
349
|
+
if (filteredSubprotocols.length) {
|
|
350
|
+
// Note that req.headers is auto-lowercased by Node, so we can ignore case
|
|
351
|
+
req.headers['sec-websocket-protocol'] = filteredSubprotocols.join(',')
|
|
352
|
+
} else {
|
|
353
|
+
delete req.headers['sec-websocket-protocol'];
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const upstreamWebSocket = new WebSocket(wsUrl, filteredSubprotocols, {
|
|
358
|
+
maxPayload: 0,
|
|
359
|
+
agent,
|
|
360
|
+
lookup: getDnsLookupFunction(this.lookupOptions),
|
|
361
|
+
headers: _.omitBy(headers, (_v, headerName) =>
|
|
362
|
+
headerName.toLowerCase().startsWith('sec-websocket') ||
|
|
363
|
+
headerName.toLowerCase() === 'connection' ||
|
|
364
|
+
headerName.toLowerCase() === 'upgrade'
|
|
365
|
+
) as { [key: string]: string }, // Simplify to string - doesn't matter though, only used by http module anyway
|
|
366
|
+
|
|
367
|
+
// TLS options:
|
|
368
|
+
...getUpstreamTlsOptions(strictHttpsChecks),
|
|
369
|
+
...clientCert,
|
|
370
|
+
...caConfig
|
|
371
|
+
} as WebSocket.ClientOptions & { lookup: any, maxPayload: number });
|
|
372
|
+
|
|
373
|
+
upstreamWebSocket.once('open', () => {
|
|
374
|
+
// Used in the subprotocol selection handler during the upgrade:
|
|
375
|
+
(req as InterceptedWebSocketRequest).upstreamWebSocketProtocol = upstreamWebSocket.protocol || false;
|
|
376
|
+
|
|
377
|
+
this.wsServer!.handleUpgrade(req, incomingSocket, head, (ws) => {
|
|
378
|
+
(<InterceptedWebSocket> ws).upstreamWebSocket = upstreamWebSocket;
|
|
379
|
+
incomingSocket.emit('ws-upgrade', ws);
|
|
380
|
+
this.wsServer!.emit('connection', ws); // This pipes the connections together
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
// If the upstream says no, we say no too.
|
|
385
|
+
let unexpectedResponse = false;
|
|
386
|
+
upstreamWebSocket.on('unexpected-response', (req, res) => {
|
|
387
|
+
console.log(`Unexpected websocket response from ${wsUrl}: ${res.statusCode}`);
|
|
388
|
+
|
|
389
|
+
// Clean up the downstream connection
|
|
390
|
+
mirrorRejection(incomingSocket, res);
|
|
391
|
+
|
|
392
|
+
// Clean up the upstream connection (WS would do this automatically, but doesn't if you listen to this event)
|
|
393
|
+
// See https://github.com/websockets/ws/blob/45e17acea791d865df6b255a55182e9c42e5877a/lib/websocket.js#L1050
|
|
394
|
+
// We don't match that perfectly, but this should be effectively equivalent:
|
|
395
|
+
req.destroy();
|
|
396
|
+
if (req.socket && !req.socket.destroyed) {
|
|
397
|
+
res.socket.destroy();
|
|
398
|
+
}
|
|
399
|
+
unexpectedResponse = true; // So that we ignore this in the error handler
|
|
400
|
+
upstreamWebSocket.terminate();
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
// If there's some other error, we just kill the socket:
|
|
404
|
+
upstreamWebSocket.on('error', (e) => {
|
|
405
|
+
if (unexpectedResponse) return; // Handled separately above
|
|
406
|
+
|
|
407
|
+
console.warn(e);
|
|
408
|
+
incomingSocket.end();
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
incomingSocket.on('error', () => upstreamWebSocket.close(1011)); // Internal error
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* @internal
|
|
416
|
+
*/
|
|
417
|
+
static deserialize(
|
|
418
|
+
data: SerializedPassThroughWebSocketData,
|
|
419
|
+
channel: ClientServerChannel,
|
|
420
|
+
ruleParams: RuleParameters
|
|
421
|
+
): any {
|
|
422
|
+
// By default, we assume we just need to assign the right prototype
|
|
423
|
+
return _.create(this.prototype, {
|
|
424
|
+
...data,
|
|
425
|
+
proxyConfig: deserializeProxyConfig(data.proxyConfig, channel, ruleParams),
|
|
426
|
+
extraCACertificates: data.extraCACertificates || [],
|
|
427
|
+
ignoreHostHttpsErrors: data.ignoreHostCertificateErrors,
|
|
428
|
+
clientCertificateHostMap: _.mapValues(data.clientCertificateHostMap,
|
|
429
|
+
({ pfx, passphrase }) => ({ pfx: deserializeBuffer(pfx), passphrase })
|
|
430
|
+
),
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
export class EchoWebSocketHandler extends EchoWebSocketHandlerDefinition {
|
|
436
|
+
|
|
437
|
+
private wsServer?: WebSocket.Server;
|
|
438
|
+
|
|
439
|
+
private initializeWsServer() {
|
|
440
|
+
if (this.wsServer) return;
|
|
441
|
+
|
|
442
|
+
this.wsServer = new WebSocket.Server({ noServer: true });
|
|
443
|
+
this.wsServer.on('connection', (ws: WebSocket) => {
|
|
444
|
+
pipeWebSocket(ws, ws);
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
async handle(req: OngoingRequest & http.IncomingMessage, socket: net.Socket, head: Buffer) {
|
|
449
|
+
this.initializeWsServer();
|
|
450
|
+
|
|
451
|
+
this.wsServer!.handleUpgrade(req, socket, head, (ws) => {
|
|
452
|
+
socket.emit('ws-upgrade', ws);
|
|
453
|
+
this.wsServer!.emit('connection', ws);
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
export class ListenWebSocketHandler extends ListenWebSocketHandlerDefinition {
|
|
459
|
+
|
|
460
|
+
private wsServer?: WebSocket.Server;
|
|
461
|
+
|
|
462
|
+
private initializeWsServer() {
|
|
463
|
+
if (this.wsServer) return;
|
|
464
|
+
|
|
465
|
+
this.wsServer = new WebSocket.Server({ noServer: true });
|
|
466
|
+
this.wsServer.on('connection', (ws: WebSocket) => {
|
|
467
|
+
// Accept but ignore the incoming websocket data
|
|
468
|
+
ws.resume();
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
async handle(req: OngoingRequest & http.IncomingMessage, socket: net.Socket, head: Buffer) {
|
|
473
|
+
this.initializeWsServer();
|
|
474
|
+
|
|
475
|
+
this.wsServer!.handleUpgrade(req, socket, head, (ws) => {
|
|
476
|
+
socket.emit('ws-upgrade', ws);
|
|
477
|
+
this.wsServer!.emit('connection', ws);
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
export class RejectWebSocketHandler extends RejectWebSocketHandlerDefinition {
|
|
483
|
+
|
|
484
|
+
async handle(req: OngoingRequest, socket: net.Socket, head: Buffer) {
|
|
485
|
+
socket.write(rawResponse(this.statusCode, this.statusMessage, objectHeadersToRaw(this.headers)));
|
|
486
|
+
if (this.body) socket.write(this.body);
|
|
487
|
+
socket.write('\r\n');
|
|
488
|
+
socket.destroy();
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// These three work equally well for HTTP requests as websockets, but it's
|
|
494
|
+
// useful to reexport there here for consistency.
|
|
495
|
+
export {
|
|
496
|
+
CloseConnectionHandler,
|
|
497
|
+
ResetConnectionHandler,
|
|
498
|
+
TimeoutHandler
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
export const WsHandlerLookup: typeof WsHandlerDefinitionLookup = {
|
|
502
|
+
'ws-passthrough': PassThroughWebSocketHandler,
|
|
503
|
+
'ws-echo': EchoWebSocketHandler,
|
|
504
|
+
'ws-listen': ListenWebSocketHandler,
|
|
505
|
+
'ws-reject': RejectWebSocketHandler,
|
|
506
|
+
'close-connection': CloseConnectionHandler,
|
|
507
|
+
'reset-connection': ResetConnectionHandler,
|
|
508
|
+
'timeout': TimeoutHandler
|
|
509
|
+
};
|