@api-client/core 0.3.2 → 0.3.5

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 (205) hide show
  1. package/build/browser.d.ts +1 -0
  2. package/build/browser.js +1 -0
  3. package/build/browser.js.map +1 -1
  4. package/build/index.d.ts +1 -1
  5. package/build/index.js +1 -1
  6. package/build/index.js.map +1 -1
  7. package/build/src/models/ErrorResponse.d.ts +5 -4
  8. package/build/src/models/ErrorResponse.js +18 -5
  9. package/build/src/models/ErrorResponse.js.map +1 -1
  10. package/build/src/models/SerializableError.d.ts +30 -0
  11. package/build/src/models/SerializableError.js +63 -0
  12. package/build/src/models/SerializableError.js.map +1 -0
  13. package/build/src/runtime/http-engine/ArcEngine.js +8 -4
  14. package/build/src/runtime/http-engine/ArcEngine.js.map +1 -1
  15. package/build/src/runtime/http-engine/HttpEngine.d.ts +3 -3
  16. package/build/src/runtime/http-engine/HttpEngine.js +3 -3
  17. package/build/src/runtime/http-engine/HttpEngine.js.map +1 -1
  18. package/build/src/runtime/http-engine/NodeEngine.js +9 -4
  19. package/build/src/runtime/http-engine/NodeEngine.js.map +1 -1
  20. package/build/src/runtime/http-engine/NodeEngineDirect.js +8 -2
  21. package/build/src/runtime/http-engine/NodeEngineDirect.js.map +1 -1
  22. package/build/src/runtime/node/ProjectRunner.d.ts +2 -2
  23. package/build/src/runtime/node/ProjectRunner.js +9 -2
  24. package/build/src/runtime/node/ProjectRunner.js.map +1 -1
  25. package/build/src/runtime/store/StoreSdk.js +10 -15
  26. package/build/src/runtime/store/StoreSdk.js.map +1 -1
  27. package/package.json +1 -1
  28. package/src/data/DataReader.ts +11 -0
  29. package/src/data/DataUtils.ts +108 -0
  30. package/src/data/JmesparthReader.ts +26 -0
  31. package/src/data/Json2Xml.ts +190 -0
  32. package/src/data/JsonReader.ts +41 -0
  33. package/src/data/PayloadPointer.ts +48 -0
  34. package/src/data/RequestDataExtractor.ts +133 -0
  35. package/src/data/UrlEncodedReader.ts +20 -0
  36. package/src/data/XmlReader.ts +103 -0
  37. package/src/events/BaseEvents.ts +259 -0
  38. package/src/events/CustomEvent.ts +27 -0
  39. package/src/events/EventTypes.ts +19 -0
  40. package/src/events/Events.ts +19 -0
  41. package/src/events/authorization/AuthorizationEventTypes.ts +22 -0
  42. package/src/events/authorization/AuthorizationEvents.ts +61 -0
  43. package/src/events/cookies/CookieEventTypes.ts +13 -0
  44. package/src/events/cookies/CookieEvents.ts +157 -0
  45. package/src/events/encryption/EncryptionEventTypes.ts +4 -0
  46. package/src/events/encryption/EncryptionEvents.ts +51 -0
  47. package/src/events/environment/EnvironmentEventTypes.ts +3 -0
  48. package/src/events/environment/EnvironmentEvents.ts +24 -0
  49. package/src/events/models/ClientCertificateEvents.ts +87 -0
  50. package/src/events/models/ModelEventTypes.ts +47 -0
  51. package/src/events/models/ModelEvents.ts +7 -0
  52. package/src/events/models/ProjectEvents.ts +331 -0
  53. package/src/events/process/ProcessEventTypes.ts +5 -0
  54. package/src/events/process/ProcessEvents.ts +76 -0
  55. package/src/events/readme.md +22 -0
  56. package/src/events/reporting/ReportingEventTypes.ts +3 -0
  57. package/src/events/reporting/ReportingEvents.ts +28 -0
  58. package/src/events/telemetry/TelemetryEventTypes.ts +10 -0
  59. package/src/events/telemetry/TelemetryEvents.ts +156 -0
  60. package/src/lib/cookies/Cookie.ts +312 -0
  61. package/src/lib/cookies/Cookies.ts +326 -0
  62. package/src/lib/cookies/Utils.ts +168 -0
  63. package/src/lib/headers/Headers.ts +219 -0
  64. package/src/lib/logging/DefaultLogger.ts +19 -0
  65. package/src/lib/logging/DummyLogger.ts +21 -0
  66. package/src/lib/logging/Logger.ts +16 -0
  67. package/src/lib/transformers/PayloadSerializer.ts +332 -0
  68. package/src/lib/transformers/Utils.ts +18 -0
  69. package/src/lib/uuid.ts +40 -0
  70. package/src/mocking/LegacyInterfaces.ts +52 -0
  71. package/src/mocking/LegacyMock.ts +37 -0
  72. package/src/mocking/legacy/Authorization.ts +39 -0
  73. package/src/mocking/legacy/Certificates.ts +145 -0
  74. package/src/mocking/legacy/Cookies.ts +51 -0
  75. package/src/mocking/legacy/HostRules.ts +43 -0
  76. package/src/mocking/legacy/Http.ts +236 -0
  77. package/src/mocking/legacy/HttpResponse.ts +106 -0
  78. package/src/mocking/legacy/RestApi.ts +68 -0
  79. package/src/mocking/legacy/Urls.ts +44 -0
  80. package/src/mocking/legacy/Variables.ts +53 -0
  81. package/src/models/ArcResponse.ts +166 -0
  82. package/src/models/Authorization.ts +481 -0
  83. package/src/models/AuthorizationData.ts +60 -0
  84. package/src/models/Backend.ts +107 -0
  85. package/src/models/ClientCertificate.ts +68 -0
  86. package/src/models/Environment.ts +279 -0
  87. package/src/models/ErrorResponse.ts +113 -0
  88. package/src/models/HistoryIndex.ts +76 -0
  89. package/src/models/HistoryRequest.ts +28 -0
  90. package/src/models/HostRule.ts +163 -0
  91. package/src/models/HttpCookie.ts +285 -0
  92. package/src/models/HttpProject.ts +1294 -0
  93. package/src/models/HttpProjectListItem.ts +23 -0
  94. package/src/models/HttpRequest.ts +124 -0
  95. package/src/models/HttpResponse.ts +143 -0
  96. package/src/models/License.ts +113 -0
  97. package/src/models/ProjectDefinitionProperty.ts +40 -0
  98. package/src/models/ProjectFolder.ts +439 -0
  99. package/src/models/ProjectItem.ts +135 -0
  100. package/src/models/ProjectParent.ts +113 -0
  101. package/src/models/ProjectRequest.ts +277 -0
  102. package/src/models/ProjectSchema.ts +202 -0
  103. package/src/models/Property.ts +423 -0
  104. package/src/models/Provider.ts +98 -0
  105. package/src/models/README.md +20 -0
  106. package/src/models/Request.ts +452 -0
  107. package/src/models/RequestActions.ts +163 -0
  108. package/src/models/RequestAuthorization.ts +115 -0
  109. package/src/models/RequestConfig.ts +317 -0
  110. package/src/models/RequestLog.ts +159 -0
  111. package/src/models/RequestTime.ts +108 -0
  112. package/src/models/RequestUiMeta.ts +258 -0
  113. package/src/models/RequestsSize.ts +65 -0
  114. package/src/models/ResponseAuthorization.ts +104 -0
  115. package/src/models/ResponseRedirect.ts +158 -0
  116. package/src/models/RevisionInfo.ts +37 -0
  117. package/src/models/SentRequest.ts +125 -0
  118. package/src/models/SerializableError.ts +80 -0
  119. package/src/models/SerializablePayload.ts +68 -0
  120. package/src/models/Server.ts +153 -0
  121. package/src/models/Thing.ts +110 -0
  122. package/src/models/Url.ts +90 -0
  123. package/src/models/User.ts +120 -0
  124. package/src/models/WebApi.ts +234 -0
  125. package/src/models/WebApiIndex.ts +122 -0
  126. package/src/models/Workspace.ts +182 -0
  127. package/src/models/actions/Action.ts +213 -0
  128. package/src/models/actions/ActionView.ts +40 -0
  129. package/src/models/actions/Condition.ts +207 -0
  130. package/src/models/actions/ConditionView.ts +42 -0
  131. package/src/models/actions/Enums.ts +29 -0
  132. package/src/models/actions/RunnableAction.ts +144 -0
  133. package/src/models/actions/runnable/DeleteCookieAction.ts +113 -0
  134. package/src/models/actions/runnable/Runnable.ts +9 -0
  135. package/src/models/actions/runnable/SetCookieAction.ts +216 -0
  136. package/src/models/actions/runnable/SetVariableAction.ts +81 -0
  137. package/src/models/legacy/DataExport.ts +172 -0
  138. package/src/models/legacy/Normalizer.ts +110 -0
  139. package/src/models/legacy/actions/Actions.ts +269 -0
  140. package/src/models/legacy/authorization/Authorization.ts +572 -0
  141. package/src/models/legacy/models/ApiTypes.ts +202 -0
  142. package/src/models/legacy/models/ArcLegacyProject.ts +39 -0
  143. package/src/models/legacy/models/AuthData.ts +17 -0
  144. package/src/models/legacy/models/ClientCertificate.ts +95 -0
  145. package/src/models/legacy/models/Cookies.ts +52 -0
  146. package/src/models/legacy/models/HostRule.ts +35 -0
  147. package/src/models/legacy/models/RestApi.ts +49 -0
  148. package/src/models/legacy/models/UrlHistory.ts +37 -0
  149. package/src/models/legacy/models/Variable.ts +43 -0
  150. package/src/models/legacy/models/base.d.ts +95 -0
  151. package/src/models/legacy/request/ArcRequest.ts +405 -0
  152. package/src/models/legacy/request/ArcResponse.ts +177 -0
  153. package/src/models/legacy/request/HistoryData.ts +47 -0
  154. package/src/models/legacy/request/Legacy.ts +45 -0
  155. package/src/models/legacy/request/RequestBody.ts +87 -0
  156. package/src/models/transformers/ArcDexieTransformer.ts +323 -0
  157. package/src/models/transformers/ArcLegacyNormalizer.ts +85 -0
  158. package/src/models/transformers/ArcLegacyTransformer.ts +200 -0
  159. package/src/models/transformers/ArcPouchTransformer.ts +184 -0
  160. package/src/models/transformers/BaseTransformer.ts +116 -0
  161. package/src/models/transformers/ImportUtils.ts +141 -0
  162. package/src/models/transformers/LegacyDataExportToApiProject.ts +76 -0
  163. package/src/models/transformers/LegacyExportProcessor.ts +252 -0
  164. package/src/models/transformers/PostmanBackupTransformer.ts +306 -0
  165. package/src/models/transformers/PostmanDataTransformer.ts +50 -0
  166. package/src/models/transformers/PostmanTransformer.ts +106 -0
  167. package/src/models/transformers/PostmanV21Transformer.ts +311 -0
  168. package/src/models/transformers/PostmanV2Transformer.ts +308 -0
  169. package/src/models/transformers/har.ts +865 -0
  170. package/src/runtime/actions/ActionRunner.ts +83 -0
  171. package/src/runtime/actions/ConditionRunner.ts +194 -0
  172. package/src/runtime/actions/RunnableCondition.ts +57 -0
  173. package/src/runtime/actions/runnable/ActionRunnable.ts +19 -0
  174. package/src/runtime/actions/runnable/DeleteCookieRunnable.ts +39 -0
  175. package/src/runtime/actions/runnable/SetCookieRunnable.ts +92 -0
  176. package/src/runtime/actions/runnable/SetVariableRunnable.ts +53 -0
  177. package/src/runtime/http-engine/ArcEngine.ts +1068 -0
  178. package/src/runtime/http-engine/FormData.ts +85 -0
  179. package/src/runtime/http-engine/HttpEngine.ts +874 -0
  180. package/src/runtime/http-engine/HttpErrorCodes.ts +270 -0
  181. package/src/runtime/http-engine/NodeEngine.ts +792 -0
  182. package/src/runtime/http-engine/NodeEngineDirect.ts +482 -0
  183. package/src/runtime/http-engine/PayloadSupport.ts +84 -0
  184. package/src/runtime/http-engine/RequestUtils.ts +164 -0
  185. package/src/runtime/http-engine/ntlm/Des.ts +345 -0
  186. package/src/runtime/http-engine/ntlm/MD4.ts +135 -0
  187. package/src/runtime/http-engine/ntlm/NtlmAuth.ts +186 -0
  188. package/src/runtime/http-engine/ntlm/NtlmMessage.ts +57 -0
  189. package/src/runtime/modules/BasicAuthCache.ts +133 -0
  190. package/src/runtime/modules/ExecutionResponse.ts +4 -0
  191. package/src/runtime/modules/ModulesRegistry.ts +136 -0
  192. package/src/runtime/modules/RequestAuthorization.ts +110 -0
  193. package/src/runtime/modules/RequestCookies.ts +145 -0
  194. package/src/runtime/node/ProjectRunner.ts +281 -0
  195. package/src/runtime/node/RequestFactory.ts +422 -0
  196. package/src/runtime/node/VariablesStore.ts +25 -0
  197. package/src/runtime/store/StoreSdk.ts +838 -0
  198. package/src/runtime/variables/Cache.ts +53 -0
  199. package/src/runtime/variables/EvalFunctions.ts +132 -0
  200. package/src/runtime/variables/ProjectVariables.ts +6 -0
  201. package/src/runtime/variables/VariablesProcessor.ts +543 -0
  202. package/src/runtime/variables/VariablesTokenizer.ts +55 -0
  203. package/build/src/runtime/http-engine/Errors.d.ts +0 -10
  204. package/build/src/runtime/http-engine/Errors.js +0 -14
  205. package/build/src/runtime/http-engine/Errors.js.map +0 -1
@@ -0,0 +1,1068 @@
1
+ import net from 'net';
2
+ import tls from 'tls';
3
+ import http from 'http';
4
+ import https from 'https';
5
+ import { HttpEngine, HttpEngineOptions, HeadersReceivedDetail } from './HttpEngine.js';
6
+ import { IRequestLog } from 'src/models/RequestLog.js';
7
+ import { IHttpRequest } from '../../models/HttpRequest.js';
8
+ import { ArcResponse } from '../../models/ArcResponse.js';
9
+ import { SerializableError } from '../../models/SerializableError.js';
10
+ import { Headers } from '../../lib/headers/Headers.js';
11
+ import { PayloadSupport } from './PayloadSupport.js';
12
+ import { addContentLength, getPort } from './RequestUtils.js';
13
+ import { INtlmAuthorization } from '../../models/Authorization.js';
14
+ import { NtlmAuth, INtlmAuthConfig } from './ntlm/NtlmAuth.js';
15
+ import { PayloadSerializer } from '../../lib/transformers/PayloadSerializer.js';
16
+ import { ResponseRedirect } from '../../models/ResponseRedirect.js';
17
+
18
+ const nlBuffer = Buffer.from([13, 10]);
19
+ const nlNlBuffer = Buffer.from([13, 10, 13, 10]);
20
+
21
+ export enum RequestState {
22
+ Status,
23
+ Headers,
24
+ Body,
25
+ Done,
26
+ }
27
+
28
+ interface ResponseInfo {
29
+ contentLength?: number;
30
+ chunked: boolean;
31
+ body?: Buffer;
32
+ chunk?: Buffer;
33
+ chunkSize?: number;
34
+ }
35
+
36
+ /**
37
+ * ARC's HTTP engine.
38
+ * An HTTP 1.1 engine working directly on the socket. It communicates with the remote machine and
39
+ * collects stats about the request and response.
40
+ */
41
+ export class ArcEngine extends HttpEngine {
42
+ state = RequestState.Status;
43
+ rawHeaders?: Buffer;
44
+ _hostTestReg = /^\s*host\s*:/im;
45
+
46
+ responseInfo: ResponseInfo;
47
+ hasProxy: boolean;
48
+ isProxyTunnel: boolean;
49
+ isProxySsl: boolean;
50
+
51
+ constructor(request: IHttpRequest, opts: HttpEngineOptions = {}) {
52
+ super(request, opts);
53
+ this.responseInfo = {
54
+ chunked: false,
55
+ };
56
+ this.hasProxy = !!this.opts.proxy;
57
+ this.isProxyTunnel = this.hasProxy && this.request.url.startsWith('https:');
58
+ this.isProxySsl = !!this.opts.proxy && this.opts.proxy.startsWith('https:');
59
+ }
60
+
61
+ /**
62
+ * Sends the request
63
+ */
64
+ async send(): Promise<IRequestLog> {
65
+ const promise = this.wrapExecution();
66
+ this.sendRequest();
67
+ return promise;
68
+ }
69
+
70
+ private async sendRequest(): Promise<void> {
71
+ try {
72
+ if (this.hasProxy) {
73
+ await this.connectProxy();
74
+ } else {
75
+ await this.connect();
76
+ }
77
+ if (!this.socket) {
78
+ return;
79
+ }
80
+ const message = await this.prepareMessage();
81
+ await this.writeMessage(message);
82
+ } catch (cause) {
83
+ const e = cause as any;
84
+ const err = new SerializableError(e.message, { cause: e });
85
+ if (e.code || e.code === 0) {
86
+ err.code = e.code as string;
87
+ }
88
+ this.abort();
89
+ this._errorRequest({
90
+ message: err.message,
91
+ code: err.code,
92
+ });
93
+ throw cause;
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Cleans the state after finished.
99
+ */
100
+ _cleanUp(): void {
101
+ super._cleanUp();
102
+ this.state = RequestState.Status;
103
+ this.rawHeaders = undefined;
104
+ this.responseInfo = {
105
+ chunked: false,
106
+ };
107
+ }
108
+
109
+ /**
110
+ * Cleans up the state for redirect.
111
+ */
112
+ _cleanUpRedirect(): void {
113
+ super._cleanUpRedirect();
114
+ this.state = RequestState.Status;
115
+ this.rawHeaders = undefined;
116
+ this.responseInfo = {
117
+ chunked: false,
118
+ };
119
+ }
120
+
121
+ /**
122
+ * Prepares an HTTP message from ARC's request object.
123
+ *
124
+ * @returns Resolved promise to an `ArrayBuffer`.
125
+ */
126
+ async prepareMessage(): Promise<Buffer> {
127
+ let payload = this.request.payload;
128
+ if (['get', 'head'].includes(this.request.method.toLowerCase())) {
129
+ payload = undefined;
130
+ }
131
+ const headers = new Headers(this.request.headers);
132
+ this.prepareHeaders(headers);
133
+ const auth = this.hasProxy && !this.isProxyTunnel ? this._proxyAuthHeader() : undefined;
134
+ if (auth) {
135
+ headers.set('proxy-authorization', auth);
136
+ }
137
+ let buffer: Buffer | undefined;
138
+ if (payload) {
139
+ buffer = await PayloadSupport.payloadToBuffer(payload, headers);
140
+ if (buffer) {
141
+ addContentLength(this.request.method || 'GET', buffer, headers);
142
+ }
143
+ }
144
+
145
+ this._handleAuthorization(headers);
146
+ this.sentRequest.headers = headers.toString();
147
+ const message = this._prepareMessage(headers, buffer);
148
+ if (this.auth) {
149
+ // This restores altered by authorization original headers
150
+ // so it can be safe to use when redirecting
151
+ if (this.auth.headers) {
152
+ this.request.headers = this.auth.headers;
153
+ delete this.auth.headers;
154
+ }
155
+ }
156
+ return message;
157
+ }
158
+
159
+ /**
160
+ * Sends a data to a socket.
161
+ *
162
+ * @param buffer HTTP message to send
163
+ */
164
+ writeMessage(buffer: Buffer): Promise<void> {
165
+ this.logger.debug(`Writing the message to the socket...`);
166
+ let msg = buffer.toString();
167
+ const limit = this.opts.sentMessageLimit;
168
+ if (limit && limit > 0 && msg.length >= limit) {
169
+ msg = msg.substr(0, limit);
170
+ msg += ' ...';
171
+ }
172
+ this.sentRequest.httpMessage = msg;
173
+ const startTime = Date.now();
174
+ this.stats.startTime = startTime;
175
+ this.sentRequest.startTime = startTime;
176
+
177
+ this.stats.messageStart = Date.now();
178
+ return new Promise((resolve) => {
179
+ this.socket?.write(buffer, () => {
180
+ this.logger.debug(`The message has been sent.`);
181
+ this.stats.sentTime = Date.now();
182
+ try {
183
+ this.emit('loadstart');
184
+ } catch (_) {
185
+ //
186
+ }
187
+ resolve();
188
+ });
189
+ });
190
+ }
191
+
192
+ /**
193
+ * Connects to a server and sends the message.
194
+ *
195
+ * @returns Promise resolved when socket is connected.
196
+ */
197
+ async connect(): Promise<net.Socket> {
198
+ const port = getPort(this.uri.port, this.uri.protocol);
199
+ const host = this.uri.hostname;
200
+ let socket;
201
+ if (port === 443 || this.uri.protocol === 'https:') {
202
+ socket = await this._connectTls(port, host);
203
+ } else {
204
+ socket = await this._connect(port, host);
205
+ }
206
+ const { timeout } = this;
207
+ if (timeout > 0) {
208
+ socket.setTimeout(timeout);
209
+ }
210
+ this.socket = socket;
211
+ this._addSocketListeners(socket);
212
+ socket.resume();
213
+ return socket;
214
+ }
215
+
216
+ /**
217
+ * Connects to a server and writes a message using insecure connection.
218
+ *
219
+ * @param port A port number to connect to.
220
+ * @param host A host name to connect to
221
+ * @returns A promise resolved when the message was sent to a server
222
+ */
223
+ _connect(port: number, host: string): Promise<net.Socket> {
224
+ this.logger.debug('Opening an HTTP connection...');
225
+ return new Promise((resolve, reject) => {
226
+ this.stats.connectionTime = Date.now();
227
+ const isIp = net.isIP(host);
228
+ if (isIp) {
229
+ this.stats.lookupTime = Date.now();
230
+ }
231
+ const client = net.createConnection(port, host, () => {
232
+ this.logger.debug('HTTP connection established.');
233
+ this.stats.connectedTime = Date.now();
234
+ resolve(client);
235
+ });
236
+ client.pause();
237
+ if (!isIp) {
238
+ client.once('lookup', () => {
239
+ this.stats.lookupTime = Date.now();
240
+ });
241
+ }
242
+ client.once('error', (err) => reject(err));
243
+ });
244
+ }
245
+
246
+ /**
247
+ * Connects to a server and writes a message using secured connection.
248
+ *
249
+ * @param port A port number to connect to.
250
+ * @param host A host name to connect to
251
+ * @returns A promise resolved when the message was sent to a server
252
+ */
253
+ _connectTls(port: number, host: string): Promise<tls.TLSSocket> {
254
+ this.logger.debug('Opening an SSL connection...');
255
+ const { opts } = this;
256
+ const options: tls.ConnectionOptions = {};
257
+ const isIp = net.isIP(host);
258
+ if (!isIp) {
259
+ options.servername = host;
260
+ }
261
+ if (opts.validateCertificates) {
262
+ options.checkServerIdentity = this._checkServerIdentity.bind(this);
263
+ } else {
264
+ options.rejectUnauthorized = false;
265
+ // target.requestOCSP = false;
266
+ }
267
+ const certs = this.opts.certificates;
268
+ if (Array.isArray(certs)) {
269
+ certs.forEach(cert => this._addClientCertificate(cert, options));
270
+ }
271
+ return new Promise((resolve, reject) => {
272
+ const time = Date.now();
273
+ this.stats.connectionTime = time;
274
+ if (isIp) {
275
+ this.stats.lookupTime = time;
276
+ }
277
+ const client = tls.connect(port, host, options, () => {
278
+ this.logger.debug('SSL connection established.');
279
+ const connectTime = Date.now();
280
+ this.stats.connectedTime = connectTime;
281
+ this.stats.secureStartTime = connectTime;
282
+ resolve(client);
283
+ });
284
+ client.pause();
285
+ client.once('error', (e) => reject(e));
286
+ if (!isIp) {
287
+ client.once('lookup', () => {
288
+ this.stats.lookupTime = Date.now();
289
+ });
290
+ }
291
+ client.once('secureConnect', () => {
292
+ this.stats.secureConnectedTime = Date.now();
293
+ });
294
+ });
295
+ }
296
+
297
+ /**
298
+ * Prepares a full HTTP message body
299
+ *
300
+ * @param httpHeaders The list ogf headers to append.
301
+ * @param buffer The buffer with the HTTP message
302
+ * @returns The Buffer of the HTTP message
303
+ */
304
+ _prepareMessage(httpHeaders: Headers, buffer?: Buffer): Buffer {
305
+ this.logger.debug('Preparing an HTTP message...');
306
+ const headers = [];
307
+ // const search = this.uri.search;
308
+ // let path = this.uri.pathname;
309
+ // if (search) {
310
+ // path += search;
311
+ // }
312
+ // headers.push(`${this.arcRequest.method} ${path} HTTP/1.1`);
313
+ const status = this._createHttpStatus();
314
+ this.logger.debug(`Created message status: ${status}`);
315
+ headers.push(status);
316
+ if (this._hostRequired()) {
317
+ this.logger.debug(`Adding the "host" header: ${this.hostHeader}`);
318
+ headers.push(`Host: ${this.hostHeader}`);
319
+ }
320
+ let str = headers.join('\r\n');
321
+ const addHeaders = httpHeaders.toString();
322
+ if (addHeaders) {
323
+ this.logger.debug(`Adding headers to the request...`);
324
+ str += '\r\n';
325
+ str += PayloadSupport.normalizeString(addHeaders);
326
+ }
327
+ const startBuffer = Buffer.from(str, 'utf8');
328
+ const endBuffer = Buffer.from(new Uint8Array([13, 10, 13, 10]));
329
+ let body;
330
+ let sum = startBuffer.length + endBuffer.length;
331
+ if (buffer) {
332
+ sum += buffer.length;
333
+ body = Buffer.concat([startBuffer, endBuffer, buffer], sum);
334
+ } else {
335
+ body = Buffer.concat([startBuffer, endBuffer], sum);
336
+ }
337
+ this.logger.debug(`The message is ready.`);
338
+ return body;
339
+ }
340
+
341
+ /**
342
+ * Creates an HTTP status line for the message.
343
+ * For proxy connections it, depending whether target is SSL or not, sets the path
344
+ * as the full URL or just the authority.
345
+ * @returns The generates status message.
346
+ */
347
+ _createHttpStatus(): string {
348
+ const { request, uri, hasProxy, isProxyTunnel } = this;
349
+ const parts = [request.method];
350
+ if (hasProxy && !isProxyTunnel) {
351
+ // if (isProxyTunnel) {
352
+ // // when a tunnel then the target is over SSL so the default port is 443.
353
+ // parts.push(`${uri.hostname}:${uri.port || 443}`);
354
+ // } else {
355
+ // parts.push(arcRequest.url);
356
+ // }
357
+ parts.push(request.url);
358
+ } else {
359
+ let path = uri.pathname;
360
+ if (uri.search) {
361
+ path += uri.search;
362
+ }
363
+ parts.push(path);
364
+ }
365
+
366
+ parts.push('HTTP/1.1');
367
+ return parts.join(' ');
368
+ }
369
+
370
+ /**
371
+ * Tests if current connection is required to add `host` header.
372
+ * It returns `false` only if `host` header has been already provided.
373
+ *
374
+ * @returns True when the `host` header should be added to the headers list.
375
+ */
376
+ _hostRequired(): boolean {
377
+ const headers = this.request.headers;
378
+ if (typeof headers !== 'string') {
379
+ return true;
380
+ }
381
+ return !this._hostTestReg.test(headers);
382
+ }
383
+
384
+ /**
385
+ * Alters authorization header depending on the `auth` object
386
+ * @param headers A headers object where to append headers when needed
387
+ */
388
+ _handleAuthorization(headers: Headers): void {
389
+ const { authorization } = this.opts;
390
+ const enabled = Array.isArray(authorization) ? authorization.filter((i) => i.enabled) : [];
391
+ if (!enabled.length) {
392
+ return;
393
+ }
394
+ const ntlm = enabled.find((i) => i.type === 'ntlm');
395
+ if (ntlm) {
396
+ this._authorizeNtlm(ntlm.config as INtlmAuthorization, headers);
397
+ }
398
+ }
399
+
400
+ /**
401
+ * Authorize the request with NTLM
402
+ * @param authData Credentials to use
403
+ * @param headers A headers object where to append headers if needed
404
+ */
405
+ _authorizeNtlm(authData: INtlmAuthorization, headers: Headers): void {
406
+ const init = { ...authData, url: this.request.url } as INtlmAuthConfig;
407
+ const auth = new NtlmAuth(init);
408
+ if (!this.auth) {
409
+ this.auth = {
410
+ method: 'ntlm',
411
+ state: 0,
412
+ headers: headers.toString(),
413
+ };
414
+ const msg = auth.createMessage1(this.uri.host);
415
+ headers.set('Authorization', `NTLM ${msg.toBase64()}`);
416
+ headers.set('Connection', 'keep-alive');
417
+ } else if (this.auth && this.auth.state === 1) {
418
+ const msg = auth.createMessage3(this.auth.challengeHeader!, this.uri.host);
419
+ this.auth.state = 2;
420
+ headers.set('Authorization', `NTLM ${msg.toBase64()}`);
421
+ }
422
+ }
423
+
424
+ /**
425
+ * Add event listeners to existing socket.
426
+ * @param socket An instance of the socket.
427
+ * @return The same socket. Used for chaining.
428
+ */
429
+ _addSocketListeners(socket: net.Socket): net.Socket {
430
+ let received = false;
431
+ socket.on('data', (data) => {
432
+ this.logger.debug(`Received server data from the socket...`);
433
+ if (!received) {
434
+ const now = Date.now();
435
+ this.stats.firstReceiveTime = now;
436
+ this.emit('firstbyte');
437
+ received = true;
438
+ }
439
+ data = Buffer.from(data);
440
+ try {
441
+ this._processSocketMessage(data);
442
+ } catch (e) {
443
+ const err = e as Error;
444
+ this._errorRequest({
445
+ message: err.message || 'Unknown error occurred',
446
+ });
447
+ return;
448
+ }
449
+ });
450
+ socket.once('timeout', () => {
451
+ this.state = RequestState.Done;
452
+ this._errorRequest(new Error('Connection timeout.'));
453
+ socket.destroy();
454
+ });
455
+ socket.on('end', () => {
456
+ this.logger.debug(`Server connection ended.`);
457
+ socket.removeAllListeners('timeout');
458
+ socket.removeAllListeners('error');
459
+ const endTime = Date.now();
460
+ this.stats.lastReceivedTime = endTime;
461
+ this.sentRequest.endTime = endTime;
462
+ if (this.state !== RequestState.Done) {
463
+ if (!this.currentResponse) {
464
+ this.logger.error(`Connection closed without receiving any data.`);
465
+ // The parser haven't found the end of message so there was no message!
466
+ const e = new SerializableError('Connection closed without receiving any data', 100);
467
+ this._errorRequest(e);
468
+ } else {
469
+ // There is an issue with the response. Size mismatch? Anyway,
470
+ // it tries to create a response from current data.
471
+ this.emit('loadend');
472
+ this._publishResponse();
473
+ }
474
+ }
475
+ });
476
+ socket.once('error', (err) => {
477
+ socket.removeAllListeners('timeout');
478
+ this._errorRequest(err);
479
+ });
480
+ return socket;
481
+ }
482
+
483
+ /**
484
+ * Processes response message chunk
485
+ * @param buffer Message buffer
486
+ */
487
+ _processResponse(buffer: Buffer): void {
488
+ this._processSocketMessage(buffer);
489
+ this._reportResponse();
490
+ }
491
+
492
+ /**
493
+ * Reports response after processing it.
494
+ */
495
+ _reportResponse(): void {
496
+ this._clearSocketEventListeners();
497
+ const { aborted, currentResponse } = this;
498
+ if (aborted || !currentResponse) {
499
+ return;
500
+ }
501
+ const { status } = currentResponse;
502
+
503
+ const endTime = Date.now();
504
+ this.stats.lastReceivedTime = endTime;
505
+ this.sentRequest.endTime = endTime;
506
+
507
+ if (status >= 300 && status < 400) {
508
+ if (this.followRedirects && this._reportRedirect(status)) {
509
+ this.closeClient();
510
+ return;
511
+ }
512
+ } else if (status === 401 && this.auth) {
513
+ switch (this.auth.method) {
514
+ case 'ntlm': this.handleNtlmResponse(); return;
515
+ }
516
+ }
517
+ this.closeClient();
518
+ this.emit('loadend');
519
+ this._publishResponse();
520
+ }
521
+
522
+ /**
523
+ * Generate response object and publish it to the listeners.
524
+ */
525
+ _publishResponse(): Promise<void> {
526
+ this.state = RequestState.Done;
527
+ if (!this._rawBody) {
528
+ if (this.responseInfo.body) {
529
+ this._rawBody = this.responseInfo.body;
530
+ } else if (this.responseInfo.chunk) {
531
+ this._rawBody = this.responseInfo.chunk;
532
+ }
533
+ }
534
+ return super._publishResponse();
535
+ }
536
+
537
+ /**
538
+ * @param location The redirect location.
539
+ * @return Redirect response object
540
+ */
541
+ async _createRedirectResponse(location: string): Promise<ResponseRedirect> {
542
+ const { currentResponse = new ArcResponse() } = this;
543
+ this.currentResponse = currentResponse;
544
+ if (!this.currentResponse.payload) {
545
+ if (this._rawBody) {
546
+ this.currentResponse.payload = PayloadSerializer.stringifyBuffer(this._rawBody);
547
+ } else if (this.responseInfo.body) {
548
+ this.currentResponse.payload = PayloadSerializer.stringifyBuffer(this.responseInfo.body);
549
+ } else if (this.responseInfo.chunk) {
550
+ this.currentResponse.payload = PayloadSerializer.stringifyBuffer(this.responseInfo.chunk);
551
+ }
552
+ }
553
+ return super._createRedirectResponse(location);
554
+ }
555
+
556
+ /**
557
+ * closes the connection, if any
558
+ */
559
+ closeClient(): void {
560
+ if (this.socket && !this.socket.destroyed) {
561
+ this.socket.destroy();
562
+ }
563
+ }
564
+
565
+ /**
566
+ * Handles the response with NTLM authorization
567
+ */
568
+ handleNtlmResponse(): void {
569
+ const { auth } = this;
570
+ if (!auth) {
571
+ throw new Error('No auth data.')
572
+ }
573
+ if (auth.state === 0) {
574
+ if (this.currentHeaders.has('www-authenticate')) {
575
+ auth.state = 1;
576
+ auth.challengeHeader = this.currentHeaders.get('www-authenticate');
577
+ this._cleanUpRedirect();
578
+ this.prepareMessage().then((message) => this.writeMessage(message));
579
+ return;
580
+ }
581
+ }
582
+ delete this.auth;
583
+ this.emit('loadend');
584
+ this._publishResponse();
585
+ }
586
+
587
+ /**
588
+ * Process received message.
589
+ *
590
+ * @param data Received message.
591
+ */
592
+ _processSocketMessage(data: Buffer): void {
593
+ if (this.aborted) {
594
+ return;
595
+ }
596
+ if (this.state === RequestState.Done) {
597
+ return;
598
+ }
599
+ let remaining: Buffer | undefined = data;
600
+ if (this.state === RequestState.Status) {
601
+ remaining = this._processStatus(remaining);
602
+ if (!remaining) {
603
+ return;
604
+ }
605
+ }
606
+ if (this.state === RequestState.Headers) {
607
+ remaining = this._processHeaders(remaining);
608
+ if (!remaining) {
609
+ return;
610
+ }
611
+ }
612
+ if (this.state === RequestState.Body) {
613
+ this._processBody(remaining);
614
+ return;
615
+ }
616
+ }
617
+
618
+ /**
619
+ * Read status line from the response.
620
+ * This function will set `status` and `statusText` fields
621
+ * and then will set `state` to HEADERS.
622
+ *
623
+ * @param data The received data
624
+ */
625
+ _processStatus(data?: Buffer): Buffer | undefined {
626
+ if (this.aborted) {
627
+ return;
628
+ }
629
+ const response = ArcResponse.fromValues(0);
630
+ response.loadingTime = 0;
631
+ this.currentResponse = response;
632
+ if (!data) {
633
+ return;
634
+ }
635
+
636
+ this.logger.info('Processing status');
637
+ const index = data.indexOf(nlBuffer);
638
+ let statusLine = data.slice(0, index).toString();
639
+ data = data.slice(index + 2);
640
+ statusLine = statusLine.replace(/HTTP\/\d(\.\d)?\s/, '');
641
+ const delimiterPos = statusLine.indexOf(' ');
642
+ let status;
643
+ let msg = '';
644
+ if (delimiterPos === -1) {
645
+ status = statusLine;
646
+ } else {
647
+ status = statusLine.substr(0, delimiterPos);
648
+ msg = statusLine.substr(delimiterPos + 1);
649
+ }
650
+ let typedStatus = Number(status);
651
+ if (Number.isNaN(typedStatus)) {
652
+ typedStatus = 0;
653
+ }
654
+ if (msg && msg.indexOf('\n') !== -1) {
655
+ msg = msg.split('\n')[0];
656
+ }
657
+ this.currentResponse!.status = typedStatus;
658
+ this.currentResponse!.statusText = msg;
659
+ this.logger.info('Received status', typedStatus, msg);
660
+ this.state = RequestState.Headers;
661
+ return data;
662
+ }
663
+
664
+ /**
665
+ * Read headers from the received data.
666
+ *
667
+ * @param data Received data
668
+ * @returns Remaining data in the buffer.
669
+ */
670
+ _processHeaders(data?: Buffer): Buffer | undefined {
671
+ if (this.aborted) {
672
+ return;
673
+ }
674
+ if (!data) {
675
+ this._parseHeaders();
676
+ return;
677
+ }
678
+ this.logger.info('Processing headers');
679
+ // Looking for end of headers section
680
+ let index = data.indexOf(nlNlBuffer);
681
+ let padding = 4;
682
+ if (index === -1) {
683
+ // It can also be 2x ASCII 10
684
+ const _index = data.indexOf(Buffer.from([10, 10]));
685
+ if (_index !== -1) {
686
+ index = _index;
687
+ padding = 2;
688
+ }
689
+ }
690
+
691
+ // https://github.com/jarrodek/socket-fetch/issues/3
692
+ const enterIndex = data.indexOf(nlBuffer);
693
+ if (index === -1 && enterIndex !== 0) {
694
+ // end in next chunk
695
+ if (!this.rawHeaders) {
696
+ this.rawHeaders = data;
697
+ } else {
698
+ const sum = this.rawHeaders.length + data.length;
699
+ this.rawHeaders = Buffer.concat([this.rawHeaders, data], sum);
700
+ }
701
+ return;
702
+ }
703
+ if (enterIndex !== 0) {
704
+ const headersArray = data.slice(0, index);
705
+ if (!this.rawHeaders) {
706
+ this.rawHeaders = headersArray;
707
+ } else {
708
+ const sum = this.rawHeaders.length + headersArray.length;
709
+ this.rawHeaders = Buffer.concat([this.rawHeaders, headersArray], sum);
710
+ }
711
+ }
712
+ this._parseHeaders(this.rawHeaders);
713
+ delete this.rawHeaders;
714
+ this.state = RequestState.Body;
715
+ const start = index === -1 ? 0 : index;
716
+ const move = enterIndex === 0 ? 2 : padding;
717
+ data = data.slice(start + move);
718
+ return this._postHeaders(data);
719
+ }
720
+
721
+ /**
722
+ * Check the response headers and end the request if necessary.
723
+ * @param data Current response data buffer
724
+ */
725
+ _postHeaders(data: Buffer): Buffer|undefined {
726
+ if (this.request.method === 'HEAD') {
727
+ this._reportResponse();
728
+ return;
729
+ }
730
+ if (data.length === 0) {
731
+ if (this.currentHeaders.has('Content-Length')) {
732
+ // If the server do not close the connection and clearly indicate that
733
+ // there are no further data to receive the app can close the connection
734
+ // and prepare the response.
735
+ const length = Number(this.currentHeaders.get('Content-Length'));
736
+ // NaN never equals NaN. This is faster.
737
+ if (!Number.isNaN(length) && length === 0) {
738
+ this._reportResponse();
739
+ return;
740
+ }
741
+ }
742
+ // See: https://github.com/advanced-rest-client/arc-electron/issues/106
743
+ // The client should wait until the connection is closed instead of assuming it should end the request.
744
+
745
+ // else if (!this.currentHeaders.has('Transfer-Encoding') || !this.currentHeaders.get('Transfer-Encoding')) {
746
+ // // Fix for https://github.com/jarrodek/socket-fetch/issues/6
747
+ // // There is no body in the response.
748
+ // // this._reportResponse();
749
+ // return;
750
+ // }
751
+ return;
752
+ }
753
+ return data;
754
+ }
755
+
756
+ /**
757
+ * This function assumes that all headers has been read and it's
758
+ * just before changing the status to BODY.
759
+ */
760
+ _parseHeaders(buffer?: Buffer): void {
761
+ let raw = '';
762
+ if (buffer) {
763
+ raw = buffer.toString();
764
+ }
765
+ this.currentResponse!.headers = raw;
766
+ this.logger.info('Received headers list', raw);
767
+ const headers = new Headers(raw);
768
+ this.currentHeaders = headers;
769
+ if (headers.has('Content-Length')) {
770
+ this.responseInfo.contentLength = Number(headers.get('Content-Length'));
771
+ }
772
+ if (headers.has('Transfer-Encoding')) {
773
+ const tr = headers.get('Transfer-Encoding');
774
+ if (tr === 'chunked') {
775
+ this.responseInfo.chunked = true;
776
+ }
777
+ }
778
+ const rawHeaders = headers.toString();
779
+ const detail: HeadersReceivedDetail = {
780
+ returnValue: true,
781
+ value: rawHeaders,
782
+ };
783
+ this.emit('headersreceived', detail);
784
+ if (!detail.returnValue) {
785
+ this.abort();
786
+ }
787
+ }
788
+
789
+ /**
790
+ * @param data A data to process
791
+ */
792
+ _processBody(data?: Buffer): void {
793
+ if (this.aborted || !data) {
794
+ return;
795
+ }
796
+ if (this.responseInfo.chunked) {
797
+ this._processBodyChunked(data);
798
+ } else {
799
+ this._processBodyContentLength(data);
800
+ }
801
+ }
802
+
803
+ _processBodyContentLength(data: Buffer): void {
804
+ if (typeof this.responseInfo.contentLength === 'undefined') {
805
+ this._errorRequest(new Error(`The content-length header of the response is missing.`));
806
+ return;
807
+ }
808
+ if (!this.responseInfo.body) {
809
+ this.responseInfo.body = data;
810
+ if (data.length >= this.responseInfo.contentLength) {
811
+ this._reportResponse();
812
+ return;
813
+ }
814
+ return;
815
+ }
816
+ const sum = this.responseInfo.body.length + data.length;
817
+ this.responseInfo.body = Buffer.concat([this.responseInfo.body, data], sum);
818
+ if (this.responseInfo.body.length >= this.responseInfo.contentLength) {
819
+ this._reportResponse();
820
+ return;
821
+ }
822
+ }
823
+
824
+ /**
825
+ * @param data A latest data to process
826
+ */
827
+ _processBodyChunked(data?: Buffer): void {
828
+ if (!data) {
829
+ return;
830
+ }
831
+ if (this.responseInfo.chunk) {
832
+ data = Buffer.concat([this.responseInfo.chunk, data], this.responseInfo.chunk.length + data.length);
833
+ this.responseInfo.chunk = undefined;
834
+ }
835
+ // eslint-disable-next-line no-constant-condition
836
+ while (true) {
837
+ if (this.responseInfo.chunkSize === 0 && data.indexOf(nlNlBuffer) === 0) {
838
+ this._reportResponse();
839
+ return;
840
+ }
841
+ if (!this.responseInfo.chunkSize) {
842
+ data = this.readChunkSize(data);
843
+ if (!data) {
844
+ return;
845
+ }
846
+ if (!this.responseInfo.chunkSize && this.responseInfo.chunkSize !== 0) {
847
+ // It may happen that node's buffer cuts the data
848
+ // just before the chunk size.
849
+ // It should proceed it in next portion of the data.
850
+ this.responseInfo.chunk = data;
851
+ return;
852
+ }
853
+ if (!this.responseInfo.chunkSize) {
854
+ this._reportResponse();
855
+ return;
856
+ }
857
+ }
858
+ const size = Math.min(this.responseInfo.chunkSize, data.length);
859
+ const sliced = data.slice(0, size);
860
+ if (!this._rawBody) {
861
+ this._rawBody = sliced;
862
+ } else {
863
+ const sum = size + this._rawBody.length;
864
+ this._rawBody = Buffer.concat([this._rawBody, sliced], sum);
865
+ }
866
+
867
+ this.responseInfo.chunkSize -= size;
868
+ if (data.length === 0) {
869
+ // this.logger.warn('Next chunk will start with CRLF!');
870
+ return;
871
+ }
872
+ data = data.slice(size + 2); // + CR
873
+ if (data.length === 0) {
874
+ // this.logger.info('No more data here. Waiting for new chunk');
875
+ return;
876
+ }
877
+ }
878
+ }
879
+
880
+ /**
881
+ * If response's Transfer-Encoding is 'chunked' read until next CR.
882
+ * Everything before it is a chunk size.
883
+ */
884
+ readChunkSize(array: Buffer): Buffer|undefined {
885
+ if (this.aborted) {
886
+ return;
887
+ }
888
+ let index = array.indexOf(nlBuffer);
889
+ if (index === -1) {
890
+ // not found in this portion of data.
891
+ return array;
892
+ }
893
+ if (index === 0) {
894
+ // Node's buffer cuts CRLF after the end of chunk data, without last CLCR,
895
+ // here's to fix it.
896
+ // It can be either new line from the last chunk or end of
897
+ // the message where
898
+ // the rest of the array is [13, 10, 48, 13, 10, 13, 10]
899
+ if (array.indexOf(nlNlBuffer) === 0) {
900
+ this.responseInfo.chunkSize = 0;
901
+ return Buffer.alloc(0);
902
+ }
903
+ array = array.slice(index + 2);
904
+ index = array.indexOf(nlBuffer);
905
+ }
906
+ // this.logger.info('Size index: ', index);
907
+ const chunkSize = parseInt(array.slice(0, index).toString(), 16);
908
+ if (Number.isNaN(chunkSize)) {
909
+ this.responseInfo.chunkSize = undefined;
910
+ return array.slice(index + 2);
911
+ }
912
+ this.responseInfo.chunkSize = chunkSize;
913
+ return array.slice(index + 2);
914
+ }
915
+
916
+ /**
917
+ * Connects to a server through a proxy. Depending on the proxy type the returned socket
918
+ * is a socket created after creating a tunnel (SSL) or the proxy socket.
919
+ *
920
+ * @returns Promise resolved when socket is connected.
921
+ */
922
+ async connectProxy(): Promise<net.Socket|undefined> {
923
+ let socket;
924
+ if (this.isProxyTunnel) {
925
+ socket = await this.connectTunnel(this.isProxySsl);
926
+ } else {
927
+ socket = await this.proxyHttp(this.isProxySsl);
928
+ }
929
+ if (!socket) {
930
+ return;
931
+ }
932
+ const { timeout } = this;
933
+ if (timeout > 0) {
934
+ socket.setTimeout(timeout);
935
+ }
936
+ this.socket = socket;
937
+ this._addSocketListeners(socket);
938
+ socket.resume();
939
+ return socket;
940
+ }
941
+
942
+ /**
943
+ * Creates a tunnel to a Proxy for SSL connections.
944
+ * The returned socket is the one created after the tunnel is established.
945
+ * @param proxyIsSsl Whether the proxy is an SSL connection.
946
+ * @returns Promise resolved when socket is connected.
947
+ */
948
+ async connectTunnel(proxyIsSsl=false): Promise<net.Socket | undefined> {
949
+ this.logger.debug(`Creating a tunnel through the proxy...`);
950
+ const { proxy } = this.opts;
951
+ const { url } = this.request;
952
+ if (!proxy) {
953
+ throw new Error(`No proxy configuration found.`);
954
+ }
955
+ let proxyUrl = proxy;
956
+ if (proxyIsSsl && !proxyUrl.startsWith('https:')) {
957
+ proxyUrl = `https://${proxyUrl}`;
958
+ } else if (!proxyIsSsl && !proxyUrl.startsWith('http:')) {
959
+ proxyUrl = `http://${proxyUrl}`;
960
+ }
961
+ const proxyUri = new URL(proxyUrl);
962
+ const targetUrl = new URL(url);
963
+ const proxyPort = proxyUri.port || (proxyIsSsl ? 443 : 80);
964
+ const targetPort = targetUrl.port || 443; // target is always SSL so 443.
965
+ const authority = `${targetUrl.hostname}:${targetPort}`;
966
+ const connectOptions: https.RequestOptions = {
967
+ host: proxyUri.hostname,
968
+ port: proxyPort,
969
+ method: 'CONNECT',
970
+ path: authority,
971
+ headers: {
972
+ host: authority,
973
+ },
974
+ };
975
+ if (proxyIsSsl) {
976
+ connectOptions.rejectUnauthorized = false;
977
+ // @ts-ignore
978
+ connectOptions.requestOCSP = false;
979
+ }
980
+ const auth = this._proxyAuthHeader();
981
+ if (auth) {
982
+ this.logger.debug(`Adding proxy authorization.`);
983
+ connectOptions.headers!['proxy-authorization'] = auth;
984
+ }
985
+ const lib = proxyIsSsl ? https : http;
986
+ return new Promise((resolve, reject) => {
987
+ this.stats.connectionTime = Date.now();
988
+ const connectRequest = lib.request(connectOptions);
989
+ connectRequest.once('socket', (socket) => {
990
+ socket.on('lookup', () => {
991
+ this.stats.lookupTime = Date.now();
992
+ });
993
+ });
994
+ connectRequest.on('connect', async (res, socket, head) => {
995
+ const time = Date.now();
996
+ this.stats.connectedTime = time;
997
+ this.stats.secureStartTime = time;
998
+ if (typeof this.stats.lookupTime === 'undefined') {
999
+ this.stats.lookupTime = time;
1000
+ }
1001
+ if (res.statusCode === 401) {
1002
+ this.currentHeaders = new Headers(res.headers);
1003
+ const currentResponse = ArcResponse.fromValues(res.statusCode, res.statusMessage, this.currentHeaders.toString());
1004
+ currentResponse.loadingTime = 0;
1005
+ this.currentResponse = currentResponse;
1006
+ if (head.length) {
1007
+ this._rawBody = head;
1008
+ currentResponse.payload = PayloadSerializer.stringifyBuffer(head);
1009
+ }
1010
+ connectRequest.destroy();
1011
+ resolve(undefined);
1012
+ setTimeout(() => {
1013
+ // const e = new NetError('The proxy requires authentication.', 127);
1014
+ this._publishResponse();
1015
+ });
1016
+ } else if (res.statusCode !== 200) {
1017
+ this.logger.debug(`The proxy tunnel ended with ${res.statusCode} status code. Erroring request.`);
1018
+ connectRequest.destroy();
1019
+ const e = new SerializableError('A tunnel connection through the proxy could not be established', 111);
1020
+ reject(e);
1021
+ } else {
1022
+ this.logger.debug(`Established a proxy tunnel.`);
1023
+ this.logger.debug(`Upgrading connection to SSL...`);
1024
+ const tlsSocket = tls.connect({ socket, rejectUnauthorized: false }, () => {
1025
+ this.logger.debug(`Connection upgraded to SSL.`);
1026
+ resolve(tlsSocket);
1027
+ });
1028
+ tlsSocket.once('secureConnect', () => {
1029
+ this.stats.secureConnectedTime = Date.now();
1030
+ })
1031
+ }
1032
+ });
1033
+ connectRequest.end();
1034
+ });
1035
+ }
1036
+
1037
+ /**
1038
+ * Creates connection to a proxy for an HTTP (non-SSL) transport.
1039
+ * This is the same as calling _connect or _connectTls but the target is the proxy and not the
1040
+ * target URL. The message sent to the proxy server is different than the one sent
1041
+ * to the target.
1042
+ * @param proxyIsSsl
1043
+ * @returns Promise resolved when socket is connected.
1044
+ */
1045
+ async proxyHttp(proxyIsSsl=false): Promise<net.Socket> {
1046
+ this.logger.debug('Proxying an HTTP request...');
1047
+ const { proxy } = this.opts;
1048
+ if (!proxy) {
1049
+ throw new Error(`No proxy configuration found.`);
1050
+ }
1051
+ let proxyUrl = proxy;
1052
+ if (proxyIsSsl && !proxyUrl.startsWith('https:')) {
1053
+ proxyUrl = `https://${proxyUrl}`;
1054
+ } else if (!proxyIsSsl && !proxyUrl.startsWith('http:')) {
1055
+ proxyUrl = `http://${proxyUrl}`;
1056
+ }
1057
+ const proxyUri = new URL(proxyUrl);
1058
+ const port = Number(proxyUri.port || 443);
1059
+ const host = proxyUri.hostname;
1060
+ let socket;
1061
+ if (proxyIsSsl) {
1062
+ socket = await this._connectTls(port, host);
1063
+ } else {
1064
+ socket = await this._connect(port, host);
1065
+ }
1066
+ return socket;
1067
+ }
1068
+ }