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,1369 @@
1
+ import _ = require('lodash');
2
+ import url = require('url');
3
+ import type dns = require('dns');
4
+ import net = require('net');
5
+ import tls = require('tls');
6
+ import http = require('http');
7
+ import https = require('https');
8
+ import * as fs from 'fs/promises';
9
+ import * as h2Client from 'http2-wrapper';
10
+ import { decode as decodeBase64 } from 'base64-arraybuffer';
11
+ import { Transform } from 'stream';
12
+ import { stripIndent, oneLine } from 'common-tags';
13
+ import { TypedError } from 'typed-error';
14
+ import { applyPatch as applyJsonPatch } from 'fast-json-patch';
15
+
16
+ import {
17
+ Headers,
18
+ OngoingRequest,
19
+ CompletedRequest,
20
+ OngoingResponse
21
+ } from "../../types";
22
+
23
+ import { MaybePromise } from '../../util/type-utils';
24
+ import { isAbsoluteUrl, getEffectivePort } from '../../util/url';
25
+ import {
26
+ waitForCompletedRequest,
27
+ buildBodyReader,
28
+ shouldKeepAlive,
29
+ isHttp2,
30
+ writeHead,
31
+ encodeBodyBuffer
32
+ } from '../../util/request-utils';
33
+ import {
34
+ h1HeadersToH2,
35
+ h2HeadersToH1,
36
+ objectHeadersToRaw,
37
+ rawHeadersToObject,
38
+ rawHeadersToObjectPreservingCase,
39
+ flattenPairedRawHeaders,
40
+ pairFlatRawHeaders,
41
+ findRawHeaderIndex,
42
+ dropDefaultHeaders,
43
+ validateHeader
44
+ } from '../../util/header-utils';
45
+ import { streamToBuffer, asBuffer } from '../../util/buffer-utils';
46
+ import {
47
+ isLocalPortActive,
48
+ isSocketLoop,
49
+ requireSocketResetSupport,
50
+ resetOrDestroy
51
+ } from '../../util/socket-util';
52
+ import {
53
+ ClientServerChannel,
54
+ deserializeBuffer,
55
+ deserializeProxyConfig
56
+ } from '../../serialization/serialization';
57
+ import {
58
+ withSerializedBodyReader,
59
+ withDeserializedCallbackBuffers,
60
+ WithSerializedCallbackBuffers
61
+ } from '../../serialization/body-serialization';
62
+ import { ErrorLike, isErrorLike } from '../../util/error';
63
+
64
+ import { assertParamDereferenced, RuleParameters } from '../rule-parameters';
65
+
66
+ import { getAgent } from '../http-agents';
67
+ import { ProxySettingSource } from '../proxy-config';
68
+ import {
69
+ ForwardingOptions,
70
+ PassThroughLookupOptions,
71
+ } from '../passthrough-handling-definitions';
72
+ import {
73
+ getContentLengthAfterModification,
74
+ getHostAfterModification,
75
+ getH2HeadersAfterModification,
76
+ OVERRIDABLE_REQUEST_PSEUDOHEADERS,
77
+ buildOverriddenBody,
78
+ getUpstreamTlsOptions,
79
+ shouldUseStrictHttps,
80
+ getClientRelativeHostname,
81
+ getDnsLookupFunction,
82
+ getTrustedCAs
83
+ } from '../passthrough-handling';
84
+
85
+ import {
86
+ BeforePassthroughRequestRequest,
87
+ BeforePassthroughResponseRequest,
88
+ CallbackHandlerDefinition,
89
+ CallbackRequestMessage,
90
+ CallbackRequestResult,
91
+ CallbackResponseMessageResult,
92
+ CallbackResponseResult,
93
+ CloseConnectionHandlerDefinition,
94
+ FileHandlerDefinition,
95
+ HandlerDefinitionLookup,
96
+ JsonRpcResponseHandlerDefinition,
97
+ PassThroughHandlerDefinition,
98
+ PassThroughHandlerOptions,
99
+ PassThroughResponse,
100
+ RequestHandlerDefinition,
101
+ RequestTransform,
102
+ ResetConnectionHandlerDefinition,
103
+ ResponseTransform,
104
+ SerializedBuffer,
105
+ SerializedCallbackHandlerData,
106
+ SerializedPassThroughData,
107
+ SerializedStreamHandlerData,
108
+ SERIALIZED_OMIT,
109
+ SimpleHandlerDefinition,
110
+ StreamHandlerDefinition,
111
+ TimeoutHandlerDefinition
112
+ } from './request-handler-definitions';
113
+
114
+ // Re-export various type definitions. This is mostly for compatibility with external
115
+ // code that's manually building rule definitions.
116
+ export {
117
+ CallbackRequestResult,
118
+ CallbackResponseMessageResult,
119
+ CallbackResponseResult,
120
+ ForwardingOptions,
121
+ PassThroughResponse,
122
+ PassThroughHandlerOptions,
123
+ PassThroughLookupOptions,
124
+ RequestTransform,
125
+ ResponseTransform
126
+ }
127
+
128
+ // An error that indicates that the handler is aborting the request.
129
+ // This could be intentional, or an upstream server aborting the request.
130
+ export class AbortError extends TypedError {
131
+
132
+ constructor(
133
+ message: string,
134
+ readonly code?: string
135
+ ) {
136
+ super(message);
137
+ }
138
+
139
+ }
140
+
141
+ function isSerializedBuffer(obj: any): obj is SerializedBuffer {
142
+ return obj && obj.type === 'Buffer' && !!obj.data;
143
+ }
144
+
145
+ export interface RequestHandler extends RequestHandlerDefinition {
146
+ handle(
147
+ request: OngoingRequest,
148
+ response: OngoingResponse,
149
+ options: RequestHandlerOptions
150
+ ): Promise<void>;
151
+ }
152
+
153
+ export interface RequestHandlerOptions {
154
+ emitEventCallback?: (type: string, event: unknown) => void;
155
+ }
156
+
157
+ export class SimpleHandler extends SimpleHandlerDefinition {
158
+ async handle(_request: OngoingRequest, response: OngoingResponse) {
159
+ if (this.headers) dropDefaultHeaders(response);
160
+ writeHead(response, this.status, this.statusMessage, this.headers);
161
+
162
+ if (isSerializedBuffer(this.data)) {
163
+ this.data = Buffer.from(<any> this.data);
164
+ }
165
+
166
+ if (this.trailers) {
167
+ response.addTrailers(this.trailers);
168
+ }
169
+
170
+ response.end(this.data || "");
171
+ }
172
+ }
173
+
174
+ async function writeResponseFromCallback(
175
+ result: CallbackResponseMessageResult,
176
+ response: OngoingResponse
177
+ ) {
178
+ if (result.json !== undefined) {
179
+ result.headers = Object.assign(result.headers || {}, {
180
+ 'Content-Type': 'application/json'
181
+ });
182
+ result.body = JSON.stringify(result.json);
183
+ delete result.json;
184
+ }
185
+
186
+ if (result.headers) {
187
+ dropDefaultHeaders(response);
188
+ validateCustomHeaders({}, result.headers);
189
+ }
190
+
191
+ if (result.body && !result.rawBody) {
192
+ // RawBody takes priority if both are set (useful for backward compat) but if not then
193
+ // the body is automatically encoded to match the content-encoding header.
194
+ result.rawBody = await encodeBodyBuffer(
195
+ Buffer.from(result.body),
196
+ result.headers ?? {}
197
+ );
198
+ }
199
+
200
+ writeHead(
201
+ response,
202
+ result.statusCode || result.status || 200,
203
+ result.statusMessage,
204
+ result.headers
205
+ );
206
+
207
+ if (result.trailers) response.addTrailers(result.trailers);
208
+
209
+ response.end(result.rawBody || "");
210
+ }
211
+
212
+ export class CallbackHandler extends CallbackHandlerDefinition {
213
+
214
+ async handle(request: OngoingRequest, response: OngoingResponse) {
215
+ let req = await waitForCompletedRequest(request);
216
+
217
+ let outResponse: CallbackResponseResult;
218
+ try {
219
+ outResponse = await this.callback(req);
220
+ } catch (error) {
221
+ writeHead(response, 500, 'Callback handler threw an exception');
222
+ console.warn(`Callback handler exception: ${(error as ErrorLike).message ?? error}`);
223
+ response.end(isErrorLike(error) ? error.toString() : error);
224
+ return;
225
+ }
226
+
227
+ if (outResponse === 'close') {
228
+ (request as any).socket.end();
229
+ throw new AbortError('Connection closed intentionally by rule');
230
+ } else if (outResponse === 'reset') {
231
+ requireSocketResetSupport();
232
+ resetOrDestroy(request);
233
+ throw new AbortError('Connection reset intentionally by rule');
234
+ } else {
235
+ await writeResponseFromCallback(outResponse, response);
236
+ }
237
+ }
238
+
239
+ /**
240
+ * @internal
241
+ */
242
+ static deserialize({ name, version }: SerializedCallbackHandlerData, channel: ClientServerChannel): CallbackHandler {
243
+ const rpcCallback = async (request: CompletedRequest) => {
244
+ const callbackResult = await channel.request<
245
+ CallbackRequestMessage,
246
+ | WithSerializedCallbackBuffers<CallbackResponseMessageResult>
247
+ | 'close'
248
+ | 'reset'
249
+ >({ args: [
250
+ (version || -1) >= 2
251
+ ? withSerializedBodyReader(request)
252
+ : request // Backward compat: old handlers
253
+ ] });
254
+
255
+ if (typeof callbackResult === 'string') {
256
+ return callbackResult;
257
+ } else {
258
+ return withDeserializedCallbackBuffers(callbackResult);
259
+ }
260
+ };
261
+ // Pass across the name from the real callback, for explain()
262
+ Object.defineProperty(rpcCallback, "name", { value: name });
263
+
264
+ // Call the client's callback (via stream), and save a handler on our end for
265
+ // the response that comes back.
266
+ return new CallbackHandler(rpcCallback);
267
+ }
268
+ }
269
+
270
+ export class StreamHandler extends StreamHandlerDefinition {
271
+
272
+ async handle(_request: OngoingRequest, response: OngoingResponse) {
273
+ if (!this.stream.done) {
274
+ if (this.headers) dropDefaultHeaders(response);
275
+
276
+ writeHead(response, this.status, undefined, this.headers);
277
+ this.stream.pipe(response);
278
+ this.stream.done = true;
279
+ } else {
280
+ throw new Error(stripIndent`
281
+ Stream request handler called more than once - this is not supported.
282
+
283
+ Streams can typically only be read once, so all subsequent requests would be empty.
284
+ To mock repeated stream requests, call 'thenStream' repeatedly with multiple streams.
285
+
286
+ (Have a better way to handle this? Open an issue at ${require('../../../package.json').bugs.url})
287
+ `);
288
+ }
289
+ }
290
+
291
+ /**
292
+ * @internal
293
+ */
294
+ static deserialize(handlerData: SerializedStreamHandlerData, channel: ClientServerChannel): StreamHandler {
295
+ const handlerStream = new Transform({
296
+ objectMode: true,
297
+ transform: function (this: Transform, message, encoding, callback) {
298
+ const { event, content } = message;
299
+
300
+ let deserializedEventData = content && (
301
+ content.type === 'string' ? content.value :
302
+ content.type === 'buffer' ? Buffer.from(content.value, 'base64') :
303
+ content.type === 'arraybuffer' ? Buffer.from(decodeBase64(content.value)) :
304
+ content.type === 'nil' && undefined
305
+ );
306
+
307
+ if (event === 'data' && deserializedEventData) {
308
+ this.push(deserializedEventData);
309
+ } else if (event === 'end') {
310
+ this.end();
311
+ }
312
+
313
+ callback();
314
+ }
315
+ });
316
+
317
+ // When we get piped (i.e. to a live request), ping upstream to start streaming, and then
318
+ // pipe the resulting data into our live stream (which is streamed to the request, like normal)
319
+ handlerStream.once('resume', () => {
320
+ channel.pipe(handlerStream);
321
+ channel.write({});
322
+ });
323
+
324
+ return new StreamHandler(
325
+ handlerData.status,
326
+ handlerStream,
327
+ handlerData.headers
328
+ );
329
+ }
330
+ }
331
+
332
+ export class FileHandler extends FileHandlerDefinition {
333
+ async handle(_request: OngoingRequest, response: OngoingResponse) {
334
+ // Read the file first, to ensure we error cleanly if it's unavailable
335
+ const fileContents = await fs.readFile(this.filePath);
336
+
337
+ if (this.headers) dropDefaultHeaders(response);
338
+
339
+ writeHead(response, this.status, this.statusMessage, this.headers);
340
+ response.end(fileContents);
341
+ }
342
+ }
343
+
344
+ function validateCustomHeaders(
345
+ originalHeaders: Headers,
346
+ modifiedHeaders: Headers | undefined,
347
+ headerWhitelist: readonly string[] = []
348
+ ) {
349
+ if (!modifiedHeaders) return;
350
+
351
+ // We ignore most returned pseudo headers, so we error if you try to manually set them
352
+ const invalidHeaders = _(modifiedHeaders)
353
+ .pickBy((value, name) =>
354
+ name.toString().startsWith(':') &&
355
+ // We allow returning a preexisting header value - that's ignored
356
+ // silently, so that mutating & returning the provided headers is always safe.
357
+ value !== originalHeaders[name] &&
358
+ // In some cases, specific custom pseudoheaders may be allowed, e.g. requests
359
+ // can have custom :scheme and :authority headers set.
360
+ !headerWhitelist.includes(name)
361
+ )
362
+ .keys();
363
+
364
+ if (invalidHeaders.size() > 0) {
365
+ throw new Error(
366
+ `Cannot set custom ${invalidHeaders.join(', ')} pseudoheader values`
367
+ );
368
+ }
369
+ }
370
+
371
+ // Used in merging as a marker for values to omit, because lodash ignores undefineds.
372
+ const OMIT_SYMBOL = Symbol('omit-value');
373
+
374
+ // We play some games to preserve undefined values during serialization, because we differentiate them
375
+ // in some transforms from null/not-present keys.
376
+ const mapOmitToUndefined = <T extends { [key: string]: any }>(
377
+ input: T
378
+ ): { [K in keyof T]: T[K] | undefined } =>
379
+ _.mapValues(input, (v) =>
380
+ v === SERIALIZED_OMIT || v === OMIT_SYMBOL
381
+ ? undefined // Replace our omit placeholders with actual undefineds
382
+ : v
383
+ );
384
+
385
+ export class PassThroughHandler extends PassThroughHandlerDefinition {
386
+
387
+ private _trustedCACertificates: MaybePromise<Array<string> | undefined>;
388
+ private async trustedCACertificates(): Promise<Array<string> | undefined> {
389
+ if (!this.extraCACertificates.length) return undefined;
390
+
391
+ if (!this._trustedCACertificates) {
392
+ this._trustedCACertificates = getTrustedCAs(undefined, this.extraCACertificates);
393
+ }
394
+
395
+ return this._trustedCACertificates;
396
+ }
397
+
398
+ async handle(
399
+ clientReq: OngoingRequest,
400
+ clientRes: OngoingResponse,
401
+ options: RequestHandlerOptions
402
+ ) {
403
+ // Don't let Node add any default standard headers - we want full control
404
+ dropDefaultHeaders(clientRes);
405
+
406
+ // Capture raw request data:
407
+ let { method, url: reqUrl, rawHeaders } = clientReq as OngoingRequest;
408
+ let { protocol, hostname, port, path } = url.parse(reqUrl);
409
+
410
+ // Check if this request is a request loop:
411
+ if (isSocketLoop(this.outgoingSockets, (<any> clientReq).socket)) {
412
+ throw new Error(oneLine`
413
+ Passthrough loop detected. This probably means you're sending a request directly
414
+ to a passthrough endpoint, which is forwarding it to the target URL, which is a
415
+ passthrough endpoint, which is forwarding it to the target URL, which is a
416
+ passthrough endpoint...` +
417
+ '\n\n' + oneLine`
418
+ You should either explicitly mock a response for this URL (${reqUrl}), or use
419
+ the server as a proxy, instead of making requests to it directly.
420
+ `);
421
+ }
422
+
423
+ // We have to capture the request stream immediately, to make sure nothing is lost if it
424
+ // goes past its max length (truncating the data) before we start sending upstream.
425
+ const clientReqBody = clientReq.body.asStream();
426
+
427
+ const isH2Downstream = isHttp2(clientReq);
428
+
429
+ hostname = await getClientRelativeHostname(
430
+ hostname,
431
+ clientReq.remoteIpAddress,
432
+ getDnsLookupFunction(this.lookupOptions)
433
+ );
434
+
435
+ if (this.forwarding) {
436
+ const { targetHost, updateHostHeader } = this.forwarding;
437
+ if (!targetHost.includes('/')) {
438
+ // We're forwarding to a bare hostname
439
+ [hostname, port] = targetHost.split(':');
440
+ } else {
441
+ // We're forwarding to a fully specified URL; override the host etc, but never the path.
442
+ ({ protocol, hostname, port } = url.parse(targetHost));
443
+ }
444
+
445
+ const hostHeaderName = isH2Downstream ? ':authority' : 'host';
446
+
447
+ let hostHeaderIndex = findRawHeaderIndex(rawHeaders, hostHeaderName);
448
+ let hostHeader: [string, string];
449
+
450
+ if (hostHeaderIndex === -1) {
451
+ // Should never happen really, but just in case:
452
+ hostHeader = [hostHeaderName, hostname!];
453
+ hostHeaderIndex = rawHeaders.length;
454
+ } else {
455
+ // Clone this - we don't want to modify the original headers, as they're used for events
456
+ hostHeader = _.clone(rawHeaders[hostHeaderIndex]);
457
+ }
458
+ rawHeaders[hostHeaderIndex] = hostHeader;
459
+
460
+ if (updateHostHeader === undefined || updateHostHeader === true) {
461
+ // If updateHostHeader is true, or just not specified, match the new target
462
+ hostHeader[1] = hostname + (port ? `:${port}` : '');
463
+ } else if (updateHostHeader) {
464
+ // If it's an explicit custom value, use that directly.
465
+ hostHeader[1] = updateHostHeader;
466
+ } // Otherwise: falsey means don't touch it.
467
+
468
+ reqUrl = new URL(`${protocol}//${hostname}${(port ? `:${port}` : '')}${path}`).toString();
469
+ }
470
+
471
+ // Override the request details, if a transform or callback is specified:
472
+ let reqBodyOverride: Uint8Array | undefined;
473
+
474
+ // Set during modification here - if set, we allow overriding certain H2 headers so that manual
475
+ // modification of the supported headers works as expected.
476
+ let headersManuallyModified = false;
477
+
478
+ if (this.transformRequest) {
479
+ let headers = rawHeadersToObject(rawHeaders);
480
+
481
+ const {
482
+ replaceMethod,
483
+ updateHeaders,
484
+ replaceHeaders,
485
+ replaceBody,
486
+ replaceBodyFromFile,
487
+ updateJsonBody,
488
+ patchJsonBody,
489
+ matchReplaceBody
490
+ } = this.transformRequest;
491
+
492
+ if (replaceMethod) {
493
+ method = replaceMethod;
494
+ }
495
+
496
+ if (updateHeaders) {
497
+ headers = {
498
+ ...headers,
499
+ ...updateHeaders
500
+ };
501
+ headersManuallyModified = true;
502
+ } else if (replaceHeaders) {
503
+ headers = { ...replaceHeaders };
504
+ headersManuallyModified = true;
505
+ }
506
+
507
+ if (replaceBody) {
508
+ // Note that we're replacing the body without actually waiting for the real one, so
509
+ // this can result in sending a request much more quickly!
510
+ reqBodyOverride = asBuffer(replaceBody);
511
+ } else if (replaceBodyFromFile) {
512
+ reqBodyOverride = await fs.readFile(replaceBodyFromFile);
513
+ } else if (updateJsonBody) {
514
+ const { body: realBody } = await waitForCompletedRequest(clientReq);
515
+ const jsonBody = await realBody.getJson();
516
+ if (jsonBody === undefined) {
517
+ throw new Error("Can't update JSON in non-JSON request body");
518
+ }
519
+
520
+ const updatedBody = _.mergeWith(jsonBody, updateJsonBody, (_oldValue, newValue) => {
521
+ // We want to remove values with undefines, but Lodash ignores
522
+ // undefined return values here. Fortunately, JSON.stringify
523
+ // ignores Symbols, omitting them from the result.
524
+ if (newValue === undefined) return OMIT_SYMBOL;
525
+ });
526
+
527
+ reqBodyOverride = asBuffer(JSON.stringify(updatedBody));
528
+ } else if (patchJsonBody) {
529
+ const { body: realBody } = await waitForCompletedRequest(clientReq);
530
+ const jsonBody = await realBody.getJson();
531
+ if (jsonBody === undefined) {
532
+ throw new Error("Can't patch JSON in non-JSON request body");
533
+ }
534
+
535
+ applyJsonPatch(jsonBody, patchJsonBody, true); // Mutates the JSON body returned above
536
+ reqBodyOverride = asBuffer(JSON.stringify(jsonBody));
537
+ } else if (matchReplaceBody) {
538
+ const { body: realBody } = await waitForCompletedRequest(clientReq);
539
+
540
+ const originalBody = await realBody.getText();
541
+ if (originalBody === undefined) {
542
+ throw new Error("Can't match & replace non-decodeable request body");
543
+ }
544
+
545
+ let replacedBody = originalBody;
546
+ for (let [match, result] of matchReplaceBody) {
547
+ replacedBody = replacedBody!.replace(match, result);
548
+ }
549
+
550
+ if (replacedBody !== originalBody) {
551
+ reqBodyOverride = asBuffer(replacedBody);
552
+ }
553
+ }
554
+
555
+ if (reqBodyOverride) {
556
+ // We always re-encode the body to match the resulting content-encoding header:
557
+ reqBodyOverride = await encodeBodyBuffer(
558
+ reqBodyOverride,
559
+ headers
560
+ );
561
+
562
+ headers['content-length'] = getContentLengthAfterModification(
563
+ reqBodyOverride,
564
+ clientReq.headers,
565
+ (updateHeaders && updateHeaders['content-length'] !== undefined)
566
+ ? headers // Iff you replaced the content length
567
+ : replaceHeaders,
568
+ );
569
+ }
570
+
571
+ if (headersManuallyModified || reqBodyOverride) {
572
+ // If the headers have been updated (implicitly or explicitly) we need to regenerate them. We avoid
573
+ // this if possible, because it normalizes headers, which is slightly lossy (e.g. they're lowercased).
574
+ rawHeaders = objectHeadersToRaw(headers);
575
+ }
576
+ } else if (this.beforeRequest) {
577
+ const clientRawHeaders = rawHeaders;
578
+ const clientHeaders = rawHeadersToObject(clientRawHeaders);
579
+
580
+ const completedRequest = await waitForCompletedRequest(clientReq);
581
+
582
+ const modifiedReq = await this.beforeRequest({
583
+ ...completedRequest,
584
+ url: reqUrl, // May have been overwritten by forwarding
585
+ headers: _.cloneDeep(clientHeaders),
586
+ rawHeaders: _.cloneDeep(clientRawHeaders)
587
+ });
588
+
589
+ if (modifiedReq?.response) {
590
+ if (modifiedReq.response === 'close') {
591
+ const socket: net.Socket = (<any> clientReq).socket;
592
+ socket.end();
593
+ throw new AbortError('Connection closed intentionally by rule');
594
+ } else if (modifiedReq.response === 'reset') {
595
+ requireSocketResetSupport();
596
+ resetOrDestroy(clientReq);
597
+ throw new AbortError('Connection reset intentionally by rule');
598
+ } else {
599
+ // The callback has provided a full response: don't passthrough at all, just use it.
600
+ await writeResponseFromCallback(modifiedReq.response, clientRes);
601
+ return;
602
+ }
603
+ }
604
+
605
+ method = modifiedReq?.method || method;
606
+ reqUrl = modifiedReq?.url || reqUrl;
607
+
608
+ headersManuallyModified = !!modifiedReq?.headers;
609
+ let headers = modifiedReq?.headers || clientHeaders;
610
+
611
+ // We need to make sure the Host/:authority header is updated correctly - following the user's returned value if
612
+ // they provided one, but updating it if not to match the effective target URL of the request:
613
+ const expectedTargetUrl = modifiedReq?.url
614
+ ?? (
615
+ // If not overridden, we fall back to the original value, but we need to handle changes that forwarding
616
+ // might have made as well, especially if it's intentionally left URL & headers out of sync:
617
+ this.forwarding?.updateHostHeader === false
618
+ ? clientReq.url
619
+ : reqUrl
620
+ );
621
+
622
+ Object.assign(headers,
623
+ isH2Downstream
624
+ ? getH2HeadersAfterModification(expectedTargetUrl, clientHeaders, modifiedReq?.headers)
625
+ : { 'host': getHostAfterModification(expectedTargetUrl, clientHeaders, modifiedReq?.headers) }
626
+ );
627
+
628
+ validateCustomHeaders(
629
+ clientHeaders,
630
+ modifiedReq?.headers,
631
+ OVERRIDABLE_REQUEST_PSEUDOHEADERS // These are handled by getCorrectPseudoheaders above
632
+ );
633
+
634
+ reqBodyOverride = await buildOverriddenBody(modifiedReq, headers);
635
+
636
+ if (reqBodyOverride) {
637
+ // Automatically match the content-length to the body, unless it was explicitly overriden.
638
+ headers['content-length'] = getContentLengthAfterModification(
639
+ reqBodyOverride,
640
+ clientHeaders,
641
+ modifiedReq?.headers
642
+ );
643
+ }
644
+
645
+ // Reparse the new URL, if necessary
646
+ if (modifiedReq?.url) {
647
+ if (!isAbsoluteUrl(modifiedReq?.url)) throw new Error("Overridden request URLs must be absolute");
648
+ ({ protocol, hostname, port, path } = url.parse(reqUrl));
649
+ }
650
+
651
+ rawHeaders = objectHeadersToRaw(headers);
652
+ }
653
+
654
+ const effectivePort = getEffectivePort({ protocol, port });
655
+
656
+ const strictHttpsChecks = shouldUseStrictHttps(
657
+ hostname!,
658
+ effectivePort,
659
+ this.ignoreHostHttpsErrors
660
+ );
661
+
662
+ // Use a client cert if it's listed for the host+port or whole hostname
663
+ const hostWithPort = `${hostname}:${effectivePort}`;
664
+ const clientCert = this.clientCertificateHostMap[hostWithPort] ||
665
+ this.clientCertificateHostMap[hostname!] ||
666
+ {};
667
+
668
+ const trustedCerts = await this.trustedCACertificates();
669
+ const caConfig = trustedCerts
670
+ ? { ca: trustedCerts }
671
+ : {};
672
+
673
+ // We only do H2 upstream for HTTPS. Http2-wrapper doesn't support H2C, it's rarely used
674
+ // and we can't use ALPN to detect HTTP/2 support cleanly.
675
+ let shouldTryH2Upstream = isH2Downstream && protocol === 'https:';
676
+
677
+ let family: undefined | 4 | 6;
678
+ if (hostname === 'localhost') {
679
+ // Annoying special case: some localhost servers listen only on either ipv4 or ipv6.
680
+ // Very specific situation, but a very common one for development use.
681
+ // We need to work out which one family is, as Node sometimes makes bad choices.
682
+
683
+ if (await isLocalPortActive('::1', effectivePort)) family = 6;
684
+ else family = 4;
685
+ }
686
+
687
+ // Remote clients might configure a passthrough rule with a parameter reference for the proxy,
688
+ // delegating proxy config to the admin server. That's fine initially, but you can't actually
689
+ // handle a request in that case - make sure our proxyConfig is always dereferenced before use.
690
+ const proxySettingSource = assertParamDereferenced(this.proxyConfig) as ProxySettingSource;
691
+
692
+ // Mirror the keep-alive-ness of the incoming request in our outgoing request
693
+ const agent = await getAgent({
694
+ protocol: (protocol || undefined) as 'http:' | 'https:' | undefined,
695
+ hostname: hostname!,
696
+ port: effectivePort,
697
+ tryHttp2: shouldTryH2Upstream,
698
+ keepAlive: shouldKeepAlive(clientReq),
699
+ proxySettingSource
700
+ });
701
+
702
+ if (agent && !('http2' in agent)) {
703
+ // I.e. only use HTTP/2 if we're using an HTTP/2-compatible agent
704
+ shouldTryH2Upstream = false;
705
+ }
706
+
707
+ let makeRequest = (
708
+ shouldTryH2Upstream
709
+ ? (options: any, cb: any) =>
710
+ h2Client.auto(options, cb).catch((e) => {
711
+ // If an error occurs during auto detection via ALPN, that's an
712
+ // TypeError implies it's an invalid HTTP/2 request that was rejected.
713
+ // Anything else implies an upstream HTTP/2 issue.
714
+ e.causedByUpstreamError = !(e instanceof TypeError);
715
+ throw e;
716
+ })
717
+ // HTTP/1 + TLS
718
+ : protocol === 'https:'
719
+ ? https.request
720
+ // HTTP/1 plaintext:
721
+ : http.request
722
+ ) as typeof https.request;
723
+
724
+ if (isH2Downstream && shouldTryH2Upstream) {
725
+ // We drop all incoming pseudoheaders, and regenerate them (except legally modified ones)
726
+ rawHeaders = rawHeaders.filter(([key]) =>
727
+ !key.toString().startsWith(':') ||
728
+ (headersManuallyModified &&
729
+ OVERRIDABLE_REQUEST_PSEUDOHEADERS.includes(key.toLowerCase() as any)
730
+ )
731
+ );
732
+ } else if (isH2Downstream && !shouldTryH2Upstream) {
733
+ rawHeaders = h2HeadersToH1(rawHeaders);
734
+ }
735
+
736
+ // Drop proxy-connection header. This is almost always intended for us, not for upstream servers,
737
+ // and forwarding it causes problems (most notably, it triggers lots of weird-traffic blocks,
738
+ // most notably by Cloudflare).
739
+ rawHeaders = rawHeaders.filter(([key]) =>
740
+ key.toLowerCase() !== 'proxy-connection'
741
+ );
742
+
743
+ let serverReq: http.ClientRequest;
744
+ return new Promise<void>((resolve, reject) => (async () => { // Wrapped to easily catch (a)sync errors
745
+ serverReq = await makeRequest({
746
+ protocol,
747
+ method,
748
+ hostname,
749
+ port,
750
+ family,
751
+ path,
752
+ headers: shouldTryH2Upstream
753
+ ? rawHeadersToObjectPreservingCase(rawHeaders)
754
+ : flattenPairedRawHeaders(rawHeaders) as any,
755
+ lookup: getDnsLookupFunction(this.lookupOptions) as typeof dns.lookup,
756
+ // ^ Cast required to handle __promisify__ type hack in the official Node types
757
+ agent,
758
+
759
+ // TLS options:
760
+ ...getUpstreamTlsOptions(strictHttpsChecks),
761
+ ...clientCert,
762
+ ...caConfig
763
+ }, (serverRes) => (async () => {
764
+ serverRes.on('error', (e: any) => {
765
+ e.causedByUpstreamError = true;
766
+ reject(e);
767
+ });
768
+
769
+ // Forward server trailers, if we receive any:
770
+ serverRes.on('end', () => {
771
+ if (!serverRes.rawTrailers?.length) return;
772
+
773
+ const trailersToForward = pairFlatRawHeaders(serverRes.rawTrailers)
774
+ .filter(([key, value]) => {
775
+ if (!validateHeader(key, value)) {
776
+ console.warn(`Not forwarding invalid trailer: "${key}: ${value}"`);
777
+ // Nothing else we can do in this case regardless - setHeaders will
778
+ // throw within Node if we try to set this value.
779
+ return false;
780
+ }
781
+ return true;
782
+ });
783
+
784
+ try {
785
+ clientRes.addTrailers(
786
+ isHttp2(clientReq)
787
+ // HTTP/2 compat doesn't support raw headers here (yet)
788
+ ? rawHeadersToObjectPreservingCase(trailersToForward)
789
+ : trailersToForward
790
+ );
791
+ } catch (e) {
792
+ console.warn(`Failed to forward response trailers: ${e}`);
793
+ }
794
+ });
795
+
796
+ let serverStatusCode = serverRes.statusCode!;
797
+ let serverStatusMessage = serverRes.statusMessage
798
+ let serverRawHeaders = pairFlatRawHeaders(serverRes.rawHeaders);
799
+
800
+ // This is only set if we need to read the body here, for a callback or similar. If so,
801
+ // we keep the buffer in case we need it afterwards (if the cb doesn't replace it).
802
+ let originalBody: Buffer | undefined;
803
+
804
+ // This is set when we override the body data. Note that this doesn't mean we actually
805
+ // read & buffered the original data! With a fixed replacement body we can skip that.
806
+ let resBodyOverride: Uint8Array | undefined;
807
+
808
+ if (options.emitEventCallback) {
809
+ options.emitEventCallback('passthrough-response-head', {
810
+ statusCode: serverStatusCode,
811
+ statusMessage: serverStatusMessage,
812
+ httpVersion: serverRes.httpVersion,
813
+ rawHeaders: serverRawHeaders
814
+ });
815
+ }
816
+
817
+ if (isH2Downstream) {
818
+ serverRawHeaders = h1HeadersToH2(serverRawHeaders);
819
+ }
820
+
821
+ if (this.transformResponse) {
822
+ let serverHeaders = rawHeadersToObject(serverRawHeaders);
823
+
824
+ const {
825
+ replaceStatus,
826
+ updateHeaders,
827
+ replaceHeaders,
828
+ replaceBody,
829
+ replaceBodyFromFile,
830
+ updateJsonBody,
831
+ patchJsonBody,
832
+ matchReplaceBody
833
+ } = this.transformResponse;
834
+
835
+ if (replaceStatus) {
836
+ serverStatusCode = replaceStatus;
837
+ serverStatusMessage = undefined; // Reset to default
838
+ }
839
+
840
+ if (updateHeaders) {
841
+ serverHeaders = {
842
+ ...serverHeaders,
843
+ ...updateHeaders
844
+ };
845
+ } else if (replaceHeaders) {
846
+ serverHeaders = { ...replaceHeaders };
847
+ }
848
+
849
+ if (replaceBody) {
850
+ // Note that we're replacing the body without actually waiting for the real one, so
851
+ // this can result in sending a request much more quickly!
852
+ resBodyOverride = asBuffer(replaceBody);
853
+ } else if (replaceBodyFromFile) {
854
+ resBodyOverride = await fs.readFile(replaceBodyFromFile);
855
+ } else if (updateJsonBody) {
856
+ originalBody = await streamToBuffer(serverRes);
857
+ const realBody = buildBodyReader(originalBody, serverRes.headers);
858
+ const jsonBody = await realBody.getJson();
859
+
860
+ if (jsonBody === undefined) {
861
+ throw new Error("Can't update JSON in non-JSON response body");
862
+ }
863
+
864
+ const updatedBody = _.mergeWith(jsonBody, updateJsonBody, (_oldValue, newValue) => {
865
+ // We want to remove values with undefines, but Lodash ignores
866
+ // undefined return values here. Fortunately, JSON.stringify
867
+ // ignores Symbols, omitting them from the result.
868
+ if (newValue === undefined) return OMIT_SYMBOL;
869
+ });
870
+
871
+ resBodyOverride = asBuffer(JSON.stringify(updatedBody));
872
+ } else if (patchJsonBody) {
873
+ originalBody = await streamToBuffer(serverRes);
874
+ const realBody = buildBodyReader(originalBody, serverRes.headers);
875
+ const jsonBody = await realBody.getJson();
876
+
877
+ if (jsonBody === undefined) {
878
+ throw new Error("Can't patch JSON in non-JSON response body");
879
+ }
880
+
881
+ applyJsonPatch(jsonBody, patchJsonBody, true); // Mutates the JSON body returned above
882
+ resBodyOverride = asBuffer(JSON.stringify(jsonBody));
883
+ } else if (matchReplaceBody) {
884
+ originalBody = await streamToBuffer(serverRes);
885
+ const realBody = buildBodyReader(originalBody, serverRes.headers);
886
+
887
+ const originalBodyText = await realBody.getText();
888
+ if (originalBodyText === undefined) {
889
+ throw new Error("Can't match & replace non-decodeable response body");
890
+ }
891
+
892
+ let replacedBody = originalBodyText;
893
+ for (let [match, result] of matchReplaceBody) {
894
+ replacedBody = replacedBody!.replace(match, result);
895
+ }
896
+
897
+ if (replacedBody !== originalBodyText) {
898
+ resBodyOverride = asBuffer(replacedBody);
899
+ }
900
+ }
901
+
902
+ if (resBodyOverride) {
903
+ // In the above cases, the overriding data is assumed to always be in decoded form,
904
+ // so we re-encode the body to match the resulting content-encoding header:
905
+ resBodyOverride = await encodeBodyBuffer(
906
+ resBodyOverride,
907
+ serverHeaders
908
+ );
909
+
910
+ serverHeaders['content-length'] = getContentLengthAfterModification(
911
+ resBodyOverride,
912
+ serverRes.headers,
913
+ (updateHeaders && updateHeaders['content-length'] !== undefined)
914
+ ? serverHeaders // Iff you replaced the content length
915
+ : replaceHeaders,
916
+ method === 'HEAD' // HEAD responses are allowed mismatched content-length
917
+ );
918
+ }
919
+
920
+ serverRawHeaders = objectHeadersToRaw(serverHeaders);
921
+ } else if (this.beforeResponse) {
922
+ let modifiedRes: CallbackResponseResult | void;
923
+
924
+ originalBody = await streamToBuffer(serverRes);
925
+ let serverHeaders = rawHeadersToObject(serverRawHeaders);
926
+
927
+ modifiedRes = await this.beforeResponse({
928
+ id: clientReq.id,
929
+ statusCode: serverStatusCode,
930
+ statusMessage: serverRes.statusMessage,
931
+ headers: serverHeaders,
932
+ rawHeaders: _.cloneDeep(serverRawHeaders),
933
+ body: buildBodyReader(originalBody, serverHeaders)
934
+ });
935
+
936
+ if (modifiedRes === 'close') {
937
+ (clientReq as any).socket.end();
938
+ throw new AbortError('Connection closed intentionally by rule');
939
+ } else if (modifiedRes === 'reset') {
940
+ requireSocketResetSupport();
941
+ resetOrDestroy(clientReq);
942
+ throw new AbortError('Connection reset intentionally by rule');
943
+ }
944
+
945
+ validateCustomHeaders(serverHeaders, modifiedRes?.headers);
946
+
947
+ serverStatusCode = modifiedRes?.statusCode ||
948
+ modifiedRes?.status ||
949
+ serverStatusCode;
950
+ serverStatusMessage = modifiedRes?.statusMessage ||
951
+ serverStatusMessage;
952
+
953
+ serverHeaders = modifiedRes?.headers || serverHeaders;
954
+
955
+ resBodyOverride = await buildOverriddenBody(modifiedRes, serverHeaders);
956
+
957
+ if (resBodyOverride) {
958
+ serverHeaders['content-length'] = getContentLengthAfterModification(
959
+ resBodyOverride,
960
+ serverRes.headers,
961
+ modifiedRes?.headers,
962
+ method === 'HEAD' // HEAD responses are allowed mismatched content-length
963
+ );
964
+ }
965
+
966
+ serverRawHeaders = objectHeadersToRaw(serverHeaders);
967
+ }
968
+
969
+ writeHead(
970
+ clientRes,
971
+ serverStatusCode,
972
+ serverStatusMessage,
973
+ serverRawHeaders
974
+ .filter(([key, value]) => {
975
+ if (key === ':status') return false;
976
+ if (!validateHeader(key, value)) {
977
+ console.warn(`Not forwarding invalid header: "${key}: ${value}"`);
978
+ // Nothing else we can do in this case regardless - setHeaders will
979
+ // throw within Node if we try to set this value.
980
+ return false;
981
+ }
982
+ return true;
983
+ })
984
+ );
985
+
986
+ if (resBodyOverride) {
987
+ // Return the override data to the client:
988
+ clientRes.end(resBodyOverride);
989
+
990
+ // Dump the real response data, in case that body wasn't read yet:
991
+ serverRes.resume();
992
+ resolve();
993
+ } else if (originalBody) {
994
+ // If the original body was read, and not overridden, then send it
995
+ // onward directly:
996
+ clientRes.end(originalBody);
997
+ resolve();
998
+ } else {
999
+ // Otherwise the body hasn't been read - stream it live:
1000
+ serverRes.pipe(clientRes);
1001
+ serverRes.once('end', resolve);
1002
+ }
1003
+
1004
+ if (options.emitEventCallback) {
1005
+ if (!!resBodyOverride) {
1006
+ (originalBody
1007
+ ? Promise.resolve(originalBody)
1008
+ : streamToBuffer(serverRes)
1009
+ ).then((upstreamBody) => {
1010
+ options.emitEventCallback!('passthrough-response-body', {
1011
+ overridden: true,
1012
+ rawBody: upstreamBody
1013
+ });
1014
+ });
1015
+ } else {
1016
+ options.emitEventCallback('passthrough-response-body', {
1017
+ overridden: false
1018
+ // We don't bother buffering & re-sending the body if
1019
+ // it's the same as the one being sent to the client.
1020
+ });
1021
+ }
1022
+ }
1023
+ })().catch(reject));
1024
+
1025
+ serverReq.once('socket', (socket: net.Socket) => {
1026
+ // This event can fire multiple times for keep-alive sockets, which are used to
1027
+ // make multiple requests. If/when that happens, we don't need more event listeners.
1028
+ if (this.outgoingSockets.has(socket)) return;
1029
+
1030
+ // Add this port to our list of active ports, once it's connected (before then it has no port)
1031
+ if (socket.connecting) {
1032
+ socket.once('connect', () => {
1033
+ this.outgoingSockets.add(socket)
1034
+ });
1035
+ } else if (socket.localPort !== undefined) {
1036
+ this.outgoingSockets.add(socket);
1037
+ }
1038
+
1039
+ // Remove this port from our list of active ports when it's closed
1040
+ // This is called for both clean closes & errors.
1041
+ socket.once('close', () => this.outgoingSockets.delete(socket));
1042
+ });
1043
+
1044
+ // Forward any request trailers received from the client:
1045
+ const forwardTrailers = () => {
1046
+ if (clientReq.rawTrailers?.length) {
1047
+ if (serverReq.addTrailers) {
1048
+ serverReq.addTrailers(clientReq.rawTrailers);
1049
+ } else {
1050
+ // See https://github.com/szmarczak/http2-wrapper/issues/103
1051
+ console.warn('Not forwarding request trailers - not yet supported for HTTP/2');
1052
+ }
1053
+ }
1054
+ };
1055
+ // This has to be above the pipe setup below, or we end the stream before adding the
1056
+ // trailers, and they're lost.
1057
+ if (clientReqBody.readableEnded) {
1058
+ forwardTrailers();
1059
+ } else {
1060
+ clientReqBody.once('end', forwardTrailers);
1061
+ }
1062
+
1063
+ // Forward the request body to the upstream server:
1064
+ if (reqBodyOverride) {
1065
+ clientReqBody.resume(); // Dump any remaining real request body
1066
+
1067
+ if (reqBodyOverride.length > 0) serverReq.end(reqBodyOverride);
1068
+ else serverReq.end(); // http2-wrapper fails given an empty buffer for methods that aren't allowed a body
1069
+ } else {
1070
+ // asStream includes all content, including the body before this call
1071
+ clientReqBody.pipe(serverReq);
1072
+ clientReqBody.on('error', () => serverReq.abort());
1073
+ }
1074
+
1075
+ // If the downstream connection aborts, before the response has been completed,
1076
+ // we also abort the upstream connection. Important to avoid unnecessary connections,
1077
+ // and to correctly proxy client connection behaviour to the upstream server.
1078
+ function abortUpstream() {
1079
+ serverReq.abort();
1080
+ }
1081
+
1082
+ // Handle the case where the downstream connection is prematurely closed before
1083
+ // fully sending the request or receiving the response.
1084
+ clientReq.on('aborted', abortUpstream);
1085
+ clientRes.on('close', abortUpstream);
1086
+
1087
+ // Disable the upstream request abort handlers once the response has been received.
1088
+ clientRes.once('finish', () => {
1089
+ clientReq.off('aborted', abortUpstream);
1090
+ clientRes.off('close', abortUpstream);
1091
+ });
1092
+
1093
+ serverReq.on('error', (e: any) => {
1094
+ e.causedByUpstreamError = true;
1095
+ reject(e);
1096
+ });
1097
+
1098
+ // We always start upstream connections *immediately*. This might be less efficient, but it
1099
+ // ensures that we're accurately mirroring downstream, which has indeed already connected.
1100
+ serverReq.flushHeaders();
1101
+
1102
+ // For similar reasons, we don't want any buffering on outgoing data at all if possible:
1103
+ serverReq.setNoDelay(true);
1104
+
1105
+ // Fire rule events, to allow in-depth debugging of upstream traffic & modifications,
1106
+ // so anybody interested can see _exactly_ what we're sending upstream here:
1107
+ if (options.emitEventCallback) {
1108
+ options.emitEventCallback('passthrough-request-head', {
1109
+ method,
1110
+ protocol: protocol!.replace(/:$/, ''),
1111
+ hostname,
1112
+ port,
1113
+ path,
1114
+ rawHeaders
1115
+ });
1116
+
1117
+ if (!!reqBodyOverride) {
1118
+ options.emitEventCallback('passthrough-request-body', {
1119
+ overridden: true,
1120
+ rawBody: reqBodyOverride
1121
+ });
1122
+ } else {
1123
+ options.emitEventCallback!('passthrough-request-body', {
1124
+ overridden: false
1125
+ });
1126
+ }
1127
+ }
1128
+ })().catch(reject)
1129
+ ).catch((e: ErrorLike) => {
1130
+ if (!e.code && e.stack?.split('\n')[1]?.includes('node:internal/tls/secure-context')) {
1131
+ // OpenSSL can throw all sorts of weird & wonderful errors here, and rarely exposes a
1132
+ // useful error code from them. To handle that, we try to detect the most common cases,
1133
+ // notable including the useless but common 'unsupported' error that covers all
1134
+ // OpenSSL-unsupported (e.g. legacy) configurations.
1135
+
1136
+ let tlsErrorTag: string;
1137
+ if (e.message === 'unsupported') {
1138
+ e.code = 'ERR_TLS_CONTEXT_UNSUPPORTED';
1139
+ tlsErrorTag = 'context-unsupported';
1140
+ e.message = 'Unsupported TLS configuration';
1141
+ } else {
1142
+ e.code = 'ERR_TLS_CONTEXT_UNKNOWN';
1143
+ tlsErrorTag = 'context-unknown';
1144
+ e.message = `TLS context error: ${e.message}`;
1145
+ }
1146
+
1147
+ clientRes.tags.push(`passthrough-tls-error:${tlsErrorTag}`);
1148
+ }
1149
+
1150
+ // All errors anywhere above (thrown or from explicit reject()) should end up here.
1151
+
1152
+ // We tag the response with the error code, for debugging from events:
1153
+ clientRes.tags.push('passthrough-error:' + e.code);
1154
+
1155
+ // Tag responses, so programmatic examination can react to this
1156
+ // event, without having to parse response data or similar.
1157
+ const tlsAlertMatch = /SSL alert number (\d+)/.exec(e.message ?? '');
1158
+ if (tlsAlertMatch) {
1159
+ clientRes.tags.push('passthrough-tls-error:ssl-alert-' + tlsAlertMatch[1]);
1160
+ }
1161
+
1162
+ if ((e as any).causedByUpstreamError && !serverReq?.aborted) {
1163
+ if (e.code === 'ECONNRESET' || e.code === 'ECONNREFUSED' || this.simulateConnectionErrors) {
1164
+ // The upstream socket failed: forcibly break the downstream stream to match. This could
1165
+ // happen due to a reset, TLS or DNS failures, or anything - but critically it's a
1166
+ // connection-level issue, so we try to create connection issues downstream.
1167
+ resetOrDestroy(clientReq);
1168
+
1169
+ // Aggregate errors can be thrown if multiple (IPv4/6) addresses were tested. Note that
1170
+ // AggregateError only exists in Node 15+. If that happens, we need to combine errors:
1171
+ const errorMessage = typeof AggregateError !== 'undefined' && (e instanceof AggregateError)
1172
+ ? e.errors.map(e => e.message).join(', ')
1173
+ : (e.message ?? e.code ?? e);
1174
+
1175
+ throw new AbortError(`Upstream connection error: ${errorMessage}`, e.code);
1176
+ } else {
1177
+ e.statusCode = 502;
1178
+ e.statusMessage = 'Error communicating with upstream server';
1179
+ throw e;
1180
+ }
1181
+ } else {
1182
+ throw e;
1183
+ }
1184
+ });
1185
+ }
1186
+
1187
+ /**
1188
+ * @internal
1189
+ */
1190
+ static deserialize(
1191
+ data: SerializedPassThroughData,
1192
+ channel: ClientServerChannel,
1193
+ ruleParams: RuleParameters
1194
+ ): PassThroughHandler {
1195
+ let beforeRequest: ((req: CompletedRequest) => MaybePromise<CallbackRequestResult | void>) | undefined;
1196
+ if (data.hasBeforeRequestCallback) {
1197
+ beforeRequest = async (req: CompletedRequest) => {
1198
+ const result = withDeserializedCallbackBuffers<CallbackRequestResult>(
1199
+ await channel.request<
1200
+ BeforePassthroughRequestRequest,
1201
+ WithSerializedCallbackBuffers<CallbackRequestResult>
1202
+ >('beforeRequest', {
1203
+ args: [withSerializedBodyReader(req)]
1204
+ })
1205
+ );
1206
+
1207
+ if (result.response && typeof result.response !== 'string') {
1208
+ result.response = withDeserializedCallbackBuffers(
1209
+ result.response as WithSerializedCallbackBuffers<CallbackResponseMessageResult>
1210
+ );
1211
+ }
1212
+
1213
+ return result;
1214
+ };
1215
+ }
1216
+
1217
+ let beforeResponse: ((res: PassThroughResponse) => MaybePromise<CallbackResponseResult | void>) | undefined;
1218
+ if (data.hasBeforeResponseCallback) {
1219
+ beforeResponse = async (res: PassThroughResponse) => {
1220
+ const callbackResult = await channel.request<
1221
+ BeforePassthroughResponseRequest,
1222
+ | WithSerializedCallbackBuffers<CallbackResponseMessageResult>
1223
+ | 'close'
1224
+ | 'reset'
1225
+ | undefined
1226
+ >('beforeResponse', {
1227
+ args: [withSerializedBodyReader(res)]
1228
+ })
1229
+
1230
+ if (callbackResult && typeof callbackResult !== 'string') {
1231
+ return withDeserializedCallbackBuffers(callbackResult);
1232
+ } else {
1233
+ return callbackResult;
1234
+ }
1235
+ };
1236
+ }
1237
+
1238
+ return new PassThroughHandler({
1239
+ beforeRequest,
1240
+ beforeResponse,
1241
+ proxyConfig: deserializeProxyConfig(data.proxyConfig, channel, ruleParams),
1242
+ transformRequest: data.transformRequest ? {
1243
+ ...data.transformRequest,
1244
+ ...(data.transformRequest?.replaceBody !== undefined ? {
1245
+ replaceBody: deserializeBuffer(data.transformRequest.replaceBody)
1246
+ } : {}),
1247
+ ...(data.transformRequest?.updateHeaders !== undefined ? {
1248
+ updateHeaders: mapOmitToUndefined(JSON.parse(data.transformRequest.updateHeaders))
1249
+ } : {}),
1250
+ ...(data.transformRequest?.updateJsonBody !== undefined ? {
1251
+ updateJsonBody: mapOmitToUndefined(JSON.parse(data.transformRequest.updateJsonBody))
1252
+ } : {}),
1253
+ ...(data.transformRequest?.matchReplaceBody !== undefined ? {
1254
+ matchReplaceBody: data.transformRequest.matchReplaceBody.map(([match, result]) =>
1255
+ [
1256
+ !_.isString(match) && 'regexSource' in match
1257
+ ? new RegExp(match.regexSource, match.flags)
1258
+ : match,
1259
+ result
1260
+ ]
1261
+ )
1262
+ } : {})
1263
+ } as RequestTransform : undefined,
1264
+ transformResponse: data.transformResponse ? {
1265
+ ...data.transformResponse,
1266
+ ...(data.transformResponse?.replaceBody !== undefined ? {
1267
+ replaceBody: deserializeBuffer(data.transformResponse.replaceBody)
1268
+ } : {}),
1269
+ ...(data.transformResponse?.updateHeaders !== undefined ? {
1270
+ updateHeaders: mapOmitToUndefined(JSON.parse(data.transformResponse.updateHeaders))
1271
+ } : {}),
1272
+ ...(data.transformResponse?.updateJsonBody !== undefined ? {
1273
+ updateJsonBody: mapOmitToUndefined(JSON.parse(data.transformResponse.updateJsonBody))
1274
+ } : {}),
1275
+ ...(data.transformResponse?.matchReplaceBody !== undefined ? {
1276
+ matchReplaceBody: data.transformResponse.matchReplaceBody.map(([match, result]) =>
1277
+ [
1278
+ !_.isString(match) && 'regexSource' in match
1279
+ ? new RegExp(match.regexSource, match.flags)
1280
+ : match,
1281
+ result
1282
+ ]
1283
+ )
1284
+ } : {})
1285
+ } as ResponseTransform : undefined,
1286
+ // Backward compat for old clients:
1287
+ ...data.forwardToLocation ? {
1288
+ forwarding: { targetHost: data.forwardToLocation }
1289
+ } : {},
1290
+ forwarding: data.forwarding,
1291
+ lookupOptions: data.lookupOptions,
1292
+ simulateConnectionErrors: !!data.simulateConnectionErrors,
1293
+ ignoreHostHttpsErrors: data.ignoreHostCertificateErrors,
1294
+ additionalTrustedCAs: data.extraCACertificates,
1295
+ clientCertificateHostMap: _.mapValues(data.clientCertificateHostMap,
1296
+ ({ pfx, passphrase }) => ({ pfx: deserializeBuffer(pfx), passphrase })
1297
+ ),
1298
+ });
1299
+ }
1300
+ }
1301
+
1302
+ export class CloseConnectionHandler extends CloseConnectionHandlerDefinition {
1303
+ async handle(request: OngoingRequest) {
1304
+ const socket: net.Socket = (<any> request).socket;
1305
+ socket.end();
1306
+ throw new AbortError('Connection closed intentionally by rule');
1307
+ }
1308
+ }
1309
+
1310
+ export class ResetConnectionHandler extends ResetConnectionHandlerDefinition {
1311
+ constructor() {
1312
+ super();
1313
+ requireSocketResetSupport();
1314
+ }
1315
+
1316
+ async handle(request: OngoingRequest) {
1317
+ requireSocketResetSupport();
1318
+ resetOrDestroy(request);
1319
+ throw new AbortError('Connection reset intentionally by rule');
1320
+ }
1321
+
1322
+ /**
1323
+ * @internal
1324
+ */
1325
+ static deserialize() {
1326
+ requireSocketResetSupport();
1327
+ return new ResetConnectionHandler();
1328
+ }
1329
+ }
1330
+
1331
+ export class TimeoutHandler extends TimeoutHandlerDefinition {
1332
+ async handle() {
1333
+ // Do nothing, leaving the socket open but never sending a response.
1334
+ return new Promise<void>(() => {});
1335
+ }
1336
+ }
1337
+
1338
+ export class JsonRpcResponseHandler extends JsonRpcResponseHandlerDefinition {
1339
+ async handle(request: OngoingRequest, response: OngoingResponse) {
1340
+ const data: any = await request.body.asJson()
1341
+ .catch(() => {}); // Handle parsing errors with the check below
1342
+
1343
+ if (!data || data.jsonrpc !== '2.0' || !('id' in data)) { // N.B. id can be null
1344
+ throw new Error("Can't send a JSON-RPC response to an invalid JSON-RPC request");
1345
+ }
1346
+
1347
+ response.writeHead(200, {
1348
+ 'content-type': 'application/json'
1349
+ });
1350
+
1351
+ response.end(JSON.stringify({
1352
+ jsonrpc: '2.0',
1353
+ id: data.id,
1354
+ ...this.result
1355
+ }));
1356
+ }
1357
+ }
1358
+
1359
+ export const HandlerLookup: typeof HandlerDefinitionLookup = {
1360
+ 'simple': SimpleHandler,
1361
+ 'callback': CallbackHandler,
1362
+ 'stream': StreamHandler,
1363
+ 'file': FileHandler,
1364
+ 'passthrough': PassThroughHandler,
1365
+ 'close-connection': CloseConnectionHandler,
1366
+ 'reset-connection': ResetConnectionHandler,
1367
+ 'timeout': TimeoutHandler,
1368
+ 'json-rpc-response': JsonRpcResponseHandler
1369
+ }