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,607 @@
|
|
|
1
|
+
import * as _ from 'lodash';
|
|
2
|
+
import * as net from 'net';
|
|
3
|
+
import { TLSSocket } from 'tls';
|
|
4
|
+
import * as http from 'http';
|
|
5
|
+
import * as http2 from 'http2';
|
|
6
|
+
import * as stream from 'stream';
|
|
7
|
+
import * as querystring from 'querystring';
|
|
8
|
+
import * as multipart from 'parse-multipart-data';
|
|
9
|
+
import now = require("performance-now");
|
|
10
|
+
import * as url from 'url';
|
|
11
|
+
import type { SUPPORTED_ENCODING } from 'http-encoding';
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
Headers,
|
|
15
|
+
OngoingRequest,
|
|
16
|
+
CompletedRequest,
|
|
17
|
+
OngoingResponse,
|
|
18
|
+
CompletedResponse,
|
|
19
|
+
OngoingBody,
|
|
20
|
+
CompletedBody,
|
|
21
|
+
TimingEvents,
|
|
22
|
+
InitiatedRequest,
|
|
23
|
+
RawHeaders
|
|
24
|
+
} from "../types";
|
|
25
|
+
|
|
26
|
+
import {
|
|
27
|
+
bufferThenStream,
|
|
28
|
+
bufferToStream,
|
|
29
|
+
BufferInProgress,
|
|
30
|
+
splitBuffer,
|
|
31
|
+
streamToBuffer,
|
|
32
|
+
asBuffer
|
|
33
|
+
} from './buffer-utils';
|
|
34
|
+
import {
|
|
35
|
+
flattenPairedRawHeaders,
|
|
36
|
+
objectHeadersToFlat,
|
|
37
|
+
objectHeadersToRaw,
|
|
38
|
+
pairFlatRawHeaders,
|
|
39
|
+
rawHeadersToObject
|
|
40
|
+
} from './header-utils';
|
|
41
|
+
|
|
42
|
+
export const shouldKeepAlive = (req: OngoingRequest): boolean =>
|
|
43
|
+
req.httpVersion !== '1.0' &&
|
|
44
|
+
req.headers['connection'] !== 'close' &&
|
|
45
|
+
req.headers['proxy-connection'] !== 'close';
|
|
46
|
+
|
|
47
|
+
export const writeHead = (
|
|
48
|
+
response: http.ServerResponse | http2.Http2ServerResponse,
|
|
49
|
+
status: number,
|
|
50
|
+
statusMessage?: string | undefined,
|
|
51
|
+
headers?: Headers | RawHeaders | undefined
|
|
52
|
+
) => {
|
|
53
|
+
const flatHeaders: http.OutgoingHttpHeaders | string[] =
|
|
54
|
+
headers === undefined
|
|
55
|
+
? {}
|
|
56
|
+
: isHttp2(response) && Array.isArray(headers)
|
|
57
|
+
// H2 raw headers support is poor so we map to object here.
|
|
58
|
+
// We should revert to flat headers once the below is resolved in LTS:
|
|
59
|
+
// https://github.com/nodejs/node/issues/51402
|
|
60
|
+
? rawHeadersToObject(headers)
|
|
61
|
+
: isHttp2(response)
|
|
62
|
+
? headers as Headers // H2 supports object headers just fine
|
|
63
|
+
: !Array.isArray(headers)
|
|
64
|
+
? objectHeadersToFlat(headers)
|
|
65
|
+
// RawHeaders for H1, must be flattened:
|
|
66
|
+
: flattenPairedRawHeaders(headers);
|
|
67
|
+
|
|
68
|
+
// We aim to always pass flat headers to writeHead instead of calling setHeader because
|
|
69
|
+
// in most cases it's more flexible about supporting raw data, e.g. multiple headers with
|
|
70
|
+
// different casing can't be represented with setHeader at all (the latter overwrites).
|
|
71
|
+
|
|
72
|
+
if (statusMessage === undefined) {
|
|
73
|
+
// Cast is required as Node H2 types don't know about raw headers:
|
|
74
|
+
response.writeHead(status, flatHeaders as http.OutgoingHttpHeaders);
|
|
75
|
+
} else {
|
|
76
|
+
response.writeHead(status, statusMessage, flatHeaders as http.OutgoingHttpHeaders);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export function isHttp2(
|
|
81
|
+
message: | http.IncomingMessage
|
|
82
|
+
| http.ServerResponse
|
|
83
|
+
| http2.Http2ServerRequest
|
|
84
|
+
| http2.Http2ServerResponse
|
|
85
|
+
| OngoingRequest
|
|
86
|
+
| OngoingResponse
|
|
87
|
+
): message is http2.Http2ServerRequest | http2.Http2ServerResponse {
|
|
88
|
+
return ('httpVersion' in message && !!message.httpVersion?.startsWith('2')) || // H2 request
|
|
89
|
+
('stream' in message && 'createPushResponse' in message); // H2 response
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export async function encodeBodyBuffer(buffer: Uint8Array, headers: Headers) {
|
|
93
|
+
const contentEncoding = headers['content-encoding'];
|
|
94
|
+
|
|
95
|
+
// We skip encodeBuffer entirely if possible - this isn't strictly necessary, but it's useful
|
|
96
|
+
// so you can drop the http-encoding package in bundling downstream without issue in cases
|
|
97
|
+
// where you don't actually use any encodings.
|
|
98
|
+
if (!contentEncoding) return buffer;
|
|
99
|
+
|
|
100
|
+
return await (await import('http-encoding')).encodeBuffer(
|
|
101
|
+
buffer,
|
|
102
|
+
contentEncoding as SUPPORTED_ENCODING,
|
|
103
|
+
{ level: 1 }
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function decodeBodyBuffer(buffer: Buffer, headers: Headers) {
|
|
108
|
+
const contentEncoding = headers['content-encoding'];
|
|
109
|
+
|
|
110
|
+
// We skip decodeBuffer entirely if possible - this isn't strictly necessary, but it's useful
|
|
111
|
+
// so you can drop the http-encoding package in bundling downstream without issue in cases
|
|
112
|
+
// where you don't actually use any encodings.
|
|
113
|
+
if (!contentEncoding) return buffer;
|
|
114
|
+
|
|
115
|
+
return await (await import('http-encoding')).decodeBuffer(
|
|
116
|
+
buffer,
|
|
117
|
+
contentEncoding as SUPPORTED_ENCODING
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Parse an in-progress request or response stream, i.e. where the body or possibly even the headers have
|
|
122
|
+
// not been fully received/sent yet.
|
|
123
|
+
const parseBodyStream = (bodyStream: stream.Readable, maxSize: number, getHeaders: () => Headers): OngoingBody => {
|
|
124
|
+
let bufferPromise: BufferInProgress | null = null;
|
|
125
|
+
let completedBuffer: Buffer | null = null;
|
|
126
|
+
|
|
127
|
+
let body = {
|
|
128
|
+
// Returns a stream for the full body, not the live streaming body.
|
|
129
|
+
// Each call creates a new stream, which starts with the already seen
|
|
130
|
+
// and buffered data, and then continues with the live stream, if active.
|
|
131
|
+
// Listeners to this stream *must* be attached synchronously after this call.
|
|
132
|
+
asStream() {
|
|
133
|
+
// If we've already buffered the whole body, just stream it out:
|
|
134
|
+
if (completedBuffer) return bufferToStream(completedBuffer);
|
|
135
|
+
|
|
136
|
+
// Otherwise, we want to start buffering now, and wrap that with
|
|
137
|
+
// a stream that can live-stream the buffered data on demand:
|
|
138
|
+
const buffer = body.asBuffer();
|
|
139
|
+
buffer.catch(() => {}); // Errors will be handled via the stream, so silence unhandled rejections here.
|
|
140
|
+
return bufferThenStream(buffer, bodyStream);
|
|
141
|
+
},
|
|
142
|
+
asBuffer() {
|
|
143
|
+
if (!bufferPromise) {
|
|
144
|
+
bufferPromise = streamToBuffer(bodyStream, maxSize);
|
|
145
|
+
|
|
146
|
+
bufferPromise
|
|
147
|
+
.then((buffer) => completedBuffer = buffer)
|
|
148
|
+
.catch(() => {}); // If we get no body, completedBuffer stays null
|
|
149
|
+
}
|
|
150
|
+
return bufferPromise;
|
|
151
|
+
},
|
|
152
|
+
async asDecodedBuffer() {
|
|
153
|
+
const buffer = await body.asBuffer();
|
|
154
|
+
return decodeBodyBuffer(buffer, getHeaders());
|
|
155
|
+
},
|
|
156
|
+
asText(encoding: BufferEncoding = 'utf8') {
|
|
157
|
+
return body.asDecodedBuffer().then((b) => b.toString(encoding));
|
|
158
|
+
},
|
|
159
|
+
asJson() {
|
|
160
|
+
return body.asText().then((t) => JSON.parse(t));
|
|
161
|
+
},
|
|
162
|
+
asFormData() {
|
|
163
|
+
return body.asText().then((t) => querystring.parse(t));
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
return body;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function runAsyncOrUndefined<R>(func: () => Promise<R>): Promise<R | undefined> {
|
|
171
|
+
try {
|
|
172
|
+
return await func();
|
|
173
|
+
} catch {
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const waitForBody = async (body: OngoingBody, headers: Headers): Promise<CompletedBody> => {
|
|
179
|
+
const bufferBody = await body.asBuffer();
|
|
180
|
+
return buildBodyReader(bufferBody, headers);
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
export const isMockttpBody = (body: any): body is CompletedBody => {
|
|
184
|
+
return body.hasOwnProperty('getDecodedBuffer');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export const buildBodyReader = (body: Buffer, headers: Headers): CompletedBody => {
|
|
188
|
+
const completedBody = {
|
|
189
|
+
buffer: body,
|
|
190
|
+
|
|
191
|
+
async getDecodedBuffer() {
|
|
192
|
+
return runAsyncOrUndefined(async () =>
|
|
193
|
+
asBuffer(
|
|
194
|
+
await decodeBodyBuffer(this.buffer, headers)
|
|
195
|
+
)
|
|
196
|
+
);
|
|
197
|
+
},
|
|
198
|
+
async getText() {
|
|
199
|
+
return runAsyncOrUndefined(async () =>
|
|
200
|
+
(await this.getDecodedBuffer())!.toString()
|
|
201
|
+
);
|
|
202
|
+
},
|
|
203
|
+
async getJson() {
|
|
204
|
+
return runAsyncOrUndefined(async () =>
|
|
205
|
+
JSON.parse((await completedBody.getText())!)
|
|
206
|
+
)
|
|
207
|
+
},
|
|
208
|
+
async getUrlEncodedFormData() {
|
|
209
|
+
return runAsyncOrUndefined(async () => {
|
|
210
|
+
const contentType = headers["content-type"];
|
|
211
|
+
if (contentType?.includes("multipart/form-data")) return; // Actively ignore multipart data - won't work as expected
|
|
212
|
+
|
|
213
|
+
const text = await completedBody.getText();
|
|
214
|
+
return text ? querystring.parse(text) : undefined;
|
|
215
|
+
});
|
|
216
|
+
},
|
|
217
|
+
async getMultipartFormData() {
|
|
218
|
+
return runAsyncOrUndefined(async () => {
|
|
219
|
+
const contentType = headers["content-type"];
|
|
220
|
+
if (!contentType?.includes("multipart/form-data")) return;
|
|
221
|
+
|
|
222
|
+
const boundary = contentType.match(/;\s*boundary=(\S+)/);
|
|
223
|
+
|
|
224
|
+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type#boundary
|
|
225
|
+
// `boundary` is required for multipart entities.
|
|
226
|
+
if (!boundary) return;
|
|
227
|
+
|
|
228
|
+
const multipartBodyBuffer = asBuffer(await decodeBodyBuffer(this.buffer, headers));
|
|
229
|
+
return multipart.parse(multipartBodyBuffer, boundary[1]);
|
|
230
|
+
});
|
|
231
|
+
},
|
|
232
|
+
async getFormData(): Promise<querystring.ParsedUrlQuery | undefined> {
|
|
233
|
+
return runAsyncOrUndefined(async () => {
|
|
234
|
+
// Return multi-part data if present, or fallback to default URL-encoded
|
|
235
|
+
// parsing for all other cases. Data is returned in the same format regardless.
|
|
236
|
+
const multiPartBody = await completedBody.getMultipartFormData();
|
|
237
|
+
if (multiPartBody) {
|
|
238
|
+
const formData: querystring.ParsedUrlQuery = {};
|
|
239
|
+
|
|
240
|
+
multiPartBody.forEach((part) => {
|
|
241
|
+
const name = part.name;
|
|
242
|
+
if (name === undefined) {
|
|
243
|
+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition#as_a_header_for_a_multipart_body,
|
|
244
|
+
// The header must include `name` property to identify the field name.
|
|
245
|
+
// So we ignore parts without a name, treating it as invalid multipart form data.
|
|
246
|
+
} else {
|
|
247
|
+
// We do not use `filename` or `type` here, because return value of `getFormData` must be string or string array.
|
|
248
|
+
|
|
249
|
+
const prevValue = formData[name];
|
|
250
|
+
if (prevValue === undefined) {
|
|
251
|
+
formData[name] = part.data.toString();
|
|
252
|
+
} else if (Array.isArray(prevValue)) {
|
|
253
|
+
prevValue.push(part.data.toString());
|
|
254
|
+
} else {
|
|
255
|
+
formData[name] = [prevValue, part.data.toString()];
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
return formData;
|
|
261
|
+
} else {
|
|
262
|
+
return completedBody.getUrlEncodedFormData();
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
return completedBody;
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
export const parseRequestBody = (
|
|
272
|
+
req: http.IncomingMessage | http2.Http2ServerRequest,
|
|
273
|
+
options: { maxSize: number }
|
|
274
|
+
) => {
|
|
275
|
+
let transformedRequest = <OngoingRequest> <any> req;
|
|
276
|
+
transformedRequest.body = parseBodyStream(req, options.maxSize, () => req.headers);
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Build an initiated request: the external representation of a request
|
|
281
|
+
* that's just started.
|
|
282
|
+
*/
|
|
283
|
+
export function buildInitiatedRequest(request: OngoingRequest): InitiatedRequest {
|
|
284
|
+
return {
|
|
285
|
+
..._.pick(request,
|
|
286
|
+
'id',
|
|
287
|
+
'matchedRuleId',
|
|
288
|
+
'protocol',
|
|
289
|
+
'httpVersion',
|
|
290
|
+
'method',
|
|
291
|
+
'url',
|
|
292
|
+
'path',
|
|
293
|
+
'remoteIpAddress',
|
|
294
|
+
'remotePort',
|
|
295
|
+
'hostname',
|
|
296
|
+
'headers',
|
|
297
|
+
'rawHeaders',
|
|
298
|
+
'tags'
|
|
299
|
+
),
|
|
300
|
+
timingEvents: request.timingEvents
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Build a completed request: the external representation of a request
|
|
306
|
+
* that's been completely received (but not necessarily replied to).
|
|
307
|
+
*/
|
|
308
|
+
export async function waitForCompletedRequest(request: OngoingRequest): Promise<CompletedRequest> {
|
|
309
|
+
const body = await waitForBody(request.body, request.headers);
|
|
310
|
+
const requestData = buildInitiatedRequest(request);
|
|
311
|
+
return {
|
|
312
|
+
...requestData,
|
|
313
|
+
body,
|
|
314
|
+
rawTrailers: request.rawTrailers ?? [],
|
|
315
|
+
trailers: rawHeadersToObject(request.rawTrailers ?? [])
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Parse the accepted format of the headers argument for writeHead and addTrailers
|
|
321
|
+
* into a single consistent paired-tuple format.
|
|
322
|
+
*/
|
|
323
|
+
const getHeaderPairsFromArgument = (headersArg: any) => {
|
|
324
|
+
// Two legal formats of header args (flat & object), one unofficial (tuple array)
|
|
325
|
+
if (Array.isArray(headersArg)) {
|
|
326
|
+
if (!Array.isArray(headersArg[0])) {
|
|
327
|
+
// Flat -> Raw tuples
|
|
328
|
+
return pairFlatRawHeaders(headersArg);
|
|
329
|
+
} else {
|
|
330
|
+
// Already raw tuples, cheeky
|
|
331
|
+
return headersArg;
|
|
332
|
+
}
|
|
333
|
+
} else {
|
|
334
|
+
// Headers object -> raw tuples
|
|
335
|
+
return objectHeadersToRaw(headersArg ?? {});
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
export function trackResponse(
|
|
340
|
+
response: http.ServerResponse,
|
|
341
|
+
timingEvents: TimingEvents,
|
|
342
|
+
tags: string[],
|
|
343
|
+
options: { maxSize: number }
|
|
344
|
+
): OngoingResponse {
|
|
345
|
+
let trackedResponse = <OngoingResponse> response;
|
|
346
|
+
|
|
347
|
+
trackedResponse.timingEvents = timingEvents;
|
|
348
|
+
trackedResponse.tags = tags;
|
|
349
|
+
|
|
350
|
+
// Headers are sent when .writeHead or .write() are first called
|
|
351
|
+
|
|
352
|
+
const trackingStream = new stream.PassThrough();
|
|
353
|
+
|
|
354
|
+
const originalWriteHeader = trackedResponse.writeHead;
|
|
355
|
+
const originalWrite = trackedResponse.write;
|
|
356
|
+
const originalEnd = trackedResponse.end;
|
|
357
|
+
const originalAddTrailers = trackedResponse.addTrailers;
|
|
358
|
+
const originalGetHeaders = trackedResponse.getHeaders;
|
|
359
|
+
|
|
360
|
+
let writtenHeaders: RawHeaders | undefined;
|
|
361
|
+
trackedResponse.getRawHeaders = () => writtenHeaders ?? [];
|
|
362
|
+
trackedResponse.getHeaders = () => rawHeadersToObject(trackedResponse.getRawHeaders());
|
|
363
|
+
|
|
364
|
+
trackedResponse.writeHead = function (this: typeof trackedResponse, ...args: any) {
|
|
365
|
+
if (!timingEvents.headersSentTimestamp) {
|
|
366
|
+
timingEvents.headersSentTimestamp = now();
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// HTTP/2 responses shouldn't have a status message:
|
|
370
|
+
if (isHttp2(trackedResponse) && typeof args[1] === 'string') {
|
|
371
|
+
args[1] = undefined;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
let headersArg: any;
|
|
375
|
+
if (args[2]) {
|
|
376
|
+
headersArg = args[2];
|
|
377
|
+
} else if (typeof args[1] !== 'string') {
|
|
378
|
+
headersArg = args[1];
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
writtenHeaders = getHeaderPairsFromArgument(headersArg);
|
|
382
|
+
|
|
383
|
+
if (isHttp2(trackedResponse)) {
|
|
384
|
+
writtenHeaders.unshift([':status', args[0].toString()]);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Headers might also have been set with setHeader before. They'll be combined, with headers
|
|
388
|
+
// here taking precendence. We simulate this by pulling in all values from getHeaders() and
|
|
389
|
+
// remembering any of those that we're not about to override.
|
|
390
|
+
const storedHeaders = originalGetHeaders.apply(this);
|
|
391
|
+
const writtenHeaderKeys = writtenHeaders.map(([key]) => key.toLowerCase());
|
|
392
|
+
const storedHeaderKeys = Object.keys(storedHeaders);
|
|
393
|
+
if (storedHeaderKeys.length) {
|
|
394
|
+
storedHeaderKeys
|
|
395
|
+
.filter((key) => !writtenHeaderKeys.includes(key))
|
|
396
|
+
.reverse() // We're unshifting (these were set first) so we have to reverse to keep order.
|
|
397
|
+
.forEach((key) => {
|
|
398
|
+
const value = storedHeaders[key];
|
|
399
|
+
if (Array.isArray(value)) {
|
|
400
|
+
value.reverse().forEach(v => writtenHeaders?.unshift([key, v]));
|
|
401
|
+
} else if (value !== undefined) {
|
|
402
|
+
writtenHeaders?.unshift([key, value]);
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return originalWriteHeader.apply(this, args);
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
let writtenTrailers: RawHeaders | undefined;
|
|
411
|
+
trackedResponse.getRawTrailers = () => writtenTrailers ?? [];
|
|
412
|
+
|
|
413
|
+
trackedResponse.addTrailers = function (this: typeof trackedResponse, ...args: any) {
|
|
414
|
+
const trailersArg = args[0];
|
|
415
|
+
writtenTrailers = getHeaderPairsFromArgument(trailersArg);
|
|
416
|
+
return originalAddTrailers.apply(this, args);
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
const trackingWrite = function (this: typeof trackedResponse, ...args: any) {
|
|
420
|
+
trackingStream.write.apply(trackingStream, args);
|
|
421
|
+
return originalWrite.apply(this, args);
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
trackedResponse.write = trackingWrite;
|
|
425
|
+
|
|
426
|
+
trackedResponse.end = function (...args: any) {
|
|
427
|
+
// We temporarily disable write tracking here, as .end
|
|
428
|
+
// can call this.write, but that write should not be
|
|
429
|
+
// tracked, or we'll get duplicate writes when trackingStream
|
|
430
|
+
// calls it on itself too.
|
|
431
|
+
|
|
432
|
+
trackedResponse.write = originalWrite;
|
|
433
|
+
|
|
434
|
+
trackingStream.end.apply(trackingStream, args);
|
|
435
|
+
let result = originalEnd.apply(this, args);
|
|
436
|
+
|
|
437
|
+
trackedResponse.write = trackingWrite;
|
|
438
|
+
return result;
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
trackedResponse.body = parseBodyStream(
|
|
442
|
+
trackingStream,
|
|
443
|
+
options.maxSize,
|
|
444
|
+
() => trackedResponse.getHeaders()
|
|
445
|
+
);
|
|
446
|
+
|
|
447
|
+
// Proxy errors (e.g. write-after-end) to the response, so they can be
|
|
448
|
+
// handled elsewhere, rather than killing the process outright.
|
|
449
|
+
trackingStream.on('error', (e) => trackedResponse.emit('error', e));
|
|
450
|
+
|
|
451
|
+
return trackedResponse;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Build a completed response: the external representation of a response
|
|
456
|
+
* that's been completely written out and sent back to the client.
|
|
457
|
+
*/
|
|
458
|
+
export async function waitForCompletedResponse(
|
|
459
|
+
response: OngoingResponse | CompletedResponse
|
|
460
|
+
): Promise<CompletedResponse> {
|
|
461
|
+
// Ongoing response has 'getHeaders' - completed has 'headers'.
|
|
462
|
+
if ('headers' in response) return response;
|
|
463
|
+
|
|
464
|
+
const body = await waitForBody(response.body, response.getHeaders());
|
|
465
|
+
response.timingEvents.responseSentTimestamp = response.timingEvents.responseSentTimestamp || now();
|
|
466
|
+
|
|
467
|
+
const completedResponse: CompletedResponse = _(response).pick([
|
|
468
|
+
'id',
|
|
469
|
+
'statusCode',
|
|
470
|
+
'timingEvents',
|
|
471
|
+
'tags'
|
|
472
|
+
]).assign({
|
|
473
|
+
statusMessage: '',
|
|
474
|
+
|
|
475
|
+
headers: response.getHeaders(),
|
|
476
|
+
rawHeaders: response.getRawHeaders(),
|
|
477
|
+
|
|
478
|
+
body: body,
|
|
479
|
+
|
|
480
|
+
rawTrailers: response.getRawTrailers(),
|
|
481
|
+
trailers: rawHeadersToObject(response.getRawTrailers())
|
|
482
|
+
}).valueOf();
|
|
483
|
+
|
|
484
|
+
if (!(response instanceof http2.Http2ServerResponse)) {
|
|
485
|
+
// H2 has no status messages, and generates a warning if you look for one
|
|
486
|
+
completedResponse.statusMessage = response.statusMessage;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
return completedResponse;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Take raw HTTP request bytes received, have a go at parsing something useful out of them.
|
|
493
|
+
// Very lax - this is a method to use when normal parsing has failed, not as standard
|
|
494
|
+
export function tryToParseHttpRequest(input: Buffer, socket: net.Socket): PartiallyParsedHttpRequest {
|
|
495
|
+
const req: PartiallyParsedHttpRequest = {};
|
|
496
|
+
try {
|
|
497
|
+
req.protocol = socket.__lastHopEncrypted ? "https" : "http"; // Wild guess really
|
|
498
|
+
|
|
499
|
+
// For TLS sockets, we default the hostname to the name given by SNI. Might be overridden
|
|
500
|
+
// by the URL or Host header later, if available.
|
|
501
|
+
if (socket instanceof TLSSocket) req.hostname = socket.servername;
|
|
502
|
+
|
|
503
|
+
const lines = splitBuffer(input, '\r\n');
|
|
504
|
+
const requestLine = lines[0].slice(0, lines[0].length).toString('ascii');
|
|
505
|
+
const [method, rawUri, httpProtocol] = requestLine.split(" ");
|
|
506
|
+
|
|
507
|
+
if (method) req.method = method.slice(0, 15); // With overflows this could be *anything*. Limit it slightly.
|
|
508
|
+
|
|
509
|
+
// An empty line delineates the headers from the body
|
|
510
|
+
const emptyLineIndex = _.findIndex(lines, (line) => line.length === 0);
|
|
511
|
+
|
|
512
|
+
try {
|
|
513
|
+
const headerLines = lines.slice(1, emptyLineIndex === -1 ? undefined : emptyLineIndex);
|
|
514
|
+
const rawHeaders = headerLines
|
|
515
|
+
.map((line) => splitBuffer(line, ':', 2))
|
|
516
|
+
.filter((line) => line.length > 1)
|
|
517
|
+
.map((headerParts) =>
|
|
518
|
+
headerParts.map(p => p.toString('utf8').trim()) as [string, string]
|
|
519
|
+
);
|
|
520
|
+
|
|
521
|
+
req.rawHeaders = rawHeaders;
|
|
522
|
+
req.headers = rawHeadersToObject(rawHeaders);
|
|
523
|
+
} catch (e) {}
|
|
524
|
+
|
|
525
|
+
try {
|
|
526
|
+
const parsedUrl = url.parse(rawUri);
|
|
527
|
+
req.path = parsedUrl.path ?? undefined;
|
|
528
|
+
|
|
529
|
+
const hostHeader = _.find(req.headers, (_value, key) => key.toLowerCase() === 'host');
|
|
530
|
+
|
|
531
|
+
if (hostHeader) {
|
|
532
|
+
req.hostname = Array.isArray(hostHeader) ? hostHeader[0] : hostHeader;
|
|
533
|
+
} else if (parsedUrl.hostname) {
|
|
534
|
+
req.hostname = parsedUrl.hostname;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (rawUri.includes('://') || !req.hostname) {
|
|
538
|
+
// URI is absolute, or we have no way to guess the host at all
|
|
539
|
+
req.url = rawUri;
|
|
540
|
+
} else {
|
|
541
|
+
// URI is relative (or invalid) and we have a host: use it
|
|
542
|
+
req.url = `${req.protocol}://${req.hostname}${
|
|
543
|
+
rawUri.startsWith('/') ? '' : '/' // Add a slash if the URI is garbage
|
|
544
|
+
}${rawUri}`;
|
|
545
|
+
}
|
|
546
|
+
} catch (e) {}
|
|
547
|
+
|
|
548
|
+
try {
|
|
549
|
+
const httpVersion = httpProtocol.split('/')[1];
|
|
550
|
+
req.httpVersion = httpVersion;
|
|
551
|
+
} catch (e) {}
|
|
552
|
+
} catch (e) {}
|
|
553
|
+
|
|
554
|
+
return req;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
type PartiallyParsedHttpRequest = {
|
|
558
|
+
protocol?: string;
|
|
559
|
+
httpVersion?: string;
|
|
560
|
+
method?: string;
|
|
561
|
+
url?: string;
|
|
562
|
+
headers?: Headers;
|
|
563
|
+
rawHeaders?: RawHeaders;
|
|
564
|
+
hostname?: string;
|
|
565
|
+
path?: string;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// Take raw HTTP response bytes received, parse something useful out of them. This is *not*
|
|
569
|
+
// very lax, and will throw errors due to unexpected response data, but it's used when we
|
|
570
|
+
// ourselves generate the data (for websocket responses that 'ws' writes directly to the
|
|
571
|
+
// socket invisibly). Fortunately all responses are very simple:
|
|
572
|
+
export function parseRawHttpResponse(input: Buffer, request: OngoingRequest): CompletedResponse {
|
|
573
|
+
const { id, tags, timingEvents} = request;
|
|
574
|
+
|
|
575
|
+
const lines = splitBuffer(input, '\r\n');
|
|
576
|
+
const responseLine = lines[0].subarray(0, lines[0].length).toString('ascii');
|
|
577
|
+
const [_httpVersion, rawStatusCode, ...restResponseLine] = responseLine.split(" ");
|
|
578
|
+
|
|
579
|
+
const statusCode = parseInt(rawStatusCode, 10);
|
|
580
|
+
const statusMessage = restResponseLine.join(' ');
|
|
581
|
+
|
|
582
|
+
// An empty line delineates the headers from the body
|
|
583
|
+
const emptyLineIndex = _.findIndex(lines, (line) => line.length === 0);
|
|
584
|
+
|
|
585
|
+
const headerLines = lines.slice(1, emptyLineIndex === -1 ? undefined : emptyLineIndex);
|
|
586
|
+
const rawHeaders = headerLines
|
|
587
|
+
.map((line) => splitBuffer(line, ':', 2))
|
|
588
|
+
.map((headerParts) =>
|
|
589
|
+
headerParts.map(p => p.toString('utf8').trim()) as [string, string]
|
|
590
|
+
);
|
|
591
|
+
|
|
592
|
+
const headers = rawHeadersToObject(rawHeaders);
|
|
593
|
+
const body = buildBodyReader(Buffer.from([]), {});
|
|
594
|
+
|
|
595
|
+
return {
|
|
596
|
+
id,
|
|
597
|
+
tags,
|
|
598
|
+
timingEvents,
|
|
599
|
+
statusCode,
|
|
600
|
+
statusMessage,
|
|
601
|
+
rawHeaders,
|
|
602
|
+
headers,
|
|
603
|
+
body,
|
|
604
|
+
rawTrailers: [],
|
|
605
|
+
trailers: {}
|
|
606
|
+
};
|
|
607
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function shouldPassThrough(
|
|
2
|
+
hostname: string | undefined,
|
|
3
|
+
// Only one of these two should have values (validated above):
|
|
4
|
+
passThroughPatterns: URLPattern[],
|
|
5
|
+
interceptOnlyPatterns: URLPattern[] | undefined
|
|
6
|
+
): boolean {
|
|
7
|
+
if (!hostname) return false;
|
|
8
|
+
|
|
9
|
+
if (interceptOnlyPatterns) {
|
|
10
|
+
return !interceptOnlyPatterns.some((pattern) =>
|
|
11
|
+
pattern.test(`https://${hostname}`)
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return passThroughPatterns.some((pattern) =>
|
|
16
|
+
pattern.test(`https://${hostname}`)
|
|
17
|
+
);
|
|
18
|
+
}
|