@mbtest/mountebank 2.9.2-beta.9050

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.
Files changed (207) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +94 -0
  3. package/bin/mb +136 -0
  4. package/package.json +71 -0
  5. package/releases.json +52 -0
  6. package/src/cli/api.js +112 -0
  7. package/src/cli/cli.js +420 -0
  8. package/src/controllers/configController.js +64 -0
  9. package/src/controllers/feedController.js +115 -0
  10. package/src/controllers/homeController.js +58 -0
  11. package/src/controllers/imposterController.js +328 -0
  12. package/src/controllers/impostersController.js +215 -0
  13. package/src/controllers/logsController.js +52 -0
  14. package/src/models/behaviors.js +553 -0
  15. package/src/models/behaviorsValidator.js +186 -0
  16. package/src/models/compatibility.js +133 -0
  17. package/src/models/dryRunValidator.js +261 -0
  18. package/src/models/filesystemBackedImpostersRepository.js +908 -0
  19. package/src/models/http/baseHttpServer.js +207 -0
  20. package/src/models/http/headersMap.js +87 -0
  21. package/src/models/http/httpProxy.js +230 -0
  22. package/src/models/http/httpRequest.js +82 -0
  23. package/src/models/http/httpServer.js +18 -0
  24. package/src/models/http/index.js +18 -0
  25. package/src/models/https/cert/mb-cert.pem +20 -0
  26. package/src/models/https/cert/mb-csr.pem +16 -0
  27. package/src/models/https/cert/mb-key.pem +27 -0
  28. package/src/models/https/httpsServer.js +42 -0
  29. package/src/models/https/index.js +18 -0
  30. package/src/models/imposter.js +243 -0
  31. package/src/models/imposterPrinter.js +120 -0
  32. package/src/models/impostersRepository.js +49 -0
  33. package/src/models/inMemoryImpostersRepository.js +418 -0
  34. package/src/models/jsonpath.js +44 -0
  35. package/src/models/mbConnection.js +107 -0
  36. package/src/models/predicates.js +438 -0
  37. package/src/models/protocols.js +242 -0
  38. package/src/models/responseResolver.js +398 -0
  39. package/src/models/smtp/index.js +16 -0
  40. package/src/models/smtp/smtpRequest.js +60 -0
  41. package/src/models/smtp/smtpServer.js +109 -0
  42. package/src/models/tcp/index.js +18 -0
  43. package/src/models/tcp/tcpProxy.js +110 -0
  44. package/src/models/tcp/tcpRequest.js +23 -0
  45. package/src/models/tcp/tcpServer.js +156 -0
  46. package/src/models/tcp/tcpValidator.js +19 -0
  47. package/src/models/xpath.js +95 -0
  48. package/src/mountebank.js +245 -0
  49. package/src/public/images/arrow_down.png +0 -0
  50. package/src/public/images/arrow_up.png +0 -0
  51. package/src/public/images/book.jpg +0 -0
  52. package/src/public/images/dataflow.png +0 -0
  53. package/src/public/images/favicon.ico +0 -0
  54. package/src/public/images/forkme_right_orange_ff7600.png +0 -0
  55. package/src/public/images/mountebank.png +0 -0
  56. package/src/public/images/overview.gif +0 -0
  57. package/src/public/images/quote.png +0 -0
  58. package/src/public/images/tw-logo.png +0 -0
  59. package/src/public/scripts/jquery/jquery-3.6.1.min.js +2 -0
  60. package/src/public/scripts/urlHashHandler.js +31 -0
  61. package/src/public/stylesheets/application.css +424 -0
  62. package/src/public/stylesheets/ie.css +14 -0
  63. package/src/public/stylesheets/imposters.css +121 -0
  64. package/src/public/stylesheets/jqueryui/1.10.4/themes/smoothness/jquery-ui.css +1178 -0
  65. package/src/util/combinators.js +68 -0
  66. package/src/util/date.js +51 -0
  67. package/src/util/errors.js +55 -0
  68. package/src/util/helpers.js +131 -0
  69. package/src/util/inherit.js +28 -0
  70. package/src/util/ip.js +54 -0
  71. package/src/util/logger.js +83 -0
  72. package/src/util/middleware.js +256 -0
  73. package/src/util/scopedLogger.js +47 -0
  74. package/src/views/_footer.ejs +20 -0
  75. package/src/views/_header.ejs +113 -0
  76. package/src/views/_imposter.ejs +8 -0
  77. package/src/views/config.ejs +71 -0
  78. package/src/views/docs/api/behaviors/copy.ejs +427 -0
  79. package/src/views/docs/api/behaviors/decorate.ejs +182 -0
  80. package/src/views/docs/api/behaviors/lookup.ejs +220 -0
  81. package/src/views/docs/api/behaviors/shellTransform.ejs +153 -0
  82. package/src/views/docs/api/behaviors/wait.ejs +121 -0
  83. package/src/views/docs/api/behaviors.ejs +141 -0
  84. package/src/views/docs/api/contracts/addStub-description.ejs +10 -0
  85. package/src/views/docs/api/contracts/addStub.ejs +10 -0
  86. package/src/views/docs/api/contracts/config-description.ejs +32 -0
  87. package/src/views/docs/api/contracts/config.ejs +23 -0
  88. package/src/views/docs/api/contracts/home-description.ejs +18 -0
  89. package/src/views/docs/api/contracts/home.ejs +13 -0
  90. package/src/views/docs/api/contracts/imposter-description.ejs +439 -0
  91. package/src/views/docs/api/contracts/imposter.ejs +182 -0
  92. package/src/views/docs/api/contracts/imposters-description.ejs +13 -0
  93. package/src/views/docs/api/contracts/imposters.ejs +13 -0
  94. package/src/views/docs/api/contracts/logs-description.ejs +3 -0
  95. package/src/views/docs/api/contracts/logs.ejs +14 -0
  96. package/src/views/docs/api/contracts/stub-description.ejs +4 -0
  97. package/src/views/docs/api/contracts/stub.ejs +7 -0
  98. package/src/views/docs/api/contracts/stubs-description.ejs +4 -0
  99. package/src/views/docs/api/contracts/stubs.ejs +11 -0
  100. package/src/views/docs/api/contracts.ejs +133 -0
  101. package/src/views/docs/api/errors.ejs +64 -0
  102. package/src/views/docs/api/fault/connectionReset.ejs +31 -0
  103. package/src/views/docs/api/fault/randomDataThenClose.ejs +31 -0
  104. package/src/views/docs/api/faults.ejs +57 -0
  105. package/src/views/docs/api/injection.ejs +426 -0
  106. package/src/views/docs/api/json.ejs +205 -0
  107. package/src/views/docs/api/jsonpath.ejs +210 -0
  108. package/src/views/docs/api/mocks.ejs +130 -0
  109. package/src/views/docs/api/overview.ejs +968 -0
  110. package/src/views/docs/api/predicates/and.ejs +62 -0
  111. package/src/views/docs/api/predicates/contains.ejs +64 -0
  112. package/src/views/docs/api/predicates/deepEquals.ejs +114 -0
  113. package/src/views/docs/api/predicates/endsWith.ejs +66 -0
  114. package/src/views/docs/api/predicates/equals.ejs +125 -0
  115. package/src/views/docs/api/predicates/exists.ejs +118 -0
  116. package/src/views/docs/api/predicates/inject.ejs +67 -0
  117. package/src/views/docs/api/predicates/matches.ejs +66 -0
  118. package/src/views/docs/api/predicates/not.ejs +52 -0
  119. package/src/views/docs/api/predicates/or.ejs +79 -0
  120. package/src/views/docs/api/predicates/startsWith.ejs +62 -0
  121. package/src/views/docs/api/predicates.ejs +382 -0
  122. package/src/views/docs/api/proxies.ejs +191 -0
  123. package/src/views/docs/api/proxy/addDecorateBehavior.ejs +115 -0
  124. package/src/views/docs/api/proxy/addWaitBehavior.ejs +96 -0
  125. package/src/views/docs/api/proxy/injectHeaders.ejs +91 -0
  126. package/src/views/docs/api/proxy/predicateGenerators.ejs +600 -0
  127. package/src/views/docs/api/proxy/proxyModes.ejs +495 -0
  128. package/src/views/docs/api/stubs.ejs +391 -0
  129. package/src/views/docs/api/xpath.ejs +281 -0
  130. package/src/views/docs/cli/configFiles.ejs +133 -0
  131. package/src/views/docs/cli/customFormatters.ejs +53 -0
  132. package/src/views/docs/cli/help.ejs +6 -0
  133. package/src/views/docs/cli/replay.ejs +42 -0
  134. package/src/views/docs/cli/restart.ejs +10 -0
  135. package/src/views/docs/cli/save.ejs +68 -0
  136. package/src/views/docs/cli/start.ejs +234 -0
  137. package/src/views/docs/cli/stop.ejs +32 -0
  138. package/src/views/docs/commandLine.ejs +93 -0
  139. package/src/views/docs/communityExtensions.ejs +233 -0
  140. package/src/views/docs/gettingStarted.ejs +146 -0
  141. package/src/views/docs/mentalModel.ejs +51 -0
  142. package/src/views/docs/protocols/custom.ejs +231 -0
  143. package/src/views/docs/protocols/http.ejs +238 -0
  144. package/src/views/docs/protocols/https.ejs +246 -0
  145. package/src/views/docs/protocols/smtp.ejs +142 -0
  146. package/src/views/docs/protocols/tcp.ejs +431 -0
  147. package/src/views/docs/security.ejs +38 -0
  148. package/src/views/faqs.ejs +65 -0
  149. package/src/views/feed.ejs +33 -0
  150. package/src/views/imposter.ejs +22 -0
  151. package/src/views/imposters.ejs +33 -0
  152. package/src/views/index.ejs +89 -0
  153. package/src/views/license.ejs +30 -0
  154. package/src/views/logs.ejs +77 -0
  155. package/src/views/releases/v1.1.0.ejs +55 -0
  156. package/src/views/releases/v1.1.36.ejs +84 -0
  157. package/src/views/releases/v1.1.72.ejs +92 -0
  158. package/src/views/releases/v1.10.0.ejs +108 -0
  159. package/src/views/releases/v1.11.0.ejs +109 -0
  160. package/src/views/releases/v1.12.0.ejs +96 -0
  161. package/src/views/releases/v1.13.0.ejs +118 -0
  162. package/src/views/releases/v1.14.0.ejs +107 -0
  163. package/src/views/releases/v1.14.1.ejs +94 -0
  164. package/src/views/releases/v1.15.0.ejs +113 -0
  165. package/src/views/releases/v1.16.0.ejs +104 -0
  166. package/src/views/releases/v1.2.0.ejs +78 -0
  167. package/src/views/releases/v1.2.103.ejs +86 -0
  168. package/src/views/releases/v1.2.122.ejs +86 -0
  169. package/src/views/releases/v1.2.30.ejs +84 -0
  170. package/src/views/releases/v1.2.45.ejs +84 -0
  171. package/src/views/releases/v1.2.56.ejs +79 -0
  172. package/src/views/releases/v1.3.0.ejs +86 -0
  173. package/src/views/releases/v1.3.1.ejs +100 -0
  174. package/src/views/releases/v1.4.0.ejs +96 -0
  175. package/src/views/releases/v1.4.1.ejs +103 -0
  176. package/src/views/releases/v1.4.2.ejs +100 -0
  177. package/src/views/releases/v1.4.3.ejs +113 -0
  178. package/src/views/releases/v1.5.0.ejs +104 -0
  179. package/src/views/releases/v1.5.1.ejs +91 -0
  180. package/src/views/releases/v1.6.0.ejs +109 -0
  181. package/src/views/releases/v1.7.0.ejs +113 -0
  182. package/src/views/releases/v1.7.1.ejs +90 -0
  183. package/src/views/releases/v1.7.2.ejs +96 -0
  184. package/src/views/releases/v1.8.0.ejs +121 -0
  185. package/src/views/releases/v1.9.0.ejs +111 -0
  186. package/src/views/releases/v2.0.0.ejs +159 -0
  187. package/src/views/releases/v2.1.0.ejs +121 -0
  188. package/src/views/releases/v2.1.1.ejs +106 -0
  189. package/src/views/releases/v2.1.2.ejs +84 -0
  190. package/src/views/releases/v2.2.0.ejs +115 -0
  191. package/src/views/releases/v2.2.1.ejs +102 -0
  192. package/src/views/releases/v2.3.0.ejs +121 -0
  193. package/src/views/releases/v2.3.1.ejs +100 -0
  194. package/src/views/releases/v2.3.2.ejs +102 -0
  195. package/src/views/releases/v2.3.3.ejs +97 -0
  196. package/src/views/releases/v2.4.0.ejs +114 -0
  197. package/src/views/releases/v2.5.0.ejs +51 -0
  198. package/src/views/releases/v2.6.0.ejs +35 -0
  199. package/src/views/releases/v2.7.0.ejs +32 -0
  200. package/src/views/releases/v2.8.0.ejs +36 -0
  201. package/src/views/releases/v2.8.1.ejs +7 -0
  202. package/src/views/releases/v2.8.2.ejs +26 -0
  203. package/src/views/releases/v2.9.0.ejs +32 -0
  204. package/src/views/releases/v2.9.1.ejs +10 -0
  205. package/src/views/releases.ejs +26 -0
  206. package/src/views/sitemap.ejs +36 -0
  207. package/src/views/support.ejs +14 -0
@@ -0,0 +1,110 @@
1
+ 'use strict';
2
+
3
+ const net = require('net'),
4
+ errors = require('../../util/errors.js');
5
+
6
+ /**
7
+ * Represents the tcp proxy implementation
8
+ * @module
9
+ */
10
+
11
+ /**
12
+ * Creates the proxy
13
+ * @param {Object} logger - The logger
14
+ * @param {string} encoding - utf8 or base64, depending on if the destination expects text or binary
15
+ * @param {Function} isEndOfRequest - the function defining a logical request
16
+ * @returns {Object}
17
+ */
18
+ function create (logger, encoding, isEndOfRequest) {
19
+
20
+ if (typeof isEndOfRequest === 'undefined') {
21
+ isEndOfRequest = () => true; // defaults to a packet boundary
22
+ }
23
+
24
+ function socketName (socket) {
25
+ return `${socket.host}:${socket.port}`;
26
+ }
27
+
28
+ function format (request) {
29
+ return request.data.toString(encoding);
30
+ }
31
+
32
+ function connectionInfoFor (proxyDestination) {
33
+ const parts = new URL(proxyDestination);
34
+
35
+ if (parts.protocol !== 'tcp:') {
36
+ throw errors.InvalidProxyError('Unable to proxy to any protocol other than tcp',
37
+ { source: proxyDestination });
38
+ }
39
+ return { host: parts.hostname, port: parts.port };
40
+ }
41
+
42
+ /**
43
+ * Proxies a tcp request to the destination
44
+ * @param {string} proxyDestination - The URL to proxy to (e.g. tcp://127.0.0.1:3535)
45
+ * @param {Object} originalRequest - The tcp request to forward
46
+ * @param {Object} options - Proxy options
47
+ * @param {Boolean} options.keepalive - Whether to keep the connection alive or not
48
+ * @returns {Object} - A promise resolving to the response
49
+ */
50
+ function to (proxyDestination, originalRequest, options = {}) {
51
+ return new Promise((resolve, reject) => {
52
+ try {
53
+ const proxyName = socketName(connectionInfoFor(proxyDestination)),
54
+ log = (direction, what) => {
55
+ logger.debug('Proxy %s %s %s %s %s',
56
+ originalRequest.requestFrom, direction, JSON.stringify(format(what)), direction, proxyName);
57
+ },
58
+ buffer = Buffer.from(originalRequest.data, encoding),
59
+ socket = net.connect(connectionInfoFor(proxyDestination), () => {
60
+ socket.write(buffer);
61
+ }),
62
+ packets = [];
63
+
64
+ log('=>', originalRequest);
65
+
66
+ socket.on('end', () => {
67
+ logger.debug('%s LAST-ACK', proxyName);
68
+ });
69
+
70
+ socket.on('close', () => {
71
+ logger.debug('%s CLOSED', proxyName);
72
+ });
73
+
74
+ socket.on('data', data => {
75
+ packets.push(data);
76
+ const requestBuffer = Buffer.concat(packets);
77
+ if (isEndOfRequest(requestBuffer, logger)) {
78
+ if (!options.keepalive) {
79
+ socket.end();
80
+ }
81
+ const response = { data: requestBuffer.toString(encoding) };
82
+ log('<=', response);
83
+ resolve(response);
84
+ }
85
+ });
86
+
87
+ socket.once('error', error => {
88
+ logger.error(`Proxy ${proxyName} transmission error X=> ${JSON.stringify(error)}`);
89
+
90
+ if (error.code === 'ENOTFOUND' || error.code === 'EAI_AGAIN') {
91
+ reject(errors.InvalidProxyError(`Cannot resolve ${JSON.stringify(proxyDestination)}`));
92
+ }
93
+ else if (error.code === 'ECONNREFUSED') {
94
+ reject(errors.InvalidProxyError(`Unable to connect to ${JSON.stringify(proxyDestination)}`));
95
+ }
96
+ else {
97
+ reject(error);
98
+ }
99
+ });
100
+ }
101
+ catch (e) {
102
+ reject(e);
103
+ }
104
+ });
105
+ }
106
+
107
+ return { to };
108
+ }
109
+
110
+ module.exports = { create };
@@ -0,0 +1,23 @@
1
+ 'use strict';
2
+
3
+ const helpers = require('../../util/helpers.js');
4
+
5
+ /**
6
+ * Transforms a raw tcp request into the API-friendly representation of one
7
+ * @module
8
+ */
9
+
10
+ /**
11
+ * Transforms the raw tcp request into a mountebank tcp request
12
+ * @param {Object} request - The raw tcp request
13
+ * @returns {Object} - A promise resolving to the mountebank tcp request
14
+ */
15
+ function createFrom (request) {
16
+ return Promise.resolve({
17
+ requestFrom: helpers.socketName(request.socket),
18
+ ip: request.socket.remoteAddress,
19
+ data: request.data
20
+ });
21
+ }
22
+
23
+ module.exports = { createFrom };
@@ -0,0 +1,156 @@
1
+ 'use strict';
2
+ const net = require('net'),
3
+ helpers = require('../../util/helpers.js'),
4
+ tcpRequest = require('./tcpRequest.js'),
5
+ tcpProxy = require('./tcpProxy.js'),
6
+ tcpValidator = require('./tcpValidator.js'),
7
+ errors = require('../../util/errors.js');
8
+
9
+ /**
10
+ * Represents a tcp imposter
11
+ * @module
12
+ */
13
+
14
+ function create (options, logger, responseFn) {
15
+ const mode = options.mode ? options.mode : 'text',
16
+ encoding = mode === 'binary' ? 'base64' : 'utf8',
17
+ server = net.createServer(),
18
+ connections = {},
19
+ defaultResponse = options.defaultResponse || { data: '' };
20
+
21
+ // Used to determine logical end of request; defaults to one packet but
22
+ // changeable through injection
23
+ function isEndOfRequest (requestData) {
24
+ if (!options.endOfRequestResolver || !options.endOfRequestResolver.inject) {
25
+ return true;
26
+ }
27
+
28
+ const injected = `(${options.endOfRequestResolver.inject})(requestData, logger)`;
29
+
30
+ if (mode === 'text') {
31
+ requestData = requestData.toString('utf8');
32
+ }
33
+
34
+ try {
35
+ return eval(injected);
36
+ }
37
+ catch (error) {
38
+ logger.error(`injection X=> ${error}`);
39
+ logger.error(` full source: ${JSON.stringify(injected)}`);
40
+ logger.error(` requestData: ${JSON.stringify(requestData)}`);
41
+ return false;
42
+ }
43
+ }
44
+
45
+ // eslint-disable-next-line complexity
46
+ async function respond (payload, request, clientName, socket) {
47
+ let formattedRequestData = payload.toString(encoding);
48
+ if (formattedRequestData.length > 20) {
49
+ formattedRequestData = formattedRequestData.substring(0, 20) + '...';
50
+ }
51
+ logger.info('%s => %s', clientName, formattedRequestData);
52
+
53
+ try {
54
+ // Translate network request to JSON
55
+ const jsonRequest = await tcpRequest.createFrom(request);
56
+ logger.debug('%s => %s', clientName, JSON.stringify(jsonRequest.data.toString(encoding)));
57
+
58
+ // call mountebank with JSON request
59
+ const mbResponse = await responseFn(jsonRequest),
60
+ processedResponse = mbResponse.data || defaultResponse.data,
61
+ buffer = Buffer.isBuffer(processedResponse)
62
+ ? processedResponse
63
+ : Buffer.from(processedResponse, encoding);
64
+
65
+ if (mbResponse.blocked) {
66
+ socket.destroy();
67
+ return;
68
+ }
69
+
70
+ if (helpers.simulateFault(socket, mbResponse.fault, logger)) {
71
+ return;
72
+ }
73
+
74
+ if (buffer.length > 0) {
75
+ socket.write(buffer);
76
+ logger.debug('%s <= %s', clientName, JSON.stringify(buffer.toString(encoding)));
77
+ }
78
+ }
79
+ catch (error) {
80
+ logger.error('%s X=> %s', clientName, JSON.stringify(errors.details(error)));
81
+ socket.write(JSON.stringify({ errors: [error] }), 'utf8');
82
+ }
83
+ }
84
+
85
+ server.on('connection', socket => {
86
+ let packets = [];
87
+ const clientName = helpers.socketName(socket);
88
+
89
+ logger.debug('%s ESTABLISHED', clientName);
90
+
91
+ connections[clientName] = socket;
92
+
93
+ socket.on('error', error => {
94
+ logger.error('%s transmission error X=> %s', clientName, JSON.stringify(error));
95
+ });
96
+
97
+ socket.on('end', () => {
98
+ logger.debug('%s LAST-ACK', clientName);
99
+ });
100
+
101
+ socket.on('close', () => {
102
+ logger.debug('%s CLOSED', clientName);
103
+ delete connections[clientName];
104
+ });
105
+
106
+ socket.on('data', async data => {
107
+ packets.push(data);
108
+
109
+ const payload = Buffer.concat(packets),
110
+ request = { socket: socket, data: payload.toString(encoding) };
111
+
112
+ if (isEndOfRequest(payload)) {
113
+ packets = [];
114
+ await respond(payload, request, clientName, socket);
115
+ }
116
+ });
117
+ });
118
+
119
+ return new Promise((resolve, reject) => {
120
+ server.on('error', error => {
121
+ if (error.errno === 'EADDRINUSE') {
122
+ reject(errors.ResourceConflictError(`Port ${options.port} is already in use`));
123
+ }
124
+ else if (error.errno === 'EACCES') {
125
+ reject(errors.InsufficientAccessError());
126
+ }
127
+ else {
128
+ reject(error);
129
+ }
130
+ });
131
+
132
+ // Bind the socket to a port (the || 0 bit auto-selects a port if one isn't provided)
133
+ server.listen(options.port || 0, options.host, () => {
134
+ resolve({
135
+ port: server.address().port,
136
+ metadata: { mode },
137
+ close: callback => {
138
+ server.close(() => { callback(); });
139
+ Object.keys(connections).forEach(socket => {
140
+ connections[socket].destroy();
141
+ });
142
+ },
143
+ proxy: tcpProxy.create(logger, encoding, isEndOfRequest),
144
+ encoding: encoding,
145
+ isEndOfRequest: isEndOfRequest
146
+ });
147
+ });
148
+ });
149
+ }
150
+
151
+ module.exports = {
152
+ testRequest: { data: 'test' },
153
+ testProxyResponse: { data: '' },
154
+ create: create,
155
+ validate: tcpValidator.validate
156
+ };
@@ -0,0 +1,19 @@
1
+ 'use strict';
2
+
3
+ const exceptions = require('../../util/errors.js');
4
+
5
+ /**
6
+ * Additional tcp-specific validations
7
+ * @module
8
+ */
9
+
10
+ function validate (request) {
11
+ const errors = [];
12
+
13
+ if (request.mode && ['text', 'binary'].indexOf(request.mode) < 0) {
14
+ errors.push(exceptions.ValidationError("'mode' must be one of ['text', 'binary']"));
15
+ }
16
+ return errors;
17
+ }
18
+
19
+ module.exports = { validate };
@@ -0,0 +1,95 @@
1
+ 'use strict';
2
+
3
+ const xpath = require('xpath'),
4
+ xmlDom = require('@xmldom/xmldom'),
5
+ errors = require('../util/errors.js'),
6
+ helpers = require('../util/helpers.js');
7
+
8
+ /**
9
+ * Shared logic for xpath selector
10
+ * @module
11
+ */
12
+
13
+ function xpathSelect (selectFn, selector, doc) {
14
+ if (!helpers.defined(doc)) {
15
+ return [];
16
+ }
17
+
18
+ if (doc && doc.implementation) {
19
+ // Monkey patch due to some legacy case sensitivity check in xpath
20
+ // Couldn't find a way to patch xpath due to closures so patched the dom instead
21
+ const originalHasFeature = doc.implementation.hasFeature;
22
+ doc.implementation.hasFeature = (feature, version) => {
23
+ if (feature === 'HTML' && version === '2.0') {
24
+ return false;
25
+ }
26
+ else {
27
+ return originalHasFeature.apply(doc.implementation, [feature, version]);
28
+ }
29
+ };
30
+ }
31
+
32
+ try {
33
+ return selectFn(selector, doc);
34
+ }
35
+ catch (e) {
36
+ throw errors.ValidationError('malformed xpath predicate selector', {
37
+ source: selector,
38
+ inner: e
39
+ });
40
+ }
41
+ }
42
+
43
+ function nodeValue (node) {
44
+ if (node.nodeType === node.TEXT_NODE) {
45
+ return node.nodeValue;
46
+ }
47
+ else if (node.nodeType === node.ATTRIBUTE_NODE) {
48
+ return node.value;
49
+ }
50
+ else if (node.firstChild) {
51
+ // Converting to a string allows exists to return true if the node exists,
52
+ // even if there's no data
53
+ return String(node.firstChild.data);
54
+ }
55
+ else {
56
+ return String(node.data);
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Returns xpath value(s) from given xml
62
+ * @param {String} selector - The xpath selector
63
+ * @param {Object} ns - The namespace map
64
+ * @param {String} possibleXML - the xml
65
+ * @param {Object} logger - Optional, used to log XML parsing errors
66
+ * @returns {Object}
67
+ */
68
+ function select (selector, ns, possibleXML, logger) {
69
+ const DOMParser = xmlDom.DOMParser,
70
+ parser = new DOMParser({
71
+ errorHandler: (level, message) => {
72
+ const warn = (logger || {}).warn || (() => {});
73
+ warn('%s (source: %s)', message, JSON.stringify(possibleXML));
74
+ }
75
+ }),
76
+ doc = parser.parseFromString(possibleXML),
77
+ selectFn = xpath.useNamespaces(ns || {}),
78
+ result = xpathSelect(selectFn, selector, doc);
79
+ let nodeValues;
80
+
81
+ if (['number', 'boolean'].indexOf(typeof result) >= 0) {
82
+ return result;
83
+ }
84
+
85
+ nodeValues = result.map(nodeValue);
86
+
87
+ if (nodeValues.length === 0) {
88
+ return undefined;
89
+ }
90
+ else {
91
+ return nodeValues;
92
+ }
93
+ }
94
+
95
+ module.exports = { select };
@@ -0,0 +1,245 @@
1
+ 'use strict';
2
+
3
+ const express = require('express'),
4
+ cors = require('cors'),
5
+ promClient = require('prom-client'),
6
+ errorHandler = require('errorhandler'),
7
+ path = require('path'),
8
+ middleware = require('./util/middleware'),
9
+ thisPackage = require('../package.json'),
10
+ releases = require('../releases.json'),
11
+ helpers = require('./util/helpers.js'),
12
+ utilLogger = require('./util/logger.js'),
13
+ utilIp = require('./util/ip.js'),
14
+ protocolsModule = require('./models/protocols.js'),
15
+ imposterRepositoryModule = require('./models/impostersRepository.js'),
16
+ homeControllerModule = require('./controllers/homeController.js'),
17
+ impostersControllerModule = require('./controllers/impostersController.js'),
18
+ imposterControllerModule = require('./controllers/imposterController.js'),
19
+ logsControllerModule = require('./controllers/logsController.js'),
20
+ configControllerModule = require('./controllers/configController.js'),
21
+ feedControllerModule = require('./controllers/feedController.js');
22
+
23
+ /**
24
+ * The entry point for mountebank. This module creates the mountebank server,
25
+ * configures all middleware and manages all routing
26
+ * @module
27
+ */
28
+
29
+ /**
30
+ * Creates the mountebank express application
31
+ * @param {object} options - The command line options
32
+ * @returns {Object} An object with a close method to stop the server
33
+ */
34
+
35
+ function applyDefaults (options) {
36
+ // Minimal defaults to start bypassing the CLI (e.g. embedding in an express app)
37
+ const defaults = {
38
+ port: 2525,
39
+ ipWhitelist: ['*']
40
+ };
41
+ Object.keys(defaults).forEach(key => {
42
+ options[key] = typeof options[key] === 'undefined' ? defaults[key] : options[key];
43
+ });
44
+ }
45
+
46
+ async function createApp (options) {
47
+ applyDefaults(options);
48
+
49
+ const app = express(),
50
+ hostname = options.host || 'localhost',
51
+ baseURL = `http://${hostname}:${options.port}`,
52
+ logger = utilLogger.createLogger(options),
53
+ isAllowedConnection = utilIp.createIPVerification(options),
54
+ imposters = imposterRepositoryModule.create(options, logger),
55
+ protocols = protocolsModule.loadProtocols(options, baseURL, logger, isAllowedConnection, imposters),
56
+ homeController = homeControllerModule.create(releases),
57
+ impostersController = impostersControllerModule.create(
58
+ protocols, imposters, logger, options.allowInjection),
59
+ imposterController = imposterControllerModule.create(
60
+ protocols, imposters, logger, options.allowInjection),
61
+ logfile = options.log.transports.file ? options.log.transports.file.path : false,
62
+ logsController = logsControllerModule.create(logfile),
63
+ configController = configControllerModule.create(thisPackage.version, options),
64
+ feedController = feedControllerModule.create(releases),
65
+ validateImposterExists = middleware.createImposterValidator(imposters),
66
+ prometheus = promClient;
67
+
68
+ // Clear only matters when bound using directly in-process through JS rather than the CLI
69
+ prometheus.register.clear();
70
+ prometheus.collectDefaultMetrics({ prefix: 'mb_' });
71
+
72
+ app.use(middleware.useAbsoluteUrls(options.port));
73
+ app.use(middleware.validateApiKey(options.apikey, logger));
74
+ app.use(middleware.logger(logger, ':method :url'));
75
+ app.use(middleware.globals({ port: options.port, version: thisPackage.version }));
76
+ app.use(middleware.defaultIEtoHTML);
77
+ app.use(middleware.json(logger));
78
+ app.use(express.static(path.join(__dirname, 'public')));
79
+ app.use(express.static(path.join(__dirname, '../node_modules')));
80
+ app.use(errorHandler());
81
+ app.use(cors({ origin: options.origin }));
82
+
83
+ app.disable('etag');
84
+ app.disable('x-powered-by');
85
+ app.set('views', path.join(__dirname, 'views'));
86
+ app.set('view engine', 'ejs');
87
+ app.set('json spaces', 2);
88
+
89
+ app.get('/', homeController.get);
90
+ app.get('/imposters', impostersController.get);
91
+ app.post('/imposters', impostersController.post);
92
+ app.delete('/imposters', impostersController.del);
93
+ app.put('/imposters', impostersController.put);
94
+ app.get('/imposters/:id', validateImposterExists, imposterController.get);
95
+ app.delete('/imposters/:id', imposterController.del);
96
+ app.delete('/imposters/:id/savedProxyResponses', validateImposterExists, imposterController.resetProxies);
97
+ app.delete('/imposters/:id/savedRequests', validateImposterExists, imposterController.resetRequests);
98
+
99
+ // deprecated but saved for backwards compatibility
100
+ app.delete('/imposters/:id/requests', validateImposterExists, imposterController.resetProxies);
101
+
102
+ // Changing stubs without restarting imposter
103
+ app.put('/imposters/:id/stubs', validateImposterExists, imposterController.putStubs);
104
+ app.put('/imposters/:id/stubs/:stubIndex', validateImposterExists, imposterController.putStub);
105
+ app.post('/imposters/:id/stubs', validateImposterExists, imposterController.postStub);
106
+ app.delete('/imposters/:id/stubs/:stubIndex', validateImposterExists, imposterController.deleteStub);
107
+
108
+ // Protocol implementation APIs
109
+ app.post('/imposters/:id/_requests', validateImposterExists, imposterController.postRequest);
110
+ app.post('/imposters/:id/_requests/:proxyResolutionKey', validateImposterExists, imposterController.postProxyResponse);
111
+
112
+ app.get('/logs', logsController.get);
113
+ app.get('/config', configController.get);
114
+ app.get('/feed', feedController.getFeed);
115
+ app.get('/releases', feedController.getReleases);
116
+ app.get('/releases/:version', feedController.getRelease);
117
+
118
+ app.get('/metrics', async function (request, response) {
119
+ const register = promClient.register;
120
+ response.set('Content-Type', register.contentType);
121
+ response.end(await register.metrics());
122
+ });
123
+
124
+ app.get('/sitemap', (request, response) => {
125
+ response.type('text/plain');
126
+ response.render('sitemap', { releases: releases });
127
+ });
128
+
129
+ [
130
+ '/support',
131
+ '/license',
132
+ '/faqs',
133
+ '/docs/gettingStarted',
134
+ '/docs/mentalModel',
135
+ '/docs/commandLine',
136
+ '/docs/communityExtensions',
137
+ '/docs/security',
138
+ '/docs/api/overview',
139
+ '/docs/api/contracts',
140
+ '/docs/api/mocks',
141
+ '/docs/api/stubs',
142
+ '/docs/api/predicates',
143
+ '/docs/api/xpath',
144
+ '/docs/api/json',
145
+ '/docs/api/jsonpath',
146
+ '/docs/api/proxies',
147
+ '/docs/api/injection',
148
+ '/docs/api/behaviors',
149
+ '/docs/api/errors',
150
+ '/docs/api/faults',
151
+ '/docs/protocols/http',
152
+ '/docs/protocols/https',
153
+ '/docs/protocols/tcp',
154
+ '/docs/protocols/smtp',
155
+ '/docs/protocols/custom'
156
+ ].forEach(endpoint => {
157
+ app.get(endpoint, (request, response) => {
158
+ response.render(endpoint.substring(1));
159
+ });
160
+ });
161
+
162
+ process.once('exit', () => {
163
+ imposters.stopAllSync();
164
+ });
165
+
166
+ if (options.allowInjection) {
167
+ logger.warn(`Running with --allowInjection set. See ${baseURL}/docs/security for security info`);
168
+ }
169
+
170
+ await imposters.loadAll(protocols);
171
+
172
+ return app;
173
+ }
174
+
175
+ /**
176
+ * Start the mountebank server
177
+ * @param {function} app - mountebank express application
178
+ * @param {object} options - The command line options
179
+ * @returns {Object} An object with a close method to stop the server
180
+ */
181
+ async function listen (app, options) {
182
+ const hostname = options.host || 'localhost',
183
+ baseURL = `http://${hostname}:${options.port}`,
184
+ logger = utilLogger.createLogger(options),
185
+ isAllowedConnection = utilIp.createIPVerification(options);
186
+
187
+ return new Promise(resolve => {
188
+ const connections = {},
189
+ server = app.listen(options.port, options.host, () => {
190
+ logger.info(`mountebank v${thisPackage.version} now taking orders - point your browser to ${baseURL}/ for help`);
191
+ logger.debug(`config: ${JSON.stringify({
192
+ options: options,
193
+ process: {
194
+ nodeVersion: process.version,
195
+ architecture: process.arch,
196
+ platform: process.platform
197
+ }
198
+ })}`);
199
+
200
+ resolve({
201
+ close: callback => {
202
+ server.close(() => {
203
+ logger.info('Adios - see you soon?');
204
+ callback();
205
+ });
206
+
207
+ // Force kill any open connections to prevent process hanging
208
+ Object.keys(connections).forEach(socket => {
209
+ connections[socket].destroy();
210
+ });
211
+ }
212
+ });
213
+ });
214
+
215
+ server.on('connection', socket => {
216
+ const name = helpers.socketName(socket),
217
+ ipAddress = socket.remoteAddress;
218
+ connections[name] = socket;
219
+
220
+ socket.on('close', () => {
221
+ delete connections[name];
222
+ });
223
+
224
+ socket.on('error', error => {
225
+ logger.error(`${name} transmission error X=> ${JSON.stringify(error)}`);
226
+ });
227
+
228
+ if (!isAllowedConnection(ipAddress, logger)) {
229
+ socket.destroy();
230
+ }
231
+ });
232
+ });
233
+ }
234
+
235
+ /**
236
+ * Creates the mountebank server
237
+ * @param {object} options - The command line options
238
+ * @returns {Object} An object with a close method to stop the server
239
+ */
240
+ async function create (options) {
241
+ const app = await createApp(options);
242
+ return listen(app, options);
243
+ }
244
+
245
+ module.exports = { create, createApp };
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file