@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.
- package/LICENSE +21 -0
- package/README.md +94 -0
- package/bin/mb +136 -0
- package/package.json +71 -0
- package/releases.json +52 -0
- package/src/cli/api.js +112 -0
- package/src/cli/cli.js +420 -0
- package/src/controllers/configController.js +64 -0
- package/src/controllers/feedController.js +115 -0
- package/src/controllers/homeController.js +58 -0
- package/src/controllers/imposterController.js +328 -0
- package/src/controllers/impostersController.js +215 -0
- package/src/controllers/logsController.js +52 -0
- package/src/models/behaviors.js +553 -0
- package/src/models/behaviorsValidator.js +186 -0
- package/src/models/compatibility.js +133 -0
- package/src/models/dryRunValidator.js +261 -0
- package/src/models/filesystemBackedImpostersRepository.js +908 -0
- package/src/models/http/baseHttpServer.js +207 -0
- package/src/models/http/headersMap.js +87 -0
- package/src/models/http/httpProxy.js +230 -0
- package/src/models/http/httpRequest.js +82 -0
- package/src/models/http/httpServer.js +18 -0
- package/src/models/http/index.js +18 -0
- package/src/models/https/cert/mb-cert.pem +20 -0
- package/src/models/https/cert/mb-csr.pem +16 -0
- package/src/models/https/cert/mb-key.pem +27 -0
- package/src/models/https/httpsServer.js +42 -0
- package/src/models/https/index.js +18 -0
- package/src/models/imposter.js +243 -0
- package/src/models/imposterPrinter.js +120 -0
- package/src/models/impostersRepository.js +49 -0
- package/src/models/inMemoryImpostersRepository.js +418 -0
- package/src/models/jsonpath.js +44 -0
- package/src/models/mbConnection.js +107 -0
- package/src/models/predicates.js +438 -0
- package/src/models/protocols.js +242 -0
- package/src/models/responseResolver.js +398 -0
- package/src/models/smtp/index.js +16 -0
- package/src/models/smtp/smtpRequest.js +60 -0
- package/src/models/smtp/smtpServer.js +109 -0
- package/src/models/tcp/index.js +18 -0
- package/src/models/tcp/tcpProxy.js +110 -0
- package/src/models/tcp/tcpRequest.js +23 -0
- package/src/models/tcp/tcpServer.js +156 -0
- package/src/models/tcp/tcpValidator.js +19 -0
- package/src/models/xpath.js +95 -0
- package/src/mountebank.js +245 -0
- package/src/public/images/arrow_down.png +0 -0
- package/src/public/images/arrow_up.png +0 -0
- package/src/public/images/book.jpg +0 -0
- package/src/public/images/dataflow.png +0 -0
- package/src/public/images/favicon.ico +0 -0
- package/src/public/images/forkme_right_orange_ff7600.png +0 -0
- package/src/public/images/mountebank.png +0 -0
- package/src/public/images/overview.gif +0 -0
- package/src/public/images/quote.png +0 -0
- package/src/public/images/tw-logo.png +0 -0
- package/src/public/scripts/jquery/jquery-3.6.1.min.js +2 -0
- package/src/public/scripts/urlHashHandler.js +31 -0
- package/src/public/stylesheets/application.css +424 -0
- package/src/public/stylesheets/ie.css +14 -0
- package/src/public/stylesheets/imposters.css +121 -0
- package/src/public/stylesheets/jqueryui/1.10.4/themes/smoothness/jquery-ui.css +1178 -0
- package/src/util/combinators.js +68 -0
- package/src/util/date.js +51 -0
- package/src/util/errors.js +55 -0
- package/src/util/helpers.js +131 -0
- package/src/util/inherit.js +28 -0
- package/src/util/ip.js +54 -0
- package/src/util/logger.js +83 -0
- package/src/util/middleware.js +256 -0
- package/src/util/scopedLogger.js +47 -0
- package/src/views/_footer.ejs +20 -0
- package/src/views/_header.ejs +113 -0
- package/src/views/_imposter.ejs +8 -0
- package/src/views/config.ejs +71 -0
- package/src/views/docs/api/behaviors/copy.ejs +427 -0
- package/src/views/docs/api/behaviors/decorate.ejs +182 -0
- package/src/views/docs/api/behaviors/lookup.ejs +220 -0
- package/src/views/docs/api/behaviors/shellTransform.ejs +153 -0
- package/src/views/docs/api/behaviors/wait.ejs +121 -0
- package/src/views/docs/api/behaviors.ejs +141 -0
- package/src/views/docs/api/contracts/addStub-description.ejs +10 -0
- package/src/views/docs/api/contracts/addStub.ejs +10 -0
- package/src/views/docs/api/contracts/config-description.ejs +32 -0
- package/src/views/docs/api/contracts/config.ejs +23 -0
- package/src/views/docs/api/contracts/home-description.ejs +18 -0
- package/src/views/docs/api/contracts/home.ejs +13 -0
- package/src/views/docs/api/contracts/imposter-description.ejs +439 -0
- package/src/views/docs/api/contracts/imposter.ejs +182 -0
- package/src/views/docs/api/contracts/imposters-description.ejs +13 -0
- package/src/views/docs/api/contracts/imposters.ejs +13 -0
- package/src/views/docs/api/contracts/logs-description.ejs +3 -0
- package/src/views/docs/api/contracts/logs.ejs +14 -0
- package/src/views/docs/api/contracts/stub-description.ejs +4 -0
- package/src/views/docs/api/contracts/stub.ejs +7 -0
- package/src/views/docs/api/contracts/stubs-description.ejs +4 -0
- package/src/views/docs/api/contracts/stubs.ejs +11 -0
- package/src/views/docs/api/contracts.ejs +133 -0
- package/src/views/docs/api/errors.ejs +64 -0
- package/src/views/docs/api/fault/connectionReset.ejs +31 -0
- package/src/views/docs/api/fault/randomDataThenClose.ejs +31 -0
- package/src/views/docs/api/faults.ejs +57 -0
- package/src/views/docs/api/injection.ejs +426 -0
- package/src/views/docs/api/json.ejs +205 -0
- package/src/views/docs/api/jsonpath.ejs +210 -0
- package/src/views/docs/api/mocks.ejs +130 -0
- package/src/views/docs/api/overview.ejs +968 -0
- package/src/views/docs/api/predicates/and.ejs +62 -0
- package/src/views/docs/api/predicates/contains.ejs +64 -0
- package/src/views/docs/api/predicates/deepEquals.ejs +114 -0
- package/src/views/docs/api/predicates/endsWith.ejs +66 -0
- package/src/views/docs/api/predicates/equals.ejs +125 -0
- package/src/views/docs/api/predicates/exists.ejs +118 -0
- package/src/views/docs/api/predicates/inject.ejs +67 -0
- package/src/views/docs/api/predicates/matches.ejs +66 -0
- package/src/views/docs/api/predicates/not.ejs +52 -0
- package/src/views/docs/api/predicates/or.ejs +79 -0
- package/src/views/docs/api/predicates/startsWith.ejs +62 -0
- package/src/views/docs/api/predicates.ejs +382 -0
- package/src/views/docs/api/proxies.ejs +191 -0
- package/src/views/docs/api/proxy/addDecorateBehavior.ejs +115 -0
- package/src/views/docs/api/proxy/addWaitBehavior.ejs +96 -0
- package/src/views/docs/api/proxy/injectHeaders.ejs +91 -0
- package/src/views/docs/api/proxy/predicateGenerators.ejs +600 -0
- package/src/views/docs/api/proxy/proxyModes.ejs +495 -0
- package/src/views/docs/api/stubs.ejs +391 -0
- package/src/views/docs/api/xpath.ejs +281 -0
- package/src/views/docs/cli/configFiles.ejs +133 -0
- package/src/views/docs/cli/customFormatters.ejs +53 -0
- package/src/views/docs/cli/help.ejs +6 -0
- package/src/views/docs/cli/replay.ejs +42 -0
- package/src/views/docs/cli/restart.ejs +10 -0
- package/src/views/docs/cli/save.ejs +68 -0
- package/src/views/docs/cli/start.ejs +234 -0
- package/src/views/docs/cli/stop.ejs +32 -0
- package/src/views/docs/commandLine.ejs +93 -0
- package/src/views/docs/communityExtensions.ejs +233 -0
- package/src/views/docs/gettingStarted.ejs +146 -0
- package/src/views/docs/mentalModel.ejs +51 -0
- package/src/views/docs/protocols/custom.ejs +231 -0
- package/src/views/docs/protocols/http.ejs +238 -0
- package/src/views/docs/protocols/https.ejs +246 -0
- package/src/views/docs/protocols/smtp.ejs +142 -0
- package/src/views/docs/protocols/tcp.ejs +431 -0
- package/src/views/docs/security.ejs +38 -0
- package/src/views/faqs.ejs +65 -0
- package/src/views/feed.ejs +33 -0
- package/src/views/imposter.ejs +22 -0
- package/src/views/imposters.ejs +33 -0
- package/src/views/index.ejs +89 -0
- package/src/views/license.ejs +30 -0
- package/src/views/logs.ejs +77 -0
- package/src/views/releases/v1.1.0.ejs +55 -0
- package/src/views/releases/v1.1.36.ejs +84 -0
- package/src/views/releases/v1.1.72.ejs +92 -0
- package/src/views/releases/v1.10.0.ejs +108 -0
- package/src/views/releases/v1.11.0.ejs +109 -0
- package/src/views/releases/v1.12.0.ejs +96 -0
- package/src/views/releases/v1.13.0.ejs +118 -0
- package/src/views/releases/v1.14.0.ejs +107 -0
- package/src/views/releases/v1.14.1.ejs +94 -0
- package/src/views/releases/v1.15.0.ejs +113 -0
- package/src/views/releases/v1.16.0.ejs +104 -0
- package/src/views/releases/v1.2.0.ejs +78 -0
- package/src/views/releases/v1.2.103.ejs +86 -0
- package/src/views/releases/v1.2.122.ejs +86 -0
- package/src/views/releases/v1.2.30.ejs +84 -0
- package/src/views/releases/v1.2.45.ejs +84 -0
- package/src/views/releases/v1.2.56.ejs +79 -0
- package/src/views/releases/v1.3.0.ejs +86 -0
- package/src/views/releases/v1.3.1.ejs +100 -0
- package/src/views/releases/v1.4.0.ejs +96 -0
- package/src/views/releases/v1.4.1.ejs +103 -0
- package/src/views/releases/v1.4.2.ejs +100 -0
- package/src/views/releases/v1.4.3.ejs +113 -0
- package/src/views/releases/v1.5.0.ejs +104 -0
- package/src/views/releases/v1.5.1.ejs +91 -0
- package/src/views/releases/v1.6.0.ejs +109 -0
- package/src/views/releases/v1.7.0.ejs +113 -0
- package/src/views/releases/v1.7.1.ejs +90 -0
- package/src/views/releases/v1.7.2.ejs +96 -0
- package/src/views/releases/v1.8.0.ejs +121 -0
- package/src/views/releases/v1.9.0.ejs +111 -0
- package/src/views/releases/v2.0.0.ejs +159 -0
- package/src/views/releases/v2.1.0.ejs +121 -0
- package/src/views/releases/v2.1.1.ejs +106 -0
- package/src/views/releases/v2.1.2.ejs +84 -0
- package/src/views/releases/v2.2.0.ejs +115 -0
- package/src/views/releases/v2.2.1.ejs +102 -0
- package/src/views/releases/v2.3.0.ejs +121 -0
- package/src/views/releases/v2.3.1.ejs +100 -0
- package/src/views/releases/v2.3.2.ejs +102 -0
- package/src/views/releases/v2.3.3.ejs +97 -0
- package/src/views/releases/v2.4.0.ejs +114 -0
- package/src/views/releases/v2.5.0.ejs +51 -0
- package/src/views/releases/v2.6.0.ejs +35 -0
- package/src/views/releases/v2.7.0.ejs +32 -0
- package/src/views/releases/v2.8.0.ejs +36 -0
- package/src/views/releases/v2.8.1.ejs +7 -0
- package/src/views/releases/v2.8.2.ejs +26 -0
- package/src/views/releases/v2.9.0.ejs +32 -0
- package/src/views/releases/v2.9.1.ejs +10 -0
- package/src/views/releases.ejs +26 -0
- package/src/views/sitemap.ejs +36 -0
- package/src/views/support.ejs +14 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The base implementation of http/s servers
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const net = require('net'),
|
|
9
|
+
headersMap = require('./headersMap.js'),
|
|
10
|
+
errors = require('../../util/errors.js'),
|
|
11
|
+
httpProxy = require('./httpProxy.js'),
|
|
12
|
+
httpRequest = require('./httpRequest.js'),
|
|
13
|
+
helpers = require('../../util/helpers.js');
|
|
14
|
+
|
|
15
|
+
module.exports = function (createBaseServer) {
|
|
16
|
+
|
|
17
|
+
function create (options, logger, responseFn) {
|
|
18
|
+
const connections = {},
|
|
19
|
+
defaultResponse = options.defaultResponse || {};
|
|
20
|
+
|
|
21
|
+
function postProcess (stubResponse, request) {
|
|
22
|
+
/* eslint complexity: 0 */
|
|
23
|
+
const defaultHeaders = defaultResponse.headers || {},
|
|
24
|
+
response = {
|
|
25
|
+
statusCode: stubResponse.statusCode || defaultResponse.statusCode || 200,
|
|
26
|
+
headers: stubResponse.headers || defaultHeaders,
|
|
27
|
+
body: stubResponse.body || defaultResponse.body || '',
|
|
28
|
+
_mode: stubResponse._mode || defaultResponse._mode || 'text'
|
|
29
|
+
},
|
|
30
|
+
responseHeaders = headersMap.of(response.headers),
|
|
31
|
+
encoding = response._mode === 'binary' ? 'base64' : 'utf8',
|
|
32
|
+
isObject = helpers.isObject;
|
|
33
|
+
|
|
34
|
+
if (isObject(response.body)) {
|
|
35
|
+
// Support JSON response bodies
|
|
36
|
+
response.body = JSON.stringify(response.body, null, 4);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (options.allowCORS) {
|
|
40
|
+
const requestHeaders = headersMap.of(request.headers),
|
|
41
|
+
isCrossOriginPreflight = request.method === 'OPTIONS' &&
|
|
42
|
+
requestHeaders.get('Access-Control-Request-Headers') &&
|
|
43
|
+
requestHeaders.get('Access-Control-Request-Method') &&
|
|
44
|
+
requestHeaders.get('Origin');
|
|
45
|
+
|
|
46
|
+
if (isCrossOriginPreflight) {
|
|
47
|
+
responseHeaders.set('Access-Control-Allow-Headers', requestHeaders.get('Access-Control-Request-Headers'));
|
|
48
|
+
responseHeaders.set('Access-Control-Allow-Methods', requestHeaders.get('Access-Control-Request-Method'));
|
|
49
|
+
responseHeaders.set('Access-Control-Allow-Origin', requestHeaders.get('Origin'));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (encoding === 'base64') {
|
|
54
|
+
// ensure the base64 has no newlines or other non
|
|
55
|
+
// base64 chars that will cause the body to be garbled.
|
|
56
|
+
response.body = response.body.replace(/[^A-Za-z0-9=+/]+/g, '');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!responseHeaders.has('Connection')) {
|
|
60
|
+
responseHeaders.set('Connection', 'close');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (responseHeaders.has('Content-Length')) {
|
|
64
|
+
responseHeaders.set('Content-Length', Buffer.byteLength(response.body, encoding));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return response;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const baseServer = createBaseServer(options),
|
|
71
|
+
server = baseServer.createNodeServer();
|
|
72
|
+
|
|
73
|
+
// Allow long wait behaviors
|
|
74
|
+
server.timeout = 0;
|
|
75
|
+
|
|
76
|
+
server.on('connect', (response, client, head) => {
|
|
77
|
+
const host = response.socket.servername;
|
|
78
|
+
const port = response.socket.localPort;
|
|
79
|
+
// may we prefer this method instead?
|
|
80
|
+
const proxyServer = net.createConnection({ host, port }, () => {
|
|
81
|
+
client.on('error', err => {
|
|
82
|
+
logger.warn('CLIENT TO PROXY ERROR: [%s] %s', err.code, err.message);
|
|
83
|
+
logger.debug('%s', err.stack);
|
|
84
|
+
});
|
|
85
|
+
proxyServer.on('error', err => {
|
|
86
|
+
logger.warn('SERVER PROXY ERROR: [%s] %s', err.code, err.message);
|
|
87
|
+
logger.debug('%s', err.stack);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
logger.info('PROXY TO SERVER SET UP TO %s ON %s', host, port);
|
|
91
|
+
client.write('HTTP/1.1 200 Connection established\r\n\r\n');
|
|
92
|
+
proxyServer.write(head);
|
|
93
|
+
proxyServer.pipe(client);
|
|
94
|
+
client.pipe(proxyServer);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
server.on('connection', socket => {
|
|
99
|
+
const name = helpers.socketName(socket);
|
|
100
|
+
|
|
101
|
+
logger.debug('%s ESTABLISHED', name);
|
|
102
|
+
|
|
103
|
+
if (socket.on) {
|
|
104
|
+
connections[name] = socket;
|
|
105
|
+
|
|
106
|
+
socket.on('error', error => {
|
|
107
|
+
logger.error('%s transmission error X=> %s', name, JSON.stringify(error));
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
socket.on('end', () => {
|
|
111
|
+
logger.debug('%s LAST-ACK', name);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
socket.on('close', () => {
|
|
115
|
+
logger.debug('%s CLOSED', name);
|
|
116
|
+
delete connections[name];
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
server.on('request', async (request, response) => {
|
|
122
|
+
const clientName = helpers.socketName(request.socket);
|
|
123
|
+
|
|
124
|
+
logger.info(`${clientName} => ${request.method} ${request.url}`);
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
const simplifiedRequest = await httpRequest.createFrom(request);
|
|
128
|
+
logger.debug('%s => %s', clientName, JSON.stringify(simplifiedRequest));
|
|
129
|
+
|
|
130
|
+
const mbResponse = await responseFn(simplifiedRequest, { rawUrl: request.url }),
|
|
131
|
+
stubResponse = postProcess(mbResponse, simplifiedRequest),
|
|
132
|
+
encoding = stubResponse._mode === 'binary' ? 'base64' : 'utf8';
|
|
133
|
+
|
|
134
|
+
if (mbResponse.blocked) {
|
|
135
|
+
request.socket.destroy();
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (helpers.simulateFault(request.socket, mbResponse.fault, logger)) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
response.writeHead(stubResponse.statusCode, stubResponse.headers);
|
|
144
|
+
response.end(stubResponse.body.toString(), encoding);
|
|
145
|
+
|
|
146
|
+
if (stubResponse) {
|
|
147
|
+
logger.debug('%s <= %s', clientName, JSON.stringify(stubResponse));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
const exceptions = errors;
|
|
152
|
+
logger.error('%s X=> %s', clientName, JSON.stringify(exceptions.details(error)));
|
|
153
|
+
response.writeHead(500, { 'content-type': 'application/json' });
|
|
154
|
+
response.end(JSON.stringify({ errors: [exceptions.details(error)] }), 'utf8');
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
return new Promise((resolve, reject) => {
|
|
159
|
+
server.on('error', error => {
|
|
160
|
+
if (error.errno === 'EADDRINUSE') {
|
|
161
|
+
reject(errors.ResourceConflictError(`Port ${options.port} is already in use`));
|
|
162
|
+
}
|
|
163
|
+
else if (error.errno === 'EACCES') {
|
|
164
|
+
reject(errors.InsufficientAccessError());
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
reject(error);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Bind the socket to a port (the || 0 bit auto-selects a port if one isn't provided)
|
|
172
|
+
server.listen(options.port || 0, options.host, () => {
|
|
173
|
+
resolve({
|
|
174
|
+
port: server.address().port,
|
|
175
|
+
metadata: baseServer.metadata,
|
|
176
|
+
close: callback => {
|
|
177
|
+
server.close(callback);
|
|
178
|
+
Object.keys(connections).forEach(socket => {
|
|
179
|
+
connections[socket].destroy();
|
|
180
|
+
});
|
|
181
|
+
},
|
|
182
|
+
proxy: httpProxy.create(logger),
|
|
183
|
+
encoding: 'utf8'
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
testRequest: {
|
|
191
|
+
requestFrom: '',
|
|
192
|
+
method: 'GET',
|
|
193
|
+
path: '/',
|
|
194
|
+
query: {},
|
|
195
|
+
headers: {},
|
|
196
|
+
form: {},
|
|
197
|
+
body: ''
|
|
198
|
+
},
|
|
199
|
+
testProxyResponse: {
|
|
200
|
+
statusCode: 200,
|
|
201
|
+
headers: {},
|
|
202
|
+
body: ''
|
|
203
|
+
},
|
|
204
|
+
create: create,
|
|
205
|
+
validate: undefined
|
|
206
|
+
};
|
|
207
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Utility module to to get and set headers in a case-insensitive way while maintaining
|
|
5
|
+
* the original case sent in the request
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Creates a map from the given headers
|
|
11
|
+
* @param {Object} headers - the starting headers
|
|
12
|
+
* @returns {Object} - the map
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const helpers = require('../../util/helpers.js');
|
|
16
|
+
|
|
17
|
+
function of (headers) {
|
|
18
|
+
/**
|
|
19
|
+
* Returns whether the map contains the given headerName, regardless of case
|
|
20
|
+
* @param {String} headerName - the header name
|
|
21
|
+
* @returns {boolean}
|
|
22
|
+
*/
|
|
23
|
+
function has (headerName) {
|
|
24
|
+
return Object.keys(headers).some(header => header.toLowerCase() === headerName.toLowerCase());
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function headerNameFor (headerName) {
|
|
28
|
+
const result = Object.keys(headers).find(header => header.toLowerCase() === headerName.toLowerCase());
|
|
29
|
+
|
|
30
|
+
return helpers.defined(result) ? result : headerName;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Retrieves the value for the given header in a case-insensitive way
|
|
35
|
+
* @param {String} headerName - the header name
|
|
36
|
+
* @returns {*}
|
|
37
|
+
*/
|
|
38
|
+
function get (headerName) {
|
|
39
|
+
return headers[headerNameFor(headerName)];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Sets the value for the given header in a case-insensitive way.
|
|
44
|
+
* Will add the key if it does not already exist
|
|
45
|
+
* @param {String} headerName - the header name
|
|
46
|
+
* @param {String} value - the value
|
|
47
|
+
*/
|
|
48
|
+
function set (headerName, value) {
|
|
49
|
+
headers[headerNameFor(headerName)] = value;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Retrieves all the headers with the original case for the keys
|
|
54
|
+
* @returns {Object} - the key/value pairs
|
|
55
|
+
*/
|
|
56
|
+
function all () {
|
|
57
|
+
return headers;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return { get, set, has, all };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function add (current, value) {
|
|
64
|
+
return Array.isArray(current) ? current.concat(value) : [current].concat(value);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function arrayifyIfExists (current, value) {
|
|
68
|
+
return current ? add(current, value) : value;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Converts the array of raw headers to a map, maintaining the original case of the keys.
|
|
73
|
+
* This is necessary since node downcases the headers in its request.headers
|
|
74
|
+
* @param {Object} rawHeaders - the array of alternating keys and values
|
|
75
|
+
* @returns {Object} - the map
|
|
76
|
+
*/
|
|
77
|
+
function ofRaw (rawHeaders) {
|
|
78
|
+
const result = {};
|
|
79
|
+
for (let i = 0; i < rawHeaders.length; i += 2) {
|
|
80
|
+
const name = rawHeaders[i];
|
|
81
|
+
const value = rawHeaders[i + 1];
|
|
82
|
+
result[name] = arrayifyIfExists(result[name], value);
|
|
83
|
+
}
|
|
84
|
+
return of(result);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = { ofRaw, of };
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The proxy implementation for http/s imposters
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const https = require('https'),
|
|
9
|
+
http = require('http'),
|
|
10
|
+
queryString = require('querystring'),
|
|
11
|
+
HttpProxyAgent = require('http-proxy-agent'),
|
|
12
|
+
HttpsProxyAgent = require('https-proxy-agent'),
|
|
13
|
+
helpers = require('../../util/helpers.js'),
|
|
14
|
+
headersMap = require('./headersMap.js'),
|
|
15
|
+
errors = require('../../util/errors.js');
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Creates the proxy
|
|
22
|
+
* @param {Object} logger - The logger
|
|
23
|
+
* @returns {Object}
|
|
24
|
+
*/
|
|
25
|
+
function create (logger) {
|
|
26
|
+
const BINARY_CONTENT_ENCODINGS = [
|
|
27
|
+
'gzip', 'br', 'compress', 'deflate'
|
|
28
|
+
];
|
|
29
|
+
const BINARY_MIME_TYPES = [
|
|
30
|
+
'audio/',
|
|
31
|
+
'application/epub+zip',
|
|
32
|
+
'application/gzip',
|
|
33
|
+
'application/java-archive',
|
|
34
|
+
'application/msword',
|
|
35
|
+
'application/octet-stream',
|
|
36
|
+
'application/pdf',
|
|
37
|
+
'application/rtf',
|
|
38
|
+
'application/vnd.ms-excel',
|
|
39
|
+
'application/vnd.ms-fontobject',
|
|
40
|
+
'application/vnd.ms-powerpoint',
|
|
41
|
+
'application/vnd.visio',
|
|
42
|
+
'application/x-shockwave-flash',
|
|
43
|
+
'application/x-tar',
|
|
44
|
+
'application/zip',
|
|
45
|
+
'font/',
|
|
46
|
+
'image/',
|
|
47
|
+
'model/',
|
|
48
|
+
'video/'
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
function addInjectedHeadersTo (request, headersToInject) {
|
|
52
|
+
Object.keys(headersToInject || {}).forEach(key => {
|
|
53
|
+
request.headers[key] = headersToInject[key];
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function toUrl (path, query, requestDetails) {
|
|
58
|
+
if (requestDetails) {
|
|
59
|
+
// Not passed in outOfProcess mode
|
|
60
|
+
return requestDetails.rawUrl;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const tail = queryString.stringify(query);
|
|
64
|
+
|
|
65
|
+
if (tail === '') {
|
|
66
|
+
return path;
|
|
67
|
+
}
|
|
68
|
+
return `${path}?${tail}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function hostnameFor (protocol, host, port) {
|
|
72
|
+
let result = host;
|
|
73
|
+
if ((protocol === 'http:' && port !== 80) || (protocol === 'https:' && port !== 443)) {
|
|
74
|
+
result += `:${port}`;
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function setProxyAgent (parts, options) {
|
|
80
|
+
if (process.env.http_proxy && parts.protocol === 'http:') {
|
|
81
|
+
options.agent = new HttpProxyAgent(process.env.http_proxy);
|
|
82
|
+
}
|
|
83
|
+
else if (process.env.https_proxy && parts.protocol === 'https:') {
|
|
84
|
+
options.agent = new HttpsProxyAgent(process.env.https_proxy);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function getProxyRequest (baseUrl, originalRequest, proxyOptions, requestDetails) {
|
|
89
|
+
/* eslint complexity: 0 */
|
|
90
|
+
const parts = new URL(baseUrl),
|
|
91
|
+
protocol = parts.protocol === 'https:' ? https : http,
|
|
92
|
+
defaultPort = parts.protocol === 'https:' ? 443 : 80,
|
|
93
|
+
options = {
|
|
94
|
+
method: originalRequest.method,
|
|
95
|
+
hostname: parts.hostname,
|
|
96
|
+
port: parts.port || defaultPort,
|
|
97
|
+
auth: parts.auth,
|
|
98
|
+
path: toUrl(originalRequest.path, originalRequest.query, requestDetails),
|
|
99
|
+
headers: helpers.clone(originalRequest.headers),
|
|
100
|
+
cert: proxyOptions.cert,
|
|
101
|
+
key: proxyOptions.key,
|
|
102
|
+
ciphers: proxyOptions.ciphers || 'ALL',
|
|
103
|
+
secureProtocol: proxyOptions.secureProtocol,
|
|
104
|
+
passphrase: proxyOptions.passphrase,
|
|
105
|
+
rejectUnauthorized: false
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// Only set host header if not overridden via injectHeaders (issue #388)
|
|
109
|
+
if (!proxyOptions.injectHeaders || !headersMap.of(proxyOptions.injectHeaders).has('host')) {
|
|
110
|
+
options.headers.host = hostnameFor(parts.protocol, parts.hostname, options.port);
|
|
111
|
+
}
|
|
112
|
+
setProxyAgent(parts, options);
|
|
113
|
+
|
|
114
|
+
// Avoid implicit chunked encoding (issue #132)
|
|
115
|
+
if (originalRequest.body &&
|
|
116
|
+
!headersMap.of(originalRequest.headers).has('Transfer-Encoding') &&
|
|
117
|
+
!headersMap.of(originalRequest.headers).has('Content-Length')) {
|
|
118
|
+
options.headers['Content-Length'] = Buffer.byteLength(originalRequest.body);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const proxiedRequest = protocol.request(options);
|
|
122
|
+
if (originalRequest.body) {
|
|
123
|
+
proxiedRequest.write(originalRequest.body);
|
|
124
|
+
}
|
|
125
|
+
return proxiedRequest;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function isBinaryResponse (headers) {
|
|
129
|
+
const contentEncoding = headers['content-encoding'] || '',
|
|
130
|
+
contentType = headers['content-type'] || '';
|
|
131
|
+
|
|
132
|
+
if (BINARY_CONTENT_ENCODINGS.some(binEncoding => contentEncoding.indexOf(binEncoding) >= 0)) {
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return BINARY_MIME_TYPES.some(typeName => contentType.indexOf(typeName) >= 0);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function maybeJSON (text) {
|
|
140
|
+
try {
|
|
141
|
+
return JSON.parse(text);
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return text;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function proxy (proxiedRequest) {
|
|
149
|
+
return new Promise(resolve => {
|
|
150
|
+
proxiedRequest.end();
|
|
151
|
+
|
|
152
|
+
proxiedRequest.once('response', response => {
|
|
153
|
+
const packets = [];
|
|
154
|
+
|
|
155
|
+
response.on('data', chunk => {
|
|
156
|
+
packets.push(chunk);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
response.on('end', () => {
|
|
160
|
+
const body = Buffer.concat(packets),
|
|
161
|
+
mode = isBinaryResponse(response.headers) ? 'binary' : 'text',
|
|
162
|
+
encoding = mode === 'binary' ? 'base64' : 'utf8',
|
|
163
|
+
stubResponse = {
|
|
164
|
+
statusCode: response.statusCode,
|
|
165
|
+
headers: headersMap.ofRaw(response.rawHeaders).all(),
|
|
166
|
+
body: maybeJSON(body.toString(encoding)),
|
|
167
|
+
_mode: mode
|
|
168
|
+
};
|
|
169
|
+
resolve(stubResponse);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Proxies an http/s request to a destination
|
|
177
|
+
* @memberOf module:models/http/httpProxy#
|
|
178
|
+
* @param {string} proxyDestination - The base URL to proxy to, without a path (e.g. http://www.google.com)
|
|
179
|
+
* @param {Object} originalRequest - The original http/s request to forward on to proxyDestination
|
|
180
|
+
* @param {Object} options - Proxy options
|
|
181
|
+
* @param {string} [options.cert] - The certificate, in case the destination requires mutual authentication
|
|
182
|
+
* @param {string} [options.key] - The private key, in case the destination requires mutual authentication
|
|
183
|
+
* @param {Object} [options.injectHeaders] - The headers to inject in the proxied request
|
|
184
|
+
* @param {Object} [options.passphrase] - The passphrase for the private key
|
|
185
|
+
* @param {Object} requestDetails - Additional details about the request not stored in the simplified JSON
|
|
186
|
+
* @returns {Object} - Promise resolving to the response
|
|
187
|
+
*/
|
|
188
|
+
function to (proxyDestination, originalRequest, options, requestDetails) {
|
|
189
|
+
|
|
190
|
+
addInjectedHeadersTo(originalRequest, options.injectHeaders);
|
|
191
|
+
|
|
192
|
+
function log (direction, what) {
|
|
193
|
+
logger.debug('Proxy %s %s %s %s %s',
|
|
194
|
+
originalRequest.requestFrom, direction, JSON.stringify(what), direction, proxyDestination);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return new Promise((resolve, reject) => {
|
|
198
|
+
let proxiedRequest;
|
|
199
|
+
try {
|
|
200
|
+
proxiedRequest = getProxyRequest(proxyDestination, originalRequest, options, requestDetails);
|
|
201
|
+
}
|
|
202
|
+
catch (e) {
|
|
203
|
+
reject(errors.InvalidProxyError(`Unable to connect to ${JSON.stringify(proxyDestination)}`));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
log('=>', originalRequest);
|
|
207
|
+
|
|
208
|
+
proxiedRequest.once('error', error => {
|
|
209
|
+
if (error.code === 'ENOTFOUND' || error.code === 'EAI_AGAIN') {
|
|
210
|
+
reject(errors.InvalidProxyError(`Cannot resolve ${JSON.stringify(proxyDestination)}`));
|
|
211
|
+
}
|
|
212
|
+
else if (error.code === 'ECONNREFUSED' || error.code === 'ECONNRESET') {
|
|
213
|
+
reject(errors.InvalidProxyError(`Unable to connect to ${JSON.stringify(proxyDestination)}`));
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
reject(error);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
proxy(proxiedRequest).then(response => {
|
|
221
|
+
log('<=', response);
|
|
222
|
+
resolve(response);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return { to };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
module.exports = { create };
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const queryString = require('querystring'),
|
|
4
|
+
zlib = require('zlib'),
|
|
5
|
+
helpers = require('../../util/helpers.js'),
|
|
6
|
+
headersMapModule = require('./headersMap.js');
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Transforms a node http/s request to a simplified mountebank http/s request
|
|
11
|
+
* that will be shown in the API
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
function transform (request) {
|
|
16
|
+
const url = new URL(request.url, 'http://localhost'),
|
|
17
|
+
search = url.search === '' ? '' : url.search.substr(1),
|
|
18
|
+
headersMap = headersMapModule.ofRaw(request.rawHeaders),
|
|
19
|
+
transformed = {
|
|
20
|
+
requestFrom: helpers.socketName(request.socket),
|
|
21
|
+
method: request.method,
|
|
22
|
+
path: url.pathname,
|
|
23
|
+
query: queryString.parse(search),
|
|
24
|
+
headers: headersMap.all(),
|
|
25
|
+
body: request.body,
|
|
26
|
+
ip: request.socket.remoteAddress
|
|
27
|
+
},
|
|
28
|
+
contentType = headersMap.get('Content-Type');
|
|
29
|
+
|
|
30
|
+
if (request.body && isUrlEncodedForm(contentType)) {
|
|
31
|
+
transformed.form = queryString.parse(request.body);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return transformed;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function isUrlEncodedForm (contentType) {
|
|
38
|
+
if (!contentType) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const index = contentType.indexOf(';'),
|
|
43
|
+
type = index !== -1 ? contentType.substr(0, index).trim() : contentType.trim();
|
|
44
|
+
|
|
45
|
+
return type === 'application/x-www-form-urlencoded';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Creates the API-friendly http/s request
|
|
50
|
+
* @param {Object} request - The raw http/s request
|
|
51
|
+
* @returns {Object} - Promise resolving to the simplified request
|
|
52
|
+
*/
|
|
53
|
+
function createFrom (request) {
|
|
54
|
+
return new Promise(resolve => {
|
|
55
|
+
const chunks = [];
|
|
56
|
+
request.on('data', chunk => { chunks.push(Buffer.from(chunk)); });
|
|
57
|
+
request.on('end', () => {
|
|
58
|
+
const headersMap = headersMapModule.ofRaw(request.rawHeaders),
|
|
59
|
+
contentEncoding = headersMap.get('Content-Encoding'),
|
|
60
|
+
buffer = Buffer.concat(chunks);
|
|
61
|
+
|
|
62
|
+
if (contentEncoding === 'gzip') {
|
|
63
|
+
try {
|
|
64
|
+
request.body = zlib.gunzipSync(buffer).toString();
|
|
65
|
+
}
|
|
66
|
+
catch (error) { /* do nothing */ }
|
|
67
|
+
}
|
|
68
|
+
else if (contentEncoding === 'br') {
|
|
69
|
+
try {
|
|
70
|
+
request.body = zlib.brotliDecompressSync(buffer).toString();
|
|
71
|
+
}
|
|
72
|
+
catch (error) { /* do nothing */ }
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
request.body = buffer.toString();
|
|
76
|
+
}
|
|
77
|
+
resolve(transform(request));
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
module.exports = { createFrom };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const http = require('http'),
|
|
4
|
+
baseHttpServer = require('./baseHttpServer.js');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Represents an http imposter
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
function createBaseServer () {
|
|
12
|
+
return {
|
|
13
|
+
metadata: {},
|
|
14
|
+
createNodeServer: http.createServer
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = baseHttpServer(createBaseServer);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const config = JSON.parse(process.argv[2]),
|
|
4
|
+
httpServer = require('./httpServer.js'),
|
|
5
|
+
httpProxy = require('./httpProxy.js'),
|
|
6
|
+
mbConnection = require('../mbConnection.js').create(config);
|
|
7
|
+
|
|
8
|
+
httpServer.create(config, mbConnection.logger(), mbConnection.getResponse).then(server => {
|
|
9
|
+
mbConnection.setPort(server.port);
|
|
10
|
+
mbConnection.setProxy(httpProxy.create(mbConnection.logger()));
|
|
11
|
+
|
|
12
|
+
const metadata = server.metadata;
|
|
13
|
+
metadata.port = server.port;
|
|
14
|
+
console.log(JSON.stringify(metadata));
|
|
15
|
+
}).catch(error => {
|
|
16
|
+
console.error(JSON.stringify(error));
|
|
17
|
+
process.exit(1); // eslint-disable-line no-process-exit
|
|
18
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
|
2
|
+
MIIDMTCCAhkCFC1be7uOugfu2q1NmVapw9+SQ0skMA0GCSqGSIb3DQEBCwUAMFUx
|
|
3
|
+
CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJUWDEPMA0GA1UEBwwGRGFsbGFzMRMwEQYD
|
|
4
|
+
VQQKDAptb3VudGViYW5rMRMwEQYDVQQDDAptb3VudGViYW5rMB4XDTIzMDYwNjAy
|
|
5
|
+
MjE0MVoXDTMzMDYwMzAyMjE0MVowVTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAlRY
|
|
6
|
+
MQ8wDQYDVQQHDAZEYWxsYXMxEzARBgNVBAoMCm1vdW50ZWJhbmsxEzARBgNVBAMM
|
|
7
|
+
Cm1vdW50ZWJhbmswggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDM+JWj
|
|
8
|
+
JFwtRE++8pTUs6GyWFLKMGSsZDUyJG0n9QivhJPE3fovecKzBdRZZUrAjHWP1Xxp
|
|
9
|
+
DLsqfy3FKzD9bEI2bIaa6Bv3QCUiSEheXteBlljltd4Rhup9yWnpwkBmDW4FIxV6
|
|
10
|
+
Hm5aOyRx41EGcq4Ir0rwpTMkHRrakCKkyVCuhcPbAeTtJn2jw9QIJB3G2IsWuziU
|
|
11
|
+
Fmg2TO90HPUpeoVVEgoO9B9DuCX3QmFBki3xTmm9HnvZ7C0tRZM6Y2aEWoPlzosE
|
|
12
|
+
7PcFjDmd9jmSXo3NED9+tDc4Xaoc9iPBpLMCqe6DaZBbqHxSFFS6spBJClqev09g
|
|
13
|
+
45PC3HqXl/Qxzj7VAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAH2pBSp0wtEmRiuY
|
|
14
|
+
9jhnB8C7pFDOUSUHIfOXKovH92ErqA0KGFvAhCShV71OW63C+7BfJTkt0Gq0pwfD
|
|
15
|
+
TdDQ1T9YNFU6NbNbMfSG9u0TMwajnawnNdO5Gk6LtXJFseWsn5PblCVrWD/qvLMF
|
|
16
|
+
DcllKYW8a8GBbttngt0bhGvh+DxPesjkHoeIWed6UCo3+Wdypes/vol3GL/e8Re5
|
|
17
|
+
uy3eKSBPR5dx/s6l/ZPDEksYV1ve5jv/lBKmRFKv5bFGiTrd5eUE25P2+n9DNGyY
|
|
18
|
+
yNrcLvc1WY/0sqGFmo2ci4ECZVDwx/GhKhdZw7IY8tfLfsKR7sAI6nDHO/GbPYYd
|
|
19
|
+
piqTpzQ=
|
|
20
|
+
-----END CERTIFICATE-----
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
-----BEGIN CERTIFICATE REQUEST-----
|
|
2
|
+
MIICmjCCAYICAQAwVTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAlRYMQ8wDQYDVQQH
|
|
3
|
+
DAZEYWxsYXMxEzARBgNVBAoMCm1vdW50ZWJhbmsxEzARBgNVBAMMCm1vdW50ZWJh
|
|
4
|
+
bmswggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDM+JWjJFwtRE++8pTU
|
|
5
|
+
s6GyWFLKMGSsZDUyJG0n9QivhJPE3fovecKzBdRZZUrAjHWP1XxpDLsqfy3FKzD9
|
|
6
|
+
bEI2bIaa6Bv3QCUiSEheXteBlljltd4Rhup9yWnpwkBmDW4FIxV6Hm5aOyRx41EG
|
|
7
|
+
cq4Ir0rwpTMkHRrakCKkyVCuhcPbAeTtJn2jw9QIJB3G2IsWuziUFmg2TO90HPUp
|
|
8
|
+
eoVVEgoO9B9DuCX3QmFBki3xTmm9HnvZ7C0tRZM6Y2aEWoPlzosE7PcFjDmd9jmS
|
|
9
|
+
Xo3NED9+tDc4Xaoc9iPBpLMCqe6DaZBbqHxSFFS6spBJClqev09g45PC3HqXl/Qx
|
|
10
|
+
zj7VAgMBAAGgADANBgkqhkiG9w0BAQsFAAOCAQEAUiKzIKhw1QfOeJ2HGmyC9cyh
|
|
11
|
+
r32YyUWVyKWEIdFedcsZcisGE+95UFeakP18LEnfnz8fMfxJHA/J/2rsLxvwx7UI
|
|
12
|
+
naZ9IW+RjSbpZvA3HKoiDubc29sBYX6QGdjMYQpxLzJhW20VpPx29TTVmR2Sg2rM
|
|
13
|
+
P5zhUtYi8YqE2P8bRGZTMsm585Y36dzUl4u5JKVNWBH6Z7q+339I0i+Y+EfoWDzb
|
|
14
|
+
HXztEH9OcyGbWD2sHJGLaEwYVCm6RmFRtCX9LCBjt2lPfmHwm4L+qSfUPRdsfixh
|
|
15
|
+
clLJfcE5i11Ydacp6Mlff3PXkM96Aw4t6CE9fudu1uaAMVoSq9vpmSp0tnrtXA==
|
|
16
|
+
-----END CERTIFICATE REQUEST-----
|