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,424 @@
1
+ import _ = require('lodash');
2
+ import now = require('performance-now');
3
+ import net = require('net');
4
+ import tls = require('tls');
5
+ import http = require('http');
6
+ import http2 = require('http2');
7
+ import * as streams from 'stream';
8
+
9
+ import * as semver from 'semver';
10
+ import { makeDestroyable, DestroyableServer } from 'destroyable-server';
11
+ import httpolyglot = require('@httptoolkit/httpolyglot');
12
+ import {
13
+ calculateJa3FromFingerprintData,
14
+ NonTlsError,
15
+ readTlsClientHello
16
+ } from 'read-tls-client-hello';
17
+ import { URLPattern } from "urlpattern-polyfill";
18
+
19
+ import { TlsHandshakeFailure } from '../types';
20
+ import { getCA } from '../util/tls';
21
+ import { delay } from '../util/util';
22
+ import { shouldPassThrough } from '../util/server-utils';
23
+ import {
24
+ getParentSocket,
25
+ buildSocketTimingInfo,
26
+ buildSocketEventData
27
+ } from '../util/socket-util';
28
+ import { MockttpHttpsOptions } from '../mockttp';
29
+
30
+ // Hardcore monkey-patching: force TLSSocket to link servername & remoteAddress to
31
+ // sockets as soon as they're available, without waiting for the handshake to fully
32
+ // complete, so we can easily access them if the handshake fails.
33
+ const originalSocketInit = (<any>tls.TLSSocket.prototype)._init;
34
+ (<any>tls.TLSSocket.prototype)._init = function () {
35
+ originalSocketInit.apply(this, arguments);
36
+
37
+ const tlsSocket: tls.TLSSocket = this;
38
+ const { _handle } = tlsSocket;
39
+ if (!_handle) return;
40
+
41
+ const loadSNI = _handle.oncertcb;
42
+ _handle.oncertcb = function (info: any) {
43
+ tlsSocket.servername = info.servername;
44
+ tlsSocket.initialRemoteAddress = tlsSocket.remoteAddress || // Normal case
45
+ tlsSocket._parent?.remoteAddress || // For early failing sockets
46
+ tlsSocket._handle?._parentWrap?.stream?.remoteAddress; // For HTTP/2 CONNECT
47
+ tlsSocket.initialRemotePort = tlsSocket.remotePort ||
48
+ tlsSocket._parent?.remotePort ||
49
+ tlsSocket._handle?._parentWrap?.stream?.remotePort;
50
+
51
+ return loadSNI?.apply(this, arguments as any);
52
+ };
53
+ };
54
+
55
+ export type ComboServerOptions = {
56
+ debug: boolean,
57
+ https: MockttpHttpsOptions | undefined,
58
+ http2: true | false | 'fallback'
59
+ };
60
+
61
+ // Takes an established TLS socket, calls the error listener if it's silently closed
62
+ function ifTlsDropped(socket: tls.TLSSocket, errorCallback: () => void) {
63
+ new Promise((resolve, reject) => {
64
+ // If you send data, you trust the TLS connection
65
+ socket.once('data', resolve);
66
+
67
+ // If you silently close it very quicky, you probably don't trust us
68
+ socket.once('error', reject);
69
+ socket.once('close', reject);
70
+ socket.once('end', reject);
71
+
72
+ // Some clients open later-unused TLS connections for connection pools, preconnect, etc.
73
+ // Even if these are shut later on, that doesn't mean they're are rejected connections.
74
+ // To differentiate the two cases, we consider connections OK after waiting 10x longer
75
+ // than the initial TLS handshake for an unhappy disconnection.
76
+ const timing = socket.__timingInfo;
77
+ const tlsSetupDuration = timing
78
+ ? timing.tlsConnectedTimestamp! - (timing.tunnelSetupTimestamp! || timing.initialSocketTimestamp)
79
+ : 0;
80
+ const maxTlsRejectionTime = !Object.is(tlsSetupDuration, NaN)
81
+ ? Math.max(tlsSetupDuration * 10, 100) // Ensure a sensible minimum
82
+ : 2000;
83
+
84
+ delay(maxTlsRejectionTime).then(resolve);
85
+ })
86
+ .then(() => {
87
+ // Mark the socket as having completed TLS setup - this ensures that future
88
+ // errors fire as client errors, not TLS setup errors.
89
+ socket.tlsSetupCompleted = true;
90
+ })
91
+ .catch(() => {
92
+ // If TLS setup was confirmed in any way, we know we don't have a TLS error.
93
+ if (socket.tlsSetupCompleted) return;
94
+
95
+ // To get here, the socket must have connected & done the TLS handshake, but then
96
+ // closed/ended without ever sending any data. We can fairly confidently assume
97
+ // in that case that it's rejected our certificate.
98
+ errorCallback();
99
+ });
100
+ }
101
+
102
+ function getCauseFromError(error: Error & { code?: string }) {
103
+ const cause = (
104
+ /alert certificate/.test(error.message) ||
105
+ /alert bad certificate/.test(error.message) ||
106
+ error.code === 'ERR_SSL_SSLV3_ALERT_BAD_CERTIFICATE' ||
107
+ /alert unknown ca/.test(error.message)
108
+ )
109
+ // The client explicitly told us it doesn't like the certificate
110
+ ? 'cert-rejected'
111
+ : /no shared cipher/.test(error.message)
112
+ // The client refused to negotiate a cipher. Probably means it didn't like the
113
+ // cert so refused to continue, but it could genuinely not have a shared cipher.
114
+ ? 'no-shared-cipher'
115
+ : (/ECONNRESET/.test(error.message) || error.code === 'ECONNRESET')
116
+ // The client sent no TLS alert, it just hard RST'd the connection
117
+ ? 'reset'
118
+ : error.code === 'ERR_TLS_HANDSHAKE_TIMEOUT'
119
+ ? 'handshake-timeout'
120
+ : 'unknown'; // Something else.
121
+
122
+ if (cause === 'unknown') console.log('Unknown TLS error:', error);
123
+
124
+ return cause;
125
+ }
126
+
127
+ function buildTlsError(
128
+ socket: tls.TLSSocket,
129
+ cause: TlsHandshakeFailure['failureCause']
130
+ ): TlsHandshakeFailure {
131
+ const eventData = buildSocketEventData(socket) as TlsHandshakeFailure;
132
+
133
+ eventData.failureCause = cause;
134
+ eventData.timingEvents.failureTimestamp = now();
135
+
136
+ return eventData;
137
+ }
138
+
139
+ // The low-level server that handles all the sockets & TLS. The server will correctly call the
140
+ // given handler for both HTTP & HTTPS direct connections, or connections when used as an
141
+ // either HTTP or HTTPS proxy, all on the same port.
142
+ export async function createComboServer(
143
+ options: ComboServerOptions,
144
+ requestListener: (req: http.IncomingMessage, res: http.ServerResponse) => void,
145
+ tlsClientErrorListener: (socket: tls.TLSSocket, req: TlsHandshakeFailure) => void,
146
+ tlsPassthroughListener: (socket: net.Socket, address: string, port?: number) => void
147
+ ): Promise<DestroyableServer<net.Server>> {
148
+ let server: net.Server;
149
+ if (!options.https) {
150
+ server = httpolyglot.createServer(requestListener);
151
+ } else {
152
+ const ca = await getCA(options.https);
153
+ const defaultCert = ca.generateCertificate(options.https.defaultDomain ?? 'localhost');
154
+
155
+ const serverProtocolPreferences = options.http2 === true
156
+ ? ['h2', 'http/1.1', 'http 1.1'] // 'http 1.1' is non-standard, but used by https-proxy-agent
157
+ : options.http2 === 'fallback'
158
+ ? ['http/1.1', 'http 1.1', 'h2']
159
+ // options.http2 === false:
160
+ : ['http/1.1', 'http 1.1'];
161
+
162
+ const ALPNOption: tls.TlsOptions = semver.satisfies(process.version, '>=20.4.0')
163
+ ? {
164
+ // In modern Node (20+), ALPNProtocols will reject unknown protocols. To allow those (so we can
165
+ // at least read the request, and hopefully handle HTTP-like cases - not uncommon) we use the new
166
+ // ALPNCallback feature instead, which lets us dynamically accept unrecognized protocols:
167
+ ALPNCallback: ({ protocols: clientProtocols }) => {
168
+ const preferredProtocol = serverProtocolPreferences.find(p => clientProtocols.includes(p));
169
+
170
+ // Wherever possible, we tell the client to use our preferred protocol
171
+ if (preferredProtocol) return preferredProtocol;
172
+
173
+ // If the client only offers protocols that we don't understand, shrug and accept:
174
+ else return clientProtocols[1];
175
+ }
176
+ } : {
177
+ // In Node versions without ALPNCallback, we just set preferences directly:
178
+ ALPNProtocols: serverProtocolPreferences
179
+ }
180
+
181
+ const tlsServer = tls.createServer({
182
+ key: defaultCert.key,
183
+ cert: defaultCert.cert,
184
+ ca: [defaultCert.ca],
185
+ ...ALPNOption,
186
+ SNICallback: (domain: string, cb: Function) => {
187
+ if (options.debug) console.log(`Generating certificate for ${domain}`);
188
+
189
+ try {
190
+ const generatedCert = ca.generateCertificate(domain);
191
+ cb(null, tls.createSecureContext({
192
+ key: generatedCert.key,
193
+ cert: generatedCert.cert,
194
+ ca: generatedCert.ca
195
+ }));
196
+ } catch (e) {
197
+ console.error('Cert generation error', e);
198
+ cb(e);
199
+ }
200
+ }
201
+ });
202
+
203
+ analyzeAndMaybePassThroughTls(
204
+ tlsServer,
205
+ options.https.tlsPassthrough,
206
+ options.https.tlsInterceptOnly,
207
+ tlsPassthroughListener
208
+ );
209
+
210
+ server = httpolyglot.createServer(tlsServer, requestListener);
211
+ }
212
+
213
+ // In Node v20, this option was added, rejecting all requests with no host header. While that's good, in
214
+ // our case, we want to handle the garbage requests too, so we disable it:
215
+ (server as any)._httpServer.requireHostHeader = false;
216
+
217
+ server.on('connection', (socket: net.Socket | http2.ServerHttp2Stream) => {
218
+ socket.__timingInfo = socket.__timingInfo || buildSocketTimingInfo();
219
+
220
+ // All sockets are initially marked as using unencrypted upstream connections.
221
+ // If TLS is used, this is upgraded to 'true' by secureConnection below.
222
+ socket.__lastHopEncrypted = false;
223
+
224
+ // For actual sockets, set NODELAY to avoid any buffering whilst streaming. This is
225
+ // off by default in Node HTTP, but likely to be enabled soon & is default in curl.
226
+ if ('setNoDelay' in socket) socket.setNoDelay(true);
227
+ });
228
+
229
+ server.on('secureConnection', (socket: tls.TLSSocket) => {
230
+ const parentSocket = getParentSocket(socket);
231
+ if (parentSocket) {
232
+ // Sometimes wrapper TLS sockets created by the HTTP/2 server don't include the
233
+ // underlying socket details, so it's better to make sure we copy them up.
234
+ copyAddressDetails(parentSocket, socket);
235
+ copyTimingDetails(parentSocket, socket);
236
+ // With TLS metadata, we only propagate directly from parent sockets, not through
237
+ // CONNECT etc - we only want it if the final hop is TLS, previous values don't matter.
238
+ socket.__tlsMetadata ??= parentSocket.__tlsMetadata;
239
+ } else if (!socket.__timingInfo) {
240
+ socket.__timingInfo = buildSocketTimingInfo();
241
+ }
242
+
243
+ socket.__timingInfo!.tlsConnectedTimestamp = now();
244
+
245
+ socket.__lastHopEncrypted = true;
246
+ ifTlsDropped(socket, () => {
247
+ tlsClientErrorListener(socket, buildTlsError(socket, 'closed'));
248
+ });
249
+ });
250
+
251
+ // Mark HTTP/2 sockets as set up once we receive a first settings frame. This always
252
+ // happens immediately after the connection preface, as long as the connection is OK.
253
+ server!.on('session', (session) => {
254
+ session.once('remoteSettings', () => {
255
+ session.socket.tlsSetupCompleted = true;
256
+ });
257
+ });
258
+
259
+ server.on('tlsClientError', (error: Error, socket: tls.TLSSocket) => {
260
+ tlsClientErrorListener(socket, buildTlsError(socket, getCauseFromError(error)));
261
+ });
262
+
263
+ // If the server receives a HTTP/HTTPS CONNECT request, Pretend to tunnel, then just re-handle:
264
+ server.addListener('connect', function (
265
+ req: http.IncomingMessage | http2.Http2ServerRequest,
266
+ resOrSocket: net.Socket | http2.Http2ServerResponse
267
+ ) {
268
+ if (resOrSocket instanceof net.Socket) {
269
+ handleH1Connect(req as http.IncomingMessage, resOrSocket);
270
+ } else {
271
+ handleH2Connect(req as http2.Http2ServerRequest, resOrSocket);
272
+ }
273
+ });
274
+
275
+ function handleH1Connect(req: http.IncomingMessage, socket: net.Socket) {
276
+ // Clients may disconnect at this point (for all sorts of reasons), but here
277
+ // nothing else is listening, so we need to catch errors on the socket:
278
+ socket.once('error', (e) => {
279
+ if (options.debug) {
280
+ console.log('Error on client socket', e);
281
+ }
282
+ });
283
+
284
+ const connectUrl = req.url || req.headers['host'];
285
+ if (!connectUrl) {
286
+ // If we can't work out where to go, send an error.
287
+ socket.write('HTTP/' + req.httpVersion + ' 400 Bad Request\r\n\r\n', 'utf-8');
288
+ return;
289
+ }
290
+
291
+ if (options.debug) console.log(`Proxying HTTP/1 CONNECT to ${connectUrl}`);
292
+
293
+ socket.write('HTTP/' + req.httpVersion + ' 200 OK\r\n\r\n', 'utf-8', () => {
294
+ socket.__timingInfo!.tunnelSetupTimestamp = now();
295
+ socket.__lastHopConnectAddress = connectUrl;
296
+ server.emit('connection', socket);
297
+ });
298
+ }
299
+
300
+ function handleH2Connect(req: http2.Http2ServerRequest, res: http2.Http2ServerResponse) {
301
+ const connectUrl = req.headers[':authority'];
302
+
303
+ if (!connectUrl) {
304
+ // If we can't work out where to go, send an error.
305
+ res.writeHead(400, {});
306
+ res.end();
307
+ return;
308
+ }
309
+
310
+ if (options.debug) console.log(`Proxying HTTP/2 CONNECT to ${connectUrl}`);
311
+
312
+ // Send a 200 OK response, and start the tunnel:
313
+ res.writeHead(200, {});
314
+ copyAddressDetails(res.socket, res.stream);
315
+ copyTimingDetails(res.socket, res.stream);
316
+ res.stream.__lastHopConnectAddress = connectUrl;
317
+
318
+ // When layering HTTP/2 on JS streams, we have to make sure the JS stream won't autoclose
319
+ // when the other side does, because the upper HTTP/2 layers want to handle shutdown, so
320
+ // they end up trying to write a GOAWAY at the same time as the lower stream shuts down,
321
+ // and we get assertion errors in Node v16.7+.
322
+ if (res.socket.constructor.name.includes('JSStreamSocket')) {
323
+ res.socket.allowHalfOpen = true;
324
+ }
325
+
326
+ server.emit('connection', res.stream);
327
+ }
328
+
329
+ return makeDestroyable(server);
330
+ }
331
+
332
+ type SocketIsh<MinProps extends keyof net.Socket> =
333
+ streams.Duplex & Partial<Pick<net.Socket, MinProps>>;
334
+
335
+ const SOCKET_ADDRESS_METADATA_FIELDS = [
336
+ 'localAddress',
337
+ 'localPort',
338
+ 'remoteAddress',
339
+ 'remotePort',
340
+ '__lastHopConnectAddress'
341
+ ] as const;
342
+
343
+ // Update the target socket(-ish) with the address details from the source socket,
344
+ // iff the target has no details of its own.
345
+ function copyAddressDetails(
346
+ source: SocketIsh<typeof SOCKET_ADDRESS_METADATA_FIELDS[number]>,
347
+ target: SocketIsh<typeof SOCKET_ADDRESS_METADATA_FIELDS[number]>
348
+ ) {
349
+ Object.defineProperties(target, _.zipObject(
350
+ SOCKET_ADDRESS_METADATA_FIELDS,
351
+ _.range(SOCKET_ADDRESS_METADATA_FIELDS.length).map(() => ({ writable: true }))
352
+ ) as PropertyDescriptorMap);
353
+
354
+ SOCKET_ADDRESS_METADATA_FIELDS.forEach((fieldName) => {
355
+ if (target[fieldName] === undefined) {
356
+ (target as any)[fieldName] = source[fieldName];
357
+ }
358
+ });
359
+ }
360
+
361
+ function copyTimingDetails<T extends SocketIsh<'__timingInfo'>>(
362
+ source: SocketIsh<'__timingInfo'>,
363
+ target: T
364
+ ): asserts target is T & { __timingInfo: Required<net.Socket>['__timingInfo'] } {
365
+ if (!target.__timingInfo) {
366
+ // Clone timing info, don't copy it - child sockets get their own independent timing stats
367
+ target.__timingInfo = Object.assign({}, source.__timingInfo);
368
+ }
369
+ }
370
+
371
+ /**
372
+ * Takes tls passthrough configuration (may be empty) and reconfigures a given TLS server so that all
373
+ * client hellos are parsed, matching requests are passed to the given passthrough listener (without
374
+ * continuing setup) and client hello metadata is attached to all sockets.
375
+ */
376
+ function analyzeAndMaybePassThroughTls(
377
+ server: tls.Server,
378
+ passthroughList: Required<MockttpHttpsOptions>['tlsPassthrough'] | undefined,
379
+ interceptOnlyList: Required<MockttpHttpsOptions>['tlsInterceptOnly'] | undefined,
380
+ passthroughListener: (socket: net.Socket, address: string, port?: number) => void
381
+ ) {
382
+ if (passthroughList && interceptOnlyList){
383
+ throw new Error('Cannot use both tlsPassthrough and tlsInterceptOnly options at the same time.');
384
+ }
385
+ const passThroughPatterns = passthroughList?.map(({ hostname }) => new URLPattern(`https://${hostname}`)) ?? [];
386
+ const interceptOnlyPatterns = interceptOnlyList?.map(({ hostname }) => new URLPattern(`https://${hostname}`));
387
+
388
+ const tlsConnectionListener = server.listeners('connection')[0] as (socket: net.Socket) => {};
389
+ server.removeListener('connection', tlsConnectionListener);
390
+ server.on('connection', async (socket: net.Socket) => {
391
+ try {
392
+ const helloData = await readTlsClientHello(socket);
393
+
394
+ const [connectHostname, connectPort] = socket.__lastHopConnectAddress?.split(':') ?? [];
395
+ const sniHostname = helloData.serverName;
396
+
397
+ socket.__tlsMetadata = {
398
+ sniHostname,
399
+ connectHostname,
400
+ connectPort,
401
+ clientAlpn: helloData.alpnProtocols,
402
+ ja3Fingerprint: calculateJa3FromFingerprintData(helloData.fingerprintData)
403
+ };
404
+
405
+ if (shouldPassThrough(connectHostname, passThroughPatterns, interceptOnlyPatterns)) {
406
+ const upstreamPort = connectPort ? parseInt(connectPort, 10) : undefined;
407
+ passthroughListener(socket, connectHostname, upstreamPort);
408
+ return; // Do not continue with TLS
409
+ } else if (shouldPassThrough(sniHostname, passThroughPatterns, interceptOnlyPatterns)) {
410
+ passthroughListener(socket, sniHostname!); // Can't guess the port - not included in SNI
411
+ return; // Do not continue with TLS
412
+ }
413
+ } catch (e) {
414
+ if (!(e instanceof NonTlsError)) { // Don't even warn for non-TLS traffic
415
+ console.warn(`TLS client hello data not available for TLS connection from ${
416
+ socket.remoteAddress ?? 'unknown address'
417
+ }: ${(e as Error).message ?? e}`);
418
+ }
419
+ }
420
+
421
+ // Didn't match a passthrough hostname - continue with TLS setup
422
+ tlsConnectionListener.call(server, socket);
423
+ });
424
+ }
@@ -0,0 +1,44 @@
1
+ import * as util from 'util';
2
+
3
+ import type { CompletedRequest, MockedEndpoint } from '../types';
4
+ import type { RequestRule } from '../rules/requests/request-rule';
5
+ import type { WebSocketRule } from '../rules/websockets/websocket-rule';
6
+
7
+ export class ServerMockedEndpoint implements MockedEndpoint {
8
+
9
+ constructor(private rule: RequestRule | WebSocketRule) {
10
+ this.getSeenRequests.bind(this);
11
+ };
12
+
13
+ get id() {
14
+ return this.rule.id;
15
+ }
16
+
17
+ getSeenRequests(): Promise<CompletedRequest[]> {
18
+ // Wait for all completed running requests to have all their details available
19
+ return Promise.all<CompletedRequest>(this.rule.requests);
20
+ }
21
+
22
+ async isPending(): Promise<boolean> {
23
+ // We don't actually need to wait for rule.requests to complete, because
24
+ // completion rules right now only check requestCount, and that is always
25
+ // updated synchronously when handling starts.
26
+
27
+ const ruleCompletion = this.rule.isComplete();
28
+ if (ruleCompletion !== null) {
29
+ // If the rule has a specific completion value, use it
30
+ return !ruleCompletion;
31
+ } else {
32
+ // If not, then it's default "at least one" completion:
33
+ return this.rule.requestCount === 0;
34
+ }
35
+ }
36
+
37
+ [util.inspect.custom]() {
38
+ return "Mocked endpoint: " + this.toString();
39
+ }
40
+
41
+ toString(withoutExactCompletion = false) {
42
+ return this.rule.explain(withoutExactCompletion);
43
+ }
44
+ }