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.

Files changed (304) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +123 -3
  3. package/custom-typings/Function.d.ts +4 -0
  4. package/custom-typings/cors-gate.d.ts +13 -0
  5. package/custom-typings/http-proxy-agent.d.ts +9 -0
  6. package/custom-typings/node-type-extensions.d.ts +115 -0
  7. package/custom-typings/proxy-agent-modules.d.ts +5 -0
  8. package/custom-typings/request-promise-native.d.ts +28 -0
  9. package/custom-typings/zstd-codec.d.ts +20 -0
  10. package/dist/admin/admin-bin.d.ts +3 -0
  11. package/dist/admin/admin-bin.d.ts.map +1 -0
  12. package/dist/admin/admin-bin.js +61 -0
  13. package/dist/admin/admin-bin.js.map +1 -0
  14. package/dist/admin/admin-plugin-types.d.ts +29 -0
  15. package/dist/admin/admin-plugin-types.d.ts.map +1 -0
  16. package/dist/admin/admin-plugin-types.js +3 -0
  17. package/dist/admin/admin-plugin-types.js.map +1 -0
  18. package/dist/admin/admin-server.d.ts +98 -0
  19. package/dist/admin/admin-server.d.ts.map +1 -0
  20. package/dist/admin/admin-server.js +426 -0
  21. package/dist/admin/admin-server.js.map +1 -0
  22. package/dist/admin/graphql-utils.d.ts +4 -0
  23. package/dist/admin/graphql-utils.d.ts.map +1 -0
  24. package/dist/admin/graphql-utils.js +28 -0
  25. package/dist/admin/graphql-utils.js.map +1 -0
  26. package/dist/admin/mockttp-admin-model.d.ts +7 -0
  27. package/dist/admin/mockttp-admin-model.d.ts.map +1 -0
  28. package/dist/admin/mockttp-admin-model.js +214 -0
  29. package/dist/admin/mockttp-admin-model.js.map +1 -0
  30. package/dist/admin/mockttp-admin-plugin.d.ts +28 -0
  31. package/dist/admin/mockttp-admin-plugin.d.ts.map +1 -0
  32. package/dist/admin/mockttp-admin-plugin.js +37 -0
  33. package/dist/admin/mockttp-admin-plugin.js.map +1 -0
  34. package/dist/admin/mockttp-admin-server.d.ts +16 -0
  35. package/dist/admin/mockttp-admin-server.d.ts.map +1 -0
  36. package/dist/admin/mockttp-admin-server.js +17 -0
  37. package/dist/admin/mockttp-admin-server.js.map +1 -0
  38. package/dist/admin/mockttp-schema.d.ts +2 -0
  39. package/dist/admin/mockttp-schema.d.ts.map +1 -0
  40. package/dist/admin/mockttp-schema.js +225 -0
  41. package/dist/admin/mockttp-schema.js.map +1 -0
  42. package/dist/client/admin-client.d.ts +112 -0
  43. package/dist/client/admin-client.d.ts.map +1 -0
  44. package/dist/client/admin-client.js +511 -0
  45. package/dist/client/admin-client.js.map +1 -0
  46. package/dist/client/admin-query.d.ts +13 -0
  47. package/dist/client/admin-query.d.ts.map +1 -0
  48. package/dist/client/admin-query.js +26 -0
  49. package/dist/client/admin-query.js.map +1 -0
  50. package/dist/client/mocked-endpoint-client.d.ts +12 -0
  51. package/dist/client/mocked-endpoint-client.d.ts.map +1 -0
  52. package/dist/client/mocked-endpoint-client.js +33 -0
  53. package/dist/client/mocked-endpoint-client.js.map +1 -0
  54. package/dist/client/mockttp-admin-request-builder.d.ts +38 -0
  55. package/dist/client/mockttp-admin-request-builder.d.ts.map +1 -0
  56. package/dist/client/mockttp-admin-request-builder.js +462 -0
  57. package/dist/client/mockttp-admin-request-builder.js.map +1 -0
  58. package/dist/client/mockttp-client.d.ts +56 -0
  59. package/dist/client/mockttp-client.d.ts.map +1 -0
  60. package/dist/client/mockttp-client.js +112 -0
  61. package/dist/client/mockttp-client.js.map +1 -0
  62. package/dist/client/schema-introspection.d.ts +11 -0
  63. package/dist/client/schema-introspection.d.ts.map +1 -0
  64. package/dist/client/schema-introspection.js +128 -0
  65. package/dist/client/schema-introspection.js.map +1 -0
  66. package/dist/main.browser.d.ts +49 -0
  67. package/dist/main.browser.d.ts.map +1 -0
  68. package/dist/main.browser.js +57 -0
  69. package/dist/main.browser.js.map +1 -0
  70. package/dist/main.d.ts +86 -0
  71. package/dist/main.d.ts.map +1 -0
  72. package/dist/main.js +108 -0
  73. package/dist/main.js.map +1 -0
  74. package/dist/mockttp.d.ts +774 -0
  75. package/dist/mockttp.d.ts.map +1 -0
  76. package/dist/mockttp.js +81 -0
  77. package/dist/mockttp.js.map +1 -0
  78. package/dist/pluggable-admin-api/mockttp-pluggable-admin.browser.d.ts +5 -0
  79. package/dist/pluggable-admin-api/mockttp-pluggable-admin.browser.d.ts.map +1 -0
  80. package/dist/pluggable-admin-api/mockttp-pluggable-admin.browser.js +12 -0
  81. package/dist/pluggable-admin-api/mockttp-pluggable-admin.browser.js.map +1 -0
  82. package/dist/pluggable-admin-api/mockttp-pluggable-admin.d.ts +8 -0
  83. package/dist/pluggable-admin-api/mockttp-pluggable-admin.d.ts.map +1 -0
  84. package/dist/pluggable-admin-api/mockttp-pluggable-admin.js +13 -0
  85. package/dist/pluggable-admin-api/mockttp-pluggable-admin.js.map +1 -0
  86. package/dist/pluggable-admin-api/pluggable-admin.browser.d.ts +6 -0
  87. package/dist/pluggable-admin-api/pluggable-admin.browser.d.ts.map +1 -0
  88. package/dist/pluggable-admin-api/pluggable-admin.browser.js +13 -0
  89. package/dist/pluggable-admin-api/pluggable-admin.browser.js.map +1 -0
  90. package/dist/pluggable-admin-api/pluggable-admin.d.ts +18 -0
  91. package/dist/pluggable-admin-api/pluggable-admin.d.ts.map +1 -0
  92. package/dist/pluggable-admin-api/pluggable-admin.js +20 -0
  93. package/dist/pluggable-admin-api/pluggable-admin.js.map +1 -0
  94. package/dist/rules/base-rule-builder.d.ts +185 -0
  95. package/dist/rules/base-rule-builder.d.ts.map +1 -0
  96. package/dist/rules/base-rule-builder.js +251 -0
  97. package/dist/rules/base-rule-builder.js.map +1 -0
  98. package/dist/rules/completion-checkers.d.ts +41 -0
  99. package/dist/rules/completion-checkers.d.ts.map +1 -0
  100. package/dist/rules/completion-checkers.js +87 -0
  101. package/dist/rules/completion-checkers.js.map +1 -0
  102. package/dist/rules/http-agents.d.ts +11 -0
  103. package/dist/rules/http-agents.d.ts.map +1 -0
  104. package/dist/rules/http-agents.js +91 -0
  105. package/dist/rules/http-agents.js.map +1 -0
  106. package/dist/rules/matchers.d.ts +214 -0
  107. package/dist/rules/matchers.d.ts.map +1 -0
  108. package/dist/rules/matchers.js +515 -0
  109. package/dist/rules/matchers.js.map +1 -0
  110. package/dist/rules/passthrough-handling-definitions.d.ts +106 -0
  111. package/dist/rules/passthrough-handling-definitions.d.ts.map +1 -0
  112. package/dist/rules/passthrough-handling-definitions.js +3 -0
  113. package/dist/rules/passthrough-handling-definitions.js.map +1 -0
  114. package/dist/rules/passthrough-handling.d.ts +33 -0
  115. package/dist/rules/passthrough-handling.d.ts.map +1 -0
  116. package/dist/rules/passthrough-handling.js +294 -0
  117. package/dist/rules/passthrough-handling.js.map +1 -0
  118. package/dist/rules/proxy-config.d.ts +76 -0
  119. package/dist/rules/proxy-config.d.ts.map +1 -0
  120. package/dist/rules/proxy-config.js +48 -0
  121. package/dist/rules/proxy-config.js.map +1 -0
  122. package/dist/rules/requests/request-handler-definitions.d.ts +600 -0
  123. package/dist/rules/requests/request-handler-definitions.d.ts.map +1 -0
  124. package/dist/rules/requests/request-handler-definitions.js +423 -0
  125. package/dist/rules/requests/request-handler-definitions.js.map +1 -0
  126. package/dist/rules/requests/request-handlers.d.ts +65 -0
  127. package/dist/rules/requests/request-handlers.d.ts.map +1 -0
  128. package/dist/rules/requests/request-handlers.js +1014 -0
  129. package/dist/rules/requests/request-handlers.js.map +1 -0
  130. package/dist/rules/requests/request-rule-builder.d.ts +255 -0
  131. package/dist/rules/requests/request-rule-builder.d.ts.map +1 -0
  132. package/dist/rules/requests/request-rule-builder.js +340 -0
  133. package/dist/rules/requests/request-rule-builder.js.map +1 -0
  134. package/dist/rules/requests/request-rule.d.ts +36 -0
  135. package/dist/rules/requests/request-rule.d.ts.map +1 -0
  136. package/dist/rules/requests/request-rule.js +100 -0
  137. package/dist/rules/requests/request-rule.js.map +1 -0
  138. package/dist/rules/rule-deserialization.d.ts +8 -0
  139. package/dist/rules/rule-deserialization.d.ts.map +1 -0
  140. package/dist/rules/rule-deserialization.js +27 -0
  141. package/dist/rules/rule-deserialization.js.map +1 -0
  142. package/dist/rules/rule-parameters.d.ts +21 -0
  143. package/dist/rules/rule-parameters.d.ts.map +1 -0
  144. package/dist/rules/rule-parameters.js +31 -0
  145. package/dist/rules/rule-parameters.js.map +1 -0
  146. package/dist/rules/rule-serialization.d.ts +7 -0
  147. package/dist/rules/rule-serialization.d.ts.map +1 -0
  148. package/dist/rules/rule-serialization.js +25 -0
  149. package/dist/rules/rule-serialization.js.map +1 -0
  150. package/dist/rules/websockets/websocket-handler-definitions.d.ts +78 -0
  151. package/dist/rules/websockets/websocket-handler-definitions.d.ts.map +1 -0
  152. package/dist/rules/websockets/websocket-handler-definitions.js +118 -0
  153. package/dist/rules/websockets/websocket-handler-definitions.js.map +1 -0
  154. package/dist/rules/websockets/websocket-handlers.d.ts +39 -0
  155. package/dist/rules/websockets/websocket-handlers.d.ts.map +1 -0
  156. package/dist/rules/websockets/websocket-handlers.js +356 -0
  157. package/dist/rules/websockets/websocket-handlers.js.map +1 -0
  158. package/dist/rules/websockets/websocket-rule-builder.d.ts +173 -0
  159. package/dist/rules/websockets/websocket-rule-builder.d.ts.map +1 -0
  160. package/dist/rules/websockets/websocket-rule-builder.js +232 -0
  161. package/dist/rules/websockets/websocket-rule-builder.js.map +1 -0
  162. package/dist/rules/websockets/websocket-rule.d.ts +34 -0
  163. package/dist/rules/websockets/websocket-rule.d.ts.map +1 -0
  164. package/dist/rules/websockets/websocket-rule.js +87 -0
  165. package/dist/rules/websockets/websocket-rule.js.map +1 -0
  166. package/dist/serialization/body-serialization.d.ts +43 -0
  167. package/dist/serialization/body-serialization.d.ts.map +1 -0
  168. package/dist/serialization/body-serialization.js +70 -0
  169. package/dist/serialization/body-serialization.js.map +1 -0
  170. package/dist/serialization/serialization.d.ts +63 -0
  171. package/dist/serialization/serialization.d.ts.map +1 -0
  172. package/dist/serialization/serialization.js +263 -0
  173. package/dist/serialization/serialization.js.map +1 -0
  174. package/dist/server/http-combo-server.d.ts +13 -0
  175. package/dist/server/http-combo-server.d.ts.map +1 -0
  176. package/dist/server/http-combo-server.js +330 -0
  177. package/dist/server/http-combo-server.js.map +1 -0
  178. package/dist/server/mocked-endpoint.d.ts +14 -0
  179. package/dist/server/mocked-endpoint.d.ts.map +1 -0
  180. package/dist/server/mocked-endpoint.js +40 -0
  181. package/dist/server/mocked-endpoint.js.map +1 -0
  182. package/dist/server/mockttp-server.d.ts +87 -0
  183. package/dist/server/mockttp-server.d.ts.map +1 -0
  184. package/dist/server/mockttp-server.js +859 -0
  185. package/dist/server/mockttp-server.js.map +1 -0
  186. package/dist/types.d.ts +359 -0
  187. package/dist/types.d.ts.map +1 -0
  188. package/dist/types.js +20 -0
  189. package/dist/types.js.map +1 -0
  190. package/dist/util/buffer-utils.d.ts +13 -0
  191. package/dist/util/buffer-utils.d.ts.map +1 -0
  192. package/dist/util/buffer-utils.js +141 -0
  193. package/dist/util/buffer-utils.js.map +1 -0
  194. package/dist/util/dns.d.ts +11 -0
  195. package/dist/util/dns.d.ts.map +1 -0
  196. package/dist/util/dns.js +47 -0
  197. package/dist/util/dns.js.map +1 -0
  198. package/dist/util/error.d.ts +9 -0
  199. package/dist/util/error.d.ts.map +1 -0
  200. package/dist/util/error.js +11 -0
  201. package/dist/util/error.js.map +1 -0
  202. package/dist/util/header-utils.d.ts +35 -0
  203. package/dist/util/header-utils.d.ts.map +1 -0
  204. package/dist/util/header-utils.js +200 -0
  205. package/dist/util/header-utils.js.map +1 -0
  206. package/dist/util/openssl-compat.d.ts +2 -0
  207. package/dist/util/openssl-compat.d.ts.map +1 -0
  208. package/dist/util/openssl-compat.js +26 -0
  209. package/dist/util/openssl-compat.js.map +1 -0
  210. package/dist/util/promise.d.ts +10 -0
  211. package/dist/util/promise.d.ts.map +1 -0
  212. package/dist/util/promise.js +25 -0
  213. package/dist/util/promise.js.map +1 -0
  214. package/dist/util/request-utils.d.ts +46 -0
  215. package/dist/util/request-utils.d.ts.map +1 -0
  216. package/dist/util/request-utils.js +462 -0
  217. package/dist/util/request-utils.js.map +1 -0
  218. package/dist/util/server-utils.d.ts +2 -0
  219. package/dist/util/server-utils.d.ts.map +1 -0
  220. package/dist/util/server-utils.js +14 -0
  221. package/dist/util/server-utils.js.map +1 -0
  222. package/dist/util/socket-util.d.ts +28 -0
  223. package/dist/util/socket-util.d.ts.map +1 -0
  224. package/dist/util/socket-util.js +174 -0
  225. package/dist/util/socket-util.js.map +1 -0
  226. package/dist/util/tls.d.ts +68 -0
  227. package/dist/util/tls.d.ts.map +1 -0
  228. package/dist/util/tls.js +220 -0
  229. package/dist/util/tls.js.map +1 -0
  230. package/dist/util/type-utils.d.ts +14 -0
  231. package/dist/util/type-utils.d.ts.map +1 -0
  232. package/dist/util/type-utils.js +3 -0
  233. package/dist/util/type-utils.js.map +1 -0
  234. package/dist/util/url.d.ts +17 -0
  235. package/dist/util/url.d.ts.map +1 -0
  236. package/dist/util/url.js +96 -0
  237. package/dist/util/url.js.map +1 -0
  238. package/dist/util/util.d.ts +8 -0
  239. package/dist/util/util.d.ts.map +1 -0
  240. package/dist/util/util.js +41 -0
  241. package/dist/util/util.js.map +1 -0
  242. package/docs/api-docs-landing-page.md +11 -0
  243. package/docs/runkitExample.js +16 -0
  244. package/docs/setup.md +136 -0
  245. package/nfyb8qx5.cjs +1 -0
  246. package/package.json +194 -4
  247. package/src/admin/admin-bin.ts +62 -0
  248. package/src/admin/admin-plugin-types.ts +29 -0
  249. package/src/admin/admin-server.ts +619 -0
  250. package/src/admin/graphql-utils.ts +28 -0
  251. package/src/admin/mockttp-admin-model.ts +264 -0
  252. package/src/admin/mockttp-admin-plugin.ts +59 -0
  253. package/src/admin/mockttp-admin-server.ts +27 -0
  254. package/src/admin/mockttp-schema.ts +222 -0
  255. package/src/client/admin-client.ts +652 -0
  256. package/src/client/admin-query.ts +52 -0
  257. package/src/client/mocked-endpoint-client.ts +32 -0
  258. package/src/client/mockttp-admin-request-builder.ts +540 -0
  259. package/src/client/mockttp-client.ts +178 -0
  260. package/src/client/schema-introspection.ts +131 -0
  261. package/src/main.browser.ts +60 -0
  262. package/src/main.ts +160 -0
  263. package/src/mockttp.ts +926 -0
  264. package/src/pluggable-admin-api/mockttp-pluggable-admin.browser.ts +7 -0
  265. package/src/pluggable-admin-api/mockttp-pluggable-admin.ts +13 -0
  266. package/src/pluggable-admin-api/pluggable-admin.browser.ts +9 -0
  267. package/src/pluggable-admin-api/pluggable-admin.ts +36 -0
  268. package/src/rules/base-rule-builder.ts +312 -0
  269. package/src/rules/completion-checkers.ts +90 -0
  270. package/src/rules/http-agents.ts +119 -0
  271. package/src/rules/matchers.ts +665 -0
  272. package/src/rules/passthrough-handling-definitions.ts +111 -0
  273. package/src/rules/passthrough-handling.ts +376 -0
  274. package/src/rules/proxy-config.ts +136 -0
  275. package/src/rules/requests/request-handler-definitions.ts +1089 -0
  276. package/src/rules/requests/request-handlers.ts +1369 -0
  277. package/src/rules/requests/request-rule-builder.ts +481 -0
  278. package/src/rules/requests/request-rule.ts +148 -0
  279. package/src/rules/rule-deserialization.ts +55 -0
  280. package/src/rules/rule-parameters.ts +41 -0
  281. package/src/rules/rule-serialization.ts +29 -0
  282. package/src/rules/websockets/websocket-handler-definitions.ts +196 -0
  283. package/src/rules/websockets/websocket-handlers.ts +509 -0
  284. package/src/rules/websockets/websocket-rule-builder.ts +275 -0
  285. package/src/rules/websockets/websocket-rule.ts +136 -0
  286. package/src/serialization/body-serialization.ts +84 -0
  287. package/src/serialization/serialization.ts +373 -0
  288. package/src/server/http-combo-server.ts +424 -0
  289. package/src/server/mocked-endpoint.ts +44 -0
  290. package/src/server/mockttp-server.ts +1110 -0
  291. package/src/types.ts +433 -0
  292. package/src/util/buffer-utils.ts +164 -0
  293. package/src/util/dns.ts +52 -0
  294. package/src/util/error.ts +18 -0
  295. package/src/util/header-utils.ts +220 -0
  296. package/src/util/openssl-compat.ts +26 -0
  297. package/src/util/promise.ts +31 -0
  298. package/src/util/request-utils.ts +607 -0
  299. package/src/util/server-utils.ts +18 -0
  300. package/src/util/socket-util.ts +193 -0
  301. package/src/util/tls.ts +348 -0
  302. package/src/util/type-utils.ts +15 -0
  303. package/src/util/url.ts +113 -0
  304. 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
+ }