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