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,193 @@
1
+ import * as _ from 'lodash';
2
+ import now = require("performance-now");
3
+ import * as os from 'os';
4
+ import * as net from 'net';
5
+ import * as tls from 'tls';
6
+ import * as http2 from 'http2';
7
+
8
+ import { isNode } from './util';
9
+ import { OngoingRequest, TlsConnectionEvent } from '../types';
10
+
11
+ // Test if a local port for a given interface (IPv4/6) is currently in use
12
+ export async function isLocalPortActive(interfaceIp: '::1' | '127.0.0.1', port: number) {
13
+ if (interfaceIp === '::1' && !isLocalIPv6Available) return false;
14
+
15
+ return new Promise((resolve) => {
16
+ const server = net.createServer();
17
+ server.listen({
18
+ host: interfaceIp,
19
+ port,
20
+ ipv6Only: interfaceIp === '::1'
21
+ });
22
+ server.once('listening', () => {
23
+ resolve(false);
24
+ server.close(() => {});
25
+ });
26
+ server.once('error', (e) => {
27
+ resolve(true);
28
+ });
29
+ });
30
+ }
31
+
32
+ // This file imported in browsers etc as it's used in handlers, but none of these methods are used
33
+ // directly. It is useful though to guard sections that immediately perform actions:
34
+ export const isLocalIPv6Available = isNode
35
+ ? _.some(os.networkInterfaces(),
36
+ (addresses) => _.some(addresses, a => a.address === '::1')
37
+ )
38
+ : true;
39
+
40
+ // We need to normalize ips for comparison, because the same ip may be reported as ::ffff:127.0.0.1
41
+ // and 127.0.0.1 on the two sides of the connection, for the same ip.
42
+ const normalizeIp = (ip: string | null | undefined) =>
43
+ (ip && ip.startsWith('::ffff:'))
44
+ ? ip.slice('::ffff:'.length)
45
+ : ip;
46
+
47
+ export const isLocalhostAddress = (host: string | null | undefined) =>
48
+ !!host && ( // Null/undef are something else weird, but not localhost
49
+ host === 'localhost' || // Most common
50
+ host.endsWith('.localhost') ||
51
+ host === '::1' || // IPv6
52
+ normalizeIp(host)!.match(/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) // 127.0.0.0/8 range
53
+ );
54
+
55
+
56
+ // Check whether an incoming socket is the other end of one of our outgoing sockets:
57
+ export const isSocketLoop = (outgoingSockets: net.Socket[] | Set<net.Socket>, incomingSocket: net.Socket) =>
58
+ // We effectively just compare the address & port: if they match, we've almost certainly got a loop.
59
+
60
+ // I don't think it's generally possible to see the same ip on different interfaces from one process (you need
61
+ // ip-netns network namespaces), but if it is, then there's a tiny chance of false positives here. If we have ip X,
62
+ // and on another interface somebody else has ip X, and they send a request with the same incoming port as an
63
+ // outgoing request we have on the other interface, we'll assume it's a loop. Extremely unlikely imo.
64
+
65
+ _.some([...outgoingSockets], (outgoingSocket) => {
66
+ if (!outgoingSocket.localAddress || !outgoingSocket.localPort) {
67
+ // It's possible for sockets in outgoingSockets to be closed, in which case these properties
68
+ // will be undefined. If so, we know they're not relevant to loops, so skip entirely.
69
+ return false;
70
+ } else {
71
+ return normalizeIp(outgoingSocket.localAddress) === normalizeIp(incomingSocket.remoteAddress) &&
72
+ outgoingSocket.localPort === incomingSocket.remotePort;
73
+ }
74
+ });
75
+
76
+ export function getParentSocket(socket: net.Socket) {
77
+ return socket._parent || // TLS wrapper
78
+ socket.stream || // SocketWrapper
79
+ (socket as any)._handle?._parentWrap?.stream; // HTTP/2 CONNECT'd TLS wrapper
80
+ }
81
+
82
+ const isSocketResetSupported = isNode
83
+ ? !!net.Socket.prototype.resetAndDestroy
84
+ : false; // Avoid errors in browsers
85
+ export const requireSocketResetSupport = () => {
86
+ if (!isSocketResetSupported) {
87
+ throw new Error(
88
+ 'Connection reset is only supported in Node v16.17+, v18.3.0+, or later'
89
+ );
90
+ }
91
+ };
92
+
93
+ const isHttp2Stream = (maybeStream: any): maybeStream is http2.Http2ServerRequest =>
94
+ 'httpVersion' in maybeStream &&
95
+ maybeStream.httpVersion?.startsWith('2');
96
+
97
+ /**
98
+ * Reset the socket where possible, or at least destroy it where that's not possible.
99
+ *
100
+ * This has a few cases for different layers of socket & tunneling, designed to
101
+ * simulate a real connection reset as closely as possible. That means, in general,
102
+ * we unwrap the connection as far as possible whilst still only affecting a single
103
+ * request.
104
+ *
105
+ * In practice, we unwrap HTTP/1 & TLS back as far as we can, until we hit either an
106
+ * HTTP/2 stream or a raw TCP connection. We then either send a RST_FRAME or a TCP RST
107
+ * to kill that connection.
108
+ */
109
+ export function resetOrDestroy(requestOrSocket:
110
+ | net.Socket
111
+ | OngoingRequest & { socket?: net.Socket }
112
+ | http2.Http2ServerRequest
113
+ ) {
114
+ let socket: net.Socket | http2.Http2Stream =
115
+ (isHttp2Stream(requestOrSocket) && requestOrSocket.stream)
116
+ ? requestOrSocket.stream
117
+ : ('socket' in requestOrSocket && requestOrSocket.socket)
118
+ ? requestOrSocket.socket
119
+ : requestOrSocket as net.Socket;
120
+
121
+ while (socket instanceof tls.TLSSocket) {
122
+ const parent = getParentSocket(socket);
123
+ if (!parent) break; // Not clear why, but it seems in some cases we run out of parents here
124
+ socket = parent;
125
+ }
126
+
127
+ if ('rstCode' in socket) {
128
+ // It's an HTTP/2 stream instance - let's kill it here.
129
+
130
+ // If it's the innermost stream, i.e. this is the stream of the request we're
131
+ // resetting, then we want to send an internal error. If it's a tunneling
132
+ // stream, then we want to send a CONNECT error:
133
+ const isOuterSocket = socket === (requestOrSocket as any).stream;
134
+
135
+ const errorCode = isOuterSocket
136
+ ? http2.constants.NGHTTP2_INTERNAL_ERROR
137
+ : http2.constants.NGHTTP2_CONNECT_ERROR;
138
+
139
+ const h2Stream = socket as http2.ServerHttp2Stream;
140
+ h2Stream.close(errorCode);
141
+ } else {
142
+ // Must be a net.Socket then, so we let's reset it for real:
143
+ if (isSocketResetSupported) {
144
+ try {
145
+ socket.resetAndDestroy!();
146
+ } catch (error) {
147
+ // This could fail in funky ways if the socket is not just the right kind
148
+ // of socket. We should still fail in that case, but it's useful to log
149
+ // some extra data first beforehand, so we can fix this if it ever happens:
150
+ console.warn(`Failed to reset on socket of type ${
151
+ socket.constructor.name
152
+ } with parent of type ${getParentSocket(socket as any)?.constructor.name}`);
153
+ throw error;
154
+ }
155
+ } else {
156
+ socket.destroy();
157
+ }
158
+ }
159
+ };
160
+
161
+ export function buildSocketEventData(socket: net.Socket & Partial<tls.TLSSocket>): TlsConnectionEvent {
162
+ const timingInfo = socket.__timingInfo ||
163
+ socket._parent?.__timingInfo ||
164
+ buildSocketTimingInfo();
165
+
166
+ // Attached in passThroughMatchingTls TLS sniffing logic in http-combo-server:
167
+ const tlsMetadata = socket.__tlsMetadata ||
168
+ socket._parent?.__tlsMetadata ||
169
+ {};
170
+
171
+ return {
172
+ hostname: socket.servername,
173
+ // These only work because of oncertcb monkeypatch in http-combo-server:
174
+ remoteIpAddress: socket.remoteAddress || // Normal case
175
+ socket._parent?.remoteAddress || // Pre-certCB error, e.g. timeout
176
+ socket.initialRemoteAddress!, // Recorded by certCB monkeypatch
177
+ remotePort: socket.remotePort ||
178
+ socket._parent?.remotePort ||
179
+ socket.initialRemotePort!,
180
+ tags: [],
181
+ timingEvents: {
182
+ startTime: timingInfo.initialSocket,
183
+ connectTimestamp: timingInfo.initialSocketTimestamp,
184
+ tunnelTimestamp: timingInfo.tunnelSetupTimestamp,
185
+ handshakeTimestamp: timingInfo.tlsConnectedTimestamp
186
+ },
187
+ tlsMetadata
188
+ };
189
+ }
190
+
191
+ export function buildSocketTimingInfo(): Required<net.Socket>['__timingInfo'] {
192
+ return { initialSocket: Date.now(), initialSocketTimestamp: now() };
193
+ }
@@ -0,0 +1,348 @@
1
+ import * as _ from 'lodash';
2
+ import * as fs from 'fs/promises';
3
+ import { v4 as uuid } from "uuid";
4
+ import * as forge from 'node-forge';
5
+
6
+ const { asn1, pki, md, util } = forge;
7
+
8
+ export type CAOptions = (CertDataOptions | CertPathOptions);
9
+
10
+ export interface CertDataOptions extends BaseCAOptions {
11
+ key: string;
12
+ cert: string;
13
+ };
14
+
15
+ export interface CertPathOptions extends BaseCAOptions {
16
+ keyPath: string;
17
+ certPath: string;
18
+ }
19
+
20
+ export interface BaseCAOptions {
21
+ /**
22
+ * Minimum key length when generating certificates. Defaults to 2048.
23
+ */
24
+ keyLength?: number;
25
+
26
+ /**
27
+ * The countryName that will be used in the certificate for incoming TLS
28
+ * connections.
29
+ */
30
+ countryName?: string;
31
+
32
+ /**
33
+ * The localityName that will be used in the certificate for incoming TLS
34
+ * connections.
35
+ */
36
+ localityName?: string;
37
+
38
+ /**
39
+ * The organizationName that will be used in the certificate for incoming TLS
40
+ * connections.
41
+ */
42
+ organizationName?: string;
43
+ }
44
+
45
+ export type PEM = string | string[] | Buffer | Buffer[];
46
+
47
+ export type GeneratedCertificate = {
48
+ key: string,
49
+ cert: string,
50
+ ca: string
51
+ };
52
+
53
+ /**
54
+ * Generate a CA certificate for mocking HTTPS.
55
+ *
56
+ * Returns a promise, for an object with key and cert properties,
57
+ * containing the generated private key and certificate in PEM format.
58
+ *
59
+ * These can be saved to disk, and their paths passed
60
+ * as HTTPS options to a Mockttp server.
61
+ */
62
+ export async function generateCACertificate(options: {
63
+ commonName?: string,
64
+ organizationName?: string,
65
+ countryName?: string,
66
+ bits?: number,
67
+ nameConstraints?: {
68
+ permitted?: string[]
69
+ }
70
+ } = {}) {
71
+ options = _.defaults({}, options, {
72
+ commonName: 'Mockttp Testing CA - DO NOT TRUST - TESTING ONLY',
73
+ organizationName: 'Mockttp',
74
+ countryName: 'XX', // ISO-3166-1 alpha-2 'unknown country' code
75
+ bits: 2048,
76
+ });
77
+
78
+ const keyPair = await new Promise<forge.pki.rsa.KeyPair>((resolve, reject) => {
79
+ pki.rsa.generateKeyPair({ bits: options.bits }, (error, keyPair) => {
80
+ if (error) reject(error);
81
+ else resolve(keyPair);
82
+ });
83
+ });
84
+
85
+ const cert = pki.createCertificate();
86
+ cert.publicKey = keyPair.publicKey;
87
+ cert.serialNumber = generateSerialNumber();
88
+
89
+ cert.validity.notBefore = new Date();
90
+ // Make it valid for the last 24h - helps in cases where clocks slightly disagree
91
+ cert.validity.notBefore.setDate(cert.validity.notBefore.getDate() - 1);
92
+
93
+ cert.validity.notAfter = new Date();
94
+ // Valid for the next year by default.
95
+ cert.validity.notAfter.setFullYear(cert.validity.notAfter.getFullYear() + 1);
96
+
97
+ cert.setSubject([
98
+ // All of these are required for a fully valid CA cert that will be accepted when imported anywhere:
99
+ { name: 'commonName', value: options.commonName },
100
+ { name: 'countryName', value: options.countryName },
101
+ { name: 'organizationName', value: options.organizationName }
102
+ ]);
103
+
104
+ const extensions: any[] = [
105
+ { name: 'basicConstraints', cA: true, critical: true },
106
+ { name: 'keyUsage', keyCertSign: true, digitalSignature: true, nonRepudiation: true, cRLSign: true, critical: true },
107
+ { name: 'subjectKeyIdentifier' },
108
+ ];
109
+ const permittedDomains = options.nameConstraints?.permitted || [];
110
+ if(permittedDomains.length > 0) {
111
+ extensions.push({
112
+ critical: true,
113
+ id: '2.5.29.30',
114
+ name: 'nameConstraints',
115
+ value: generateNameConstraints({
116
+ permitted: permittedDomains,
117
+ }),
118
+ })
119
+ }
120
+ cert.setExtensions(extensions);
121
+
122
+ // Self-issued too
123
+ cert.setIssuer(cert.subject.attributes);
124
+
125
+ // Self-sign the certificate - we're the root
126
+ cert.sign(keyPair.privateKey, md.sha256.create());
127
+
128
+ return {
129
+ key: pki.privateKeyToPem(keyPair.privateKey),
130
+ cert: pki.certificateToPem(cert)
131
+ };
132
+ }
133
+
134
+
135
+ type GenerateNameConstraintsInput = {
136
+ /**
137
+ * Array of permitted domains
138
+ */
139
+ permitted?: string[];
140
+ };
141
+
142
+ /**
143
+ * Generate name constraints in conformance with
144
+ * [RFC 5280 § 4.2.1.10](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.10)
145
+ */
146
+ function generateNameConstraints(
147
+ input: GenerateNameConstraintsInput
148
+ ): forge.asn1.Asn1 {
149
+ const domainsToSequence = (ips: string[]) =>
150
+ ips.map((domain) => {
151
+ return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
152
+ asn1.create(
153
+ asn1.Class.CONTEXT_SPECIFIC,
154
+ 2,
155
+ false,
156
+ util.encodeUtf8(domain)
157
+ ),
158
+ ]);
159
+ });
160
+
161
+ const permittedAndExcluded: forge.asn1.Asn1[] = [];
162
+
163
+ if (input.permitted && input.permitted.length > 0) {
164
+ permittedAndExcluded.push(
165
+ asn1.create(
166
+ asn1.Class.CONTEXT_SPECIFIC,
167
+ 0,
168
+ true,
169
+ domainsToSequence(input.permitted)
170
+ )
171
+ );
172
+ }
173
+
174
+ return asn1.create(
175
+ asn1.Class.UNIVERSAL,
176
+ asn1.Type.SEQUENCE,
177
+ true,
178
+ permittedAndExcluded
179
+ );
180
+ }
181
+
182
+ export function generateSPKIFingerprint(certPem: PEM) {
183
+ let cert = pki.certificateFromPem(certPem.toString('utf8'));
184
+ return util.encode64(
185
+ pki.getPublicKeyFingerprint(cert.publicKey, {
186
+ type: 'SubjectPublicKeyInfo',
187
+ md: md.sha256.create(),
188
+ encoding: 'binary'
189
+ })
190
+ );
191
+ }
192
+
193
+ // Generates a unique serial number for a certificate as a hex string:
194
+ function generateSerialNumber() {
195
+ return 'A' + uuid().replace(/-/g, '');
196
+ // We add a leading 'A' to ensure it's always positive (not 'F') and always
197
+ // valid (e.g. leading 000 is bad padding, and would be unparseable).
198
+ }
199
+
200
+ export async function getCA(options: CAOptions): Promise<CA> {
201
+ let certOptions: CertDataOptions;
202
+ if ('key' in options && 'cert' in options) {
203
+ certOptions = options;
204
+ }
205
+ else if ('keyPath' in options && 'certPath' in options) {
206
+ certOptions = await Promise.all([
207
+ fs.readFile(options.keyPath, 'utf8'),
208
+ fs.readFile(options.certPath, 'utf8')
209
+ ]).then(([ keyContents, certContents ]) => ({
210
+ ..._.omit(options, ['keyPath', 'certPath']),
211
+ key: keyContents,
212
+ cert: certContents
213
+ }));
214
+ }
215
+ else {
216
+ throw new Error('Unrecognized https options: you need to provide either a keyPath & certPath, or a key & cert.')
217
+ }
218
+
219
+ return new CA(certOptions);
220
+ }
221
+
222
+ // We share a single keypair across all certificates in this process, and
223
+ // instantiate it once when the first CA is created, because it can be
224
+ // expensive (depending on the key length).
225
+ // This would be a terrible idea for a real server, but for a mock server
226
+ // it's ok - if anybody can steal this, they can steal the CA cert anyway.
227
+ let KEY_PAIR: {
228
+ publicKey: forge.pki.rsa.PublicKey,
229
+ privateKey: forge.pki.rsa.PrivateKey,
230
+ length: number
231
+ } | undefined;
232
+
233
+ export class CA {
234
+ private caCert: forge.pki.Certificate;
235
+ private caKey: forge.pki.PrivateKey;
236
+ private options: CertDataOptions;
237
+
238
+ private certCache: { [domain: string]: GeneratedCertificate };
239
+
240
+ constructor(options: CertDataOptions) {
241
+ this.caKey = pki.privateKeyFromPem(options.key.toString());
242
+ this.caCert = pki.certificateFromPem(options.cert.toString());
243
+ this.certCache = {};
244
+ this.options = options ?? {};
245
+
246
+ const keyLength = options.keyLength || 2048;
247
+
248
+ if (!KEY_PAIR || KEY_PAIR.length < keyLength) {
249
+ // If we have no key, or not a long enough one, generate one.
250
+ KEY_PAIR = Object.assign(
251
+ pki.rsa.generateKeyPair(keyLength),
252
+ { length: keyLength }
253
+ );
254
+ }
255
+ }
256
+
257
+ generateCertificate(domain: string): GeneratedCertificate {
258
+ // TODO: Expire domains from the cache? Based on their actual expiry?
259
+ if (this.certCache[domain]) return this.certCache[domain];
260
+
261
+ if (domain.includes('_')) {
262
+ // TLS certificates cannot cover domains with underscores, bizarrely. More info:
263
+ // https://www.digicert.com/kb/ssl-support/underscores-not-allowed-in-fqdns.htm
264
+ // To fix this, we use wildcards instead. This is only possible for one level of
265
+ // certificate, and only for subdomains, so our options are a little limited, but
266
+ // this should be very rare (because it's not supported elsewhere either).
267
+ const [ , ...otherParts] = domain.split('.');
268
+ if (
269
+ otherParts.length <= 1 || // *.com is never valid
270
+ otherParts.some(p => p.includes('_'))
271
+ ) {
272
+ throw new Error(`Cannot generate certificate for domain due to underscores: ${domain}`);
273
+ }
274
+
275
+ // Replace the first part with a wildcard to solve the problem:
276
+ domain = `*.${otherParts.join('.')}`;
277
+ }
278
+
279
+ let cert = pki.createCertificate();
280
+
281
+ cert.publicKey = KEY_PAIR!.publicKey;
282
+ cert.serialNumber = generateSerialNumber();
283
+
284
+ cert.validity.notBefore = new Date();
285
+ // Make it valid for the last 24h - helps in cases where clocks slightly disagree.
286
+ cert.validity.notBefore.setDate(cert.validity.notBefore.getDate() - 1);
287
+
288
+ cert.validity.notAfter = new Date();
289
+ // Valid for the next year by default. TODO: Shorten (and expire the cache) automatically.
290
+ cert.validity.notAfter.setFullYear(cert.validity.notAfter.getFullYear() + 1);
291
+
292
+ cert.setSubject([
293
+ ...(domain[0] === '*'
294
+ ? [] // We skip the CN (deprecated, rarely used) for wildcards, since they can't be used here.
295
+ : [{ name: 'commonName', value: domain }]
296
+ ),
297
+ { name: 'countryName', value: this.options?.countryName ?? 'XX' }, // ISO-3166-1 alpha-2 'unknown country' code
298
+ { name: 'localityName', value: this.options?.localityName ?? 'Unknown' },
299
+ { name: 'organizationName', value: this.options?.organizationName ?? 'Mockttp Cert - DO NOT TRUST' }
300
+ ]);
301
+ cert.setIssuer(this.caCert.subject.attributes);
302
+
303
+ const policyList = forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.SEQUENCE, true, [
304
+ forge.asn1.create(forge.asn1.Class.UNIVERSAL, forge.asn1.Type.SEQUENCE, true, [
305
+ forge.asn1.create(
306
+ forge.asn1.Class.UNIVERSAL,
307
+ forge.asn1.Type.OID,
308
+ false,
309
+ forge.asn1.oidToDer('2.5.29.32.0').getBytes() // Mark all as Domain Verified
310
+ )
311
+ ])
312
+ ]);
313
+
314
+ cert.setExtensions([
315
+ { name: 'basicConstraints', cA: false, critical: true },
316
+ { name: 'keyUsage', digitalSignature: true, keyEncipherment: true, critical: true },
317
+ { name: 'extKeyUsage', serverAuth: true, clientAuth: true },
318
+ {
319
+ name: 'subjectAltName',
320
+ altNames: [{
321
+ type: 2,
322
+ value: domain
323
+ }]
324
+ },
325
+ { name: 'certificatePolicies', value: policyList },
326
+ { name: 'subjectKeyIdentifier' },
327
+ {
328
+ name: 'authorityKeyIdentifier',
329
+ // We have to calculate this ourselves due to
330
+ // https://github.com/digitalbazaar/forge/issues/462
331
+ keyIdentifier: (
332
+ this.caCert as any // generateSubjectKeyIdentifier is missing from node-forge types
333
+ ).generateSubjectKeyIdentifier().getBytes()
334
+ }
335
+ ]);
336
+
337
+ cert.sign(this.caKey, md.sha256.create());
338
+
339
+ const generatedCertificate = {
340
+ key: pki.privateKeyToPem(KEY_PAIR!.privateKey),
341
+ cert: pki.certificateToPem(cert),
342
+ ca: pki.certificateToPem(this.caCert)
343
+ };
344
+
345
+ this.certCache[domain] = generatedCertificate;
346
+ return generatedCertificate;
347
+ }
348
+ }
@@ -0,0 +1,15 @@
1
+ export type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;
2
+
3
+ // Turns T, making all props K required
4
+ export type RequireProps<T, K extends keyof T> =
5
+ Omit<T, K> & Required<Pick<T, K>>;
6
+
7
+ export type MaybePromise<T> = T | Promise<T>;
8
+
9
+ type SubsetKeyOf<T, Ks extends keyof T = keyof T> = Ks;
10
+ export type Replace<T, KV extends { [K in SubsetKeyOf<T, any>]: unknown }> =
11
+ Omit<T, keyof KV> & { [K in keyof KV]: KV[K] };
12
+
13
+ export type Mutable<T> = {
14
+ -readonly [K in keyof T]: T[K]
15
+ }
@@ -0,0 +1,113 @@
1
+ import * as url from 'url';
2
+ import * as _ from 'lodash';
3
+
4
+ import { nthIndexOf } from './util';
5
+
6
+ // Is this URL fully qualified?
7
+ // Note that this supports only HTTP - no websockets or anything else.
8
+ export const isAbsoluteUrl = (url: string) =>
9
+ url.toLowerCase().startsWith('http://') ||
10
+ url.toLowerCase().startsWith('https://');
11
+
12
+ export const isRelativeUrl = (url: string) =>
13
+ url.startsWith('/');
14
+
15
+ export const isAbsoluteProtocollessUrl = (url: string) =>
16
+ !isAbsoluteUrl(url) && !isRelativeUrl(url);
17
+
18
+ export const getUrlWithoutProtocol = (url: string): string => {
19
+ return url.split('://', 2).slice(-1).join('');
20
+ }
21
+
22
+ export const getPathFromAbsoluteUrl = (url: string) => {
23
+ const pathIndex = nthIndexOf(url, '/', 3);
24
+ if (pathIndex !== -1) {
25
+ return url.slice(pathIndex);
26
+ } else {
27
+ return '';
28
+ }
29
+ }
30
+
31
+ export const getEffectivePort = (url: { protocol: string | null, port: string | null }) => {
32
+ if (url.port) {
33
+ return parseInt(url.port, 10);
34
+ } else if (url.protocol === 'https:' || url.protocol === 'wss:') {
35
+ return 443;
36
+ } else {
37
+ return 80;
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Normalizes URLs to the form used when matching them.
43
+ *
44
+ * This accepts URLs in all three formats: relative, absolute, and protocolless-absolute,
45
+ * and returns them in the same format but normalized.
46
+ */
47
+ export const normalizeUrl: (url: string) => string =
48
+ _.memoize(
49
+ (urlInput: string): string => {
50
+ let parsedUrl: Partial<url.UrlWithStringQuery> | undefined;
51
+
52
+ let isProtocolless = false;
53
+
54
+ try {
55
+ // Strip the query and anything following it
56
+ const queryIndex = urlInput.indexOf('?');
57
+ if (queryIndex !== -1) {
58
+ urlInput = urlInput.slice(0, queryIndex);
59
+ }
60
+
61
+ if (isAbsoluteProtocollessUrl(urlInput)) {
62
+ parsedUrl = url.parse('http://' + urlInput);
63
+ isProtocolless = true;
64
+ } else {
65
+ parsedUrl = url.parse(urlInput);
66
+ }
67
+
68
+ // Trim out lots of the bits we don't like:
69
+ delete parsedUrl.host;
70
+ delete parsedUrl.query;
71
+ delete parsedUrl.search;
72
+ delete parsedUrl.hash;
73
+
74
+ if (parsedUrl.pathname) {
75
+ parsedUrl.pathname = parsedUrl.pathname.replace(
76
+ /\%[A-Fa-z0-9]{2}/g,
77
+ (encoded) => encoded.toUpperCase()
78
+ ).replace(
79
+ /[^\u0000-\u007F]+/g,
80
+ (unicodeChar) => encodeURIComponent(unicodeChar)
81
+ );
82
+ }
83
+
84
+ if (parsedUrl.hostname && parsedUrl.hostname.endsWith('.')) {
85
+ parsedUrl.hostname = parsedUrl.hostname.slice(0, -1);
86
+ }
87
+
88
+ if (
89
+ (parsedUrl.protocol === 'https:' && parsedUrl.port === '443') ||
90
+ (parsedUrl.protocol === 'http:' && parsedUrl.port === '80')
91
+ ) {
92
+ delete parsedUrl.port;
93
+ }
94
+ } catch (e) {
95
+ console.log(`Failed to normalize URL ${urlInput}`);
96
+ console.log(e);
97
+
98
+ if (!parsedUrl) return urlInput; // Totally unparseble: use as-is
99
+ // If we've successfully parsed it, we format what we managed
100
+ // and leave it at that:
101
+ }
102
+
103
+ let normalizedUrl = url.format(parsedUrl);
104
+
105
+ // If the URL came in with no protocol, it should leave with
106
+ // no protocol (protocol added temporarily above to allow parsing)
107
+ if (isProtocolless) {
108
+ normalizedUrl = normalizedUrl.slice('http://'.length);
109
+ }
110
+
111
+ return normalizedUrl;
112
+ }
113
+ );