@api-client/core 0.18.11 → 0.18.13

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 (264) hide show
  1. package/build/src/browser.d.ts +0 -3
  2. package/build/src/browser.d.ts.map +1 -1
  3. package/build/src/browser.js +0 -3
  4. package/build/src/browser.js.map +1 -1
  5. package/build/src/index.d.ts +2 -5
  6. package/build/src/index.d.ts.map +1 -1
  7. package/build/src/index.js +2 -5
  8. package/build/src/index.js.map +1 -1
  9. package/build/src/lib/logging/DefaultLogger.d.ts +14 -0
  10. package/build/src/lib/logging/DefaultLogger.d.ts.map +1 -1
  11. package/build/src/lib/logging/DefaultLogger.js +27 -0
  12. package/build/src/lib/logging/DefaultLogger.js.map +1 -1
  13. package/build/src/lib/logging/index.d.ts +4 -0
  14. package/build/src/lib/logging/index.d.ts.map +1 -0
  15. package/build/src/lib/logging/index.js +10 -0
  16. package/build/src/lib/logging/index.js.map +1 -0
  17. package/build/src/modeling/DomainModel.d.ts.map +1 -1
  18. package/build/src/modeling/DomainModel.js +11 -4
  19. package/build/src/modeling/DomainModel.js.map +1 -1
  20. package/build/src/models/ClientCertificate.d.ts +1 -1
  21. package/build/src/models/ClientCertificate.js.map +1 -1
  22. package/build/src/models/RequestConfig.d.ts +1 -1
  23. package/build/src/models/RequestConfig.js.map +1 -1
  24. package/build/src/models/SerializableError.d.ts +1 -1
  25. package/build/src/models/SerializableError.d.ts.map +1 -1
  26. package/build/src/models/SerializableError.js.map +1 -1
  27. package/build/src/proxy/RequestProxy.d.ts.map +1 -1
  28. package/build/src/proxy/RequestProxy.js +2 -2
  29. package/build/src/proxy/RequestProxy.js.map +1 -1
  30. package/build/src/runtime/http-engine/CoreEngine.d.ts +218 -139
  31. package/build/src/runtime/http-engine/CoreEngine.d.ts.map +1 -1
  32. package/build/src/runtime/http-engine/CoreEngine.js +716 -870
  33. package/build/src/runtime/http-engine/CoreEngine.js.map +1 -1
  34. package/build/src/runtime/http-engine/PayloadSupport.d.ts.map +1 -1
  35. package/build/src/runtime/http-engine/PayloadSupport.js +2 -1
  36. package/build/src/runtime/http-engine/PayloadSupport.js.map +1 -1
  37. package/build/src/runtime/http-engine/auth/AuthManager.d.ts +73 -0
  38. package/build/src/runtime/http-engine/auth/AuthManager.d.ts.map +1 -0
  39. package/build/src/runtime/http-engine/auth/AuthManager.js +186 -0
  40. package/build/src/runtime/http-engine/auth/AuthManager.js.map +1 -0
  41. package/build/src/runtime/http-engine/auth/index.d.ts +2 -0
  42. package/build/src/runtime/http-engine/auth/index.d.ts.map +1 -0
  43. package/build/src/runtime/http-engine/auth/index.js +2 -0
  44. package/build/src/runtime/http-engine/auth/index.js.map +1 -0
  45. package/build/src/runtime/http-engine/certificates/CertificateManager.d.ts +11 -0
  46. package/build/src/runtime/http-engine/certificates/CertificateManager.d.ts.map +1 -0
  47. package/build/src/runtime/http-engine/certificates/CertificateManager.js +76 -0
  48. package/build/src/runtime/http-engine/certificates/CertificateManager.js.map +1 -0
  49. package/build/src/runtime/http-engine/certificates/index.d.ts +2 -0
  50. package/build/src/runtime/http-engine/certificates/index.d.ts.map +1 -0
  51. package/build/src/runtime/http-engine/certificates/index.js +2 -0
  52. package/build/src/runtime/http-engine/certificates/index.js.map +1 -0
  53. package/build/src/runtime/http-engine/compression/CompressionManager.d.ts +25 -0
  54. package/build/src/runtime/http-engine/compression/CompressionManager.d.ts.map +1 -0
  55. package/build/src/runtime/http-engine/compression/CompressionManager.js +89 -0
  56. package/build/src/runtime/http-engine/compression/CompressionManager.js.map +1 -0
  57. package/build/src/runtime/http-engine/compression/index.d.ts +2 -0
  58. package/build/src/runtime/http-engine/compression/index.d.ts.map +1 -0
  59. package/build/src/runtime/http-engine/compression/index.js +2 -0
  60. package/build/src/runtime/http-engine/compression/index.js.map +1 -0
  61. package/build/src/runtime/http-engine/connections/ConnectionManager.d.ts +57 -0
  62. package/build/src/runtime/http-engine/connections/ConnectionManager.d.ts.map +1 -0
  63. package/build/src/runtime/http-engine/connections/ConnectionManager.js +78 -0
  64. package/build/src/runtime/http-engine/connections/ConnectionManager.js.map +1 -0
  65. package/build/src/runtime/http-engine/connections/DigestAuthHandler.d.ts +70 -0
  66. package/build/src/runtime/http-engine/connections/DigestAuthHandler.d.ts.map +1 -0
  67. package/build/src/runtime/http-engine/connections/DigestAuthHandler.js +184 -0
  68. package/build/src/runtime/http-engine/connections/DigestAuthHandler.js.map +1 -0
  69. package/build/src/runtime/http-engine/connections/DirectConnection.d.ts +22 -0
  70. package/build/src/runtime/http-engine/connections/DirectConnection.d.ts.map +1 -0
  71. package/build/src/runtime/http-engine/connections/DirectConnection.js +105 -0
  72. package/build/src/runtime/http-engine/connections/DirectConnection.js.map +1 -0
  73. package/build/src/runtime/http-engine/connections/ProxyAuthHandler.d.ts +60 -0
  74. package/build/src/runtime/http-engine/connections/ProxyAuthHandler.d.ts.map +1 -0
  75. package/build/src/runtime/http-engine/connections/ProxyAuthHandler.js +138 -0
  76. package/build/src/runtime/http-engine/connections/ProxyAuthHandler.js.map +1 -0
  77. package/build/src/runtime/http-engine/connections/ProxyConnection.d.ts +14 -0
  78. package/build/src/runtime/http-engine/connections/ProxyConnection.d.ts.map +1 -0
  79. package/build/src/runtime/http-engine/connections/ProxyConnection.js +47 -0
  80. package/build/src/runtime/http-engine/connections/ProxyConnection.js.map +1 -0
  81. package/build/src/runtime/http-engine/connections/TunnelConnection.d.ts +13 -0
  82. package/build/src/runtime/http-engine/connections/TunnelConnection.d.ts.map +1 -0
  83. package/build/src/runtime/http-engine/connections/TunnelConnection.js +175 -0
  84. package/build/src/runtime/http-engine/connections/TunnelConnection.js.map +1 -0
  85. package/build/src/runtime/http-engine/connections/index.d.ts +7 -0
  86. package/build/src/runtime/http-engine/connections/index.d.ts.map +1 -0
  87. package/build/src/runtime/http-engine/connections/index.js +7 -0
  88. package/build/src/runtime/http-engine/connections/index.js.map +1 -0
  89. package/build/src/runtime/http-engine/constants.d.ts +69 -0
  90. package/build/src/runtime/http-engine/constants.d.ts.map +1 -0
  91. package/build/src/runtime/http-engine/constants.js +90 -0
  92. package/build/src/runtime/http-engine/constants.js.map +1 -0
  93. package/build/src/runtime/http-engine/cookies/CookieProcessor.d.ts +5 -0
  94. package/build/src/runtime/http-engine/cookies/CookieProcessor.d.ts.map +1 -0
  95. package/build/src/runtime/http-engine/cookies/CookieProcessor.js +20 -0
  96. package/build/src/runtime/http-engine/cookies/CookieProcessor.js.map +1 -0
  97. package/build/src/runtime/http-engine/cookies/index.d.ts +2 -0
  98. package/build/src/runtime/http-engine/cookies/index.d.ts.map +1 -0
  99. package/build/src/runtime/http-engine/cookies/index.js +2 -0
  100. package/build/src/runtime/http-engine/cookies/index.js.map +1 -0
  101. package/build/src/runtime/http-engine/errors/HttpEngineErrors.d.ts +156 -0
  102. package/build/src/runtime/http-engine/errors/HttpEngineErrors.d.ts.map +1 -0
  103. package/build/src/runtime/http-engine/errors/HttpEngineErrors.js +227 -0
  104. package/build/src/runtime/http-engine/errors/HttpEngineErrors.js.map +1 -0
  105. package/build/src/runtime/http-engine/errors/index.d.ts +2 -0
  106. package/build/src/runtime/http-engine/errors/index.d.ts.map +1 -0
  107. package/build/src/runtime/http-engine/errors/index.js +2 -0
  108. package/build/src/runtime/http-engine/errors/index.js.map +1 -0
  109. package/build/src/runtime/http-engine/message/MessageBuilder.d.ts +66 -0
  110. package/build/src/runtime/http-engine/message/MessageBuilder.d.ts.map +1 -0
  111. package/build/src/runtime/http-engine/message/MessageBuilder.js +161 -0
  112. package/build/src/runtime/http-engine/message/MessageBuilder.js.map +1 -0
  113. package/build/src/runtime/http-engine/message/MessageProcessor.d.ts +27 -0
  114. package/build/src/runtime/http-engine/message/MessageProcessor.d.ts.map +1 -0
  115. package/build/src/runtime/http-engine/message/MessageProcessor.js +51 -0
  116. package/build/src/runtime/http-engine/message/MessageProcessor.js.map +1 -0
  117. package/build/src/runtime/http-engine/message/index.d.ts +3 -0
  118. package/build/src/runtime/http-engine/message/index.d.ts.map +1 -0
  119. package/build/src/runtime/http-engine/message/index.js +3 -0
  120. package/build/src/runtime/http-engine/message/index.js.map +1 -0
  121. package/build/src/runtime/http-engine/ntlm/NtlmAuth.d.ts +2 -8
  122. package/build/src/runtime/http-engine/ntlm/NtlmAuth.d.ts.map +1 -1
  123. package/build/src/runtime/http-engine/ntlm/NtlmAuth.js +11 -5
  124. package/build/src/runtime/http-engine/ntlm/NtlmAuth.js.map +1 -1
  125. package/build/src/runtime/http-engine/ntlm/NtlmMessage.js +6 -6
  126. package/build/src/runtime/http-engine/ntlm/NtlmMessage.js.map +1 -1
  127. package/build/src/runtime/http-engine/parsers/BodyParser.d.ts +39 -0
  128. package/build/src/runtime/http-engine/parsers/BodyParser.d.ts.map +1 -0
  129. package/build/src/runtime/http-engine/parsers/BodyParser.js +145 -0
  130. package/build/src/runtime/http-engine/parsers/BodyParser.js.map +1 -0
  131. package/build/src/runtime/http-engine/parsers/HeadersParser.d.ts +29 -0
  132. package/build/src/runtime/http-engine/parsers/HeadersParser.d.ts.map +1 -0
  133. package/build/src/runtime/http-engine/parsers/HeadersParser.js +88 -0
  134. package/build/src/runtime/http-engine/parsers/HeadersParser.js.map +1 -0
  135. package/build/src/runtime/http-engine/parsers/HttpResponseParser.d.ts +91 -0
  136. package/build/src/runtime/http-engine/parsers/HttpResponseParser.d.ts.map +1 -0
  137. package/build/src/runtime/http-engine/parsers/HttpResponseParser.js +236 -0
  138. package/build/src/runtime/http-engine/parsers/HttpResponseParser.js.map +1 -0
  139. package/build/src/runtime/http-engine/parsers/StatusParser.d.ts +20 -0
  140. package/build/src/runtime/http-engine/parsers/StatusParser.d.ts.map +1 -0
  141. package/build/src/runtime/http-engine/parsers/StatusParser.js +51 -0
  142. package/build/src/runtime/http-engine/parsers/StatusParser.js.map +1 -0
  143. package/build/src/runtime/http-engine/parsers/index.d.ts +5 -0
  144. package/build/src/runtime/http-engine/parsers/index.d.ts.map +1 -0
  145. package/build/src/runtime/http-engine/parsers/index.js +5 -0
  146. package/build/src/runtime/http-engine/parsers/index.js.map +1 -0
  147. package/build/src/runtime/http-engine/response/ResponseProcessor.d.ts +22 -0
  148. package/build/src/runtime/http-engine/response/ResponseProcessor.d.ts.map +1 -0
  149. package/build/src/runtime/http-engine/response/ResponseProcessor.js +25 -0
  150. package/build/src/runtime/http-engine/response/ResponseProcessor.js.map +1 -0
  151. package/build/src/runtime/http-engine/response/index.d.ts +2 -0
  152. package/build/src/runtime/http-engine/response/index.d.ts.map +1 -0
  153. package/build/src/runtime/http-engine/response/index.js +2 -0
  154. package/build/src/runtime/http-engine/response/index.js.map +1 -0
  155. package/build/src/runtime/http-engine/statistics/StatisticsProcessor.d.ts +7 -0
  156. package/build/src/runtime/http-engine/statistics/StatisticsProcessor.d.ts.map +1 -0
  157. package/build/src/runtime/http-engine/statistics/StatisticsProcessor.js +40 -0
  158. package/build/src/runtime/http-engine/statistics/StatisticsProcessor.js.map +1 -0
  159. package/build/src/runtime/http-engine/statistics/index.d.ts +2 -0
  160. package/build/src/runtime/http-engine/statistics/index.d.ts.map +1 -0
  161. package/build/src/runtime/http-engine/statistics/index.js +2 -0
  162. package/build/src/runtime/http-engine/statistics/index.js.map +1 -0
  163. package/build/src/runtime/http-engine/url/UrlProcessor.d.ts +24 -0
  164. package/build/src/runtime/http-engine/url/UrlProcessor.d.ts.map +1 -0
  165. package/build/src/runtime/http-engine/url/UrlProcessor.js +50 -0
  166. package/build/src/runtime/http-engine/url/UrlProcessor.js.map +1 -0
  167. package/build/src/runtime/http-engine/url/index.d.ts +2 -0
  168. package/build/src/runtime/http-engine/url/index.d.ts.map +1 -0
  169. package/build/src/runtime/http-engine/url/index.js +2 -0
  170. package/build/src/runtime/http-engine/url/index.js.map +1 -0
  171. package/build/src/runtime/http-runner/HttpRequestRunner.d.ts +3 -3
  172. package/build/src/runtime/http-runner/HttpRequestRunner.d.ts.map +1 -1
  173. package/build/src/runtime/http-runner/HttpRequestRunner.js.map +1 -1
  174. package/build/src/runtime/node/InteropInterfaces.d.ts +3 -3
  175. package/build/src/runtime/node/InteropInterfaces.d.ts.map +1 -1
  176. package/build/src/runtime/node/InteropInterfaces.js.map +1 -1
  177. package/build/src/runtime/node/ProjectRequestRunner.d.ts +2 -2
  178. package/build/src/runtime/node/ProjectRequestRunner.d.ts.map +1 -1
  179. package/build/src/runtime/node/ProjectRequestRunner.js.map +1 -1
  180. package/build/src/runtime/node/ProjectRunner.d.ts.map +1 -1
  181. package/build/src/runtime/node/ProjectRunner.js +2 -2
  182. package/build/src/runtime/node/ProjectRunner.js.map +1 -1
  183. package/build/tsconfig.tsbuildinfo +1 -1
  184. package/data/models/example-generator-api.json +24 -24
  185. package/package.json +2 -2
  186. package/src/lib/logging/DefaultLogger.ts +32 -0
  187. package/src/modeling/DomainModel.ts +11 -4
  188. package/src/models/ClientCertificate.ts +1 -1
  189. package/src/models/RequestConfig.ts +1 -1
  190. package/src/models/SerializableError.ts +1 -1
  191. package/src/proxy/RequestProxy.ts +2 -2
  192. package/src/runtime/http-engine/CoreEngine.ts +858 -893
  193. package/src/runtime/http-engine/PayloadSupport.ts +2 -1
  194. package/src/runtime/http-engine/auth/AuthManager.ts +242 -0
  195. package/src/runtime/http-engine/certificates/CertificateManager.ts +74 -0
  196. package/src/runtime/http-engine/compression/CompressionManager.ts +99 -0
  197. package/src/runtime/http-engine/connections/ConnectionManager.ts +123 -0
  198. package/src/runtime/http-engine/connections/DigestAuthHandler.ts +238 -0
  199. package/src/runtime/http-engine/connections/DirectConnection.ts +134 -0
  200. package/src/runtime/http-engine/connections/ProxyAuthHandler.ts +179 -0
  201. package/src/runtime/http-engine/connections/ProxyConnection.ts +55 -0
  202. package/src/runtime/http-engine/connections/TunnelConnection.ts +192 -0
  203. package/src/runtime/http-engine/constants.ts +103 -0
  204. package/src/runtime/http-engine/cookies/CookieProcessor.ts +25 -0
  205. package/src/runtime/http-engine/errors/HttpEngineErrors.ts +319 -0
  206. package/src/runtime/http-engine/message/MessageBuilder.ts +201 -0
  207. package/src/runtime/http-engine/message/MessageProcessor.ts +73 -0
  208. package/src/runtime/http-engine/ntlm/NtlmAuth.ts +16 -13
  209. package/src/runtime/http-engine/ntlm/NtlmMessage.ts +6 -6
  210. package/src/runtime/http-engine/parsers/BodyParser.ts +171 -0
  211. package/src/runtime/http-engine/parsers/HeadersParser.ts +103 -0
  212. package/src/runtime/http-engine/parsers/HttpResponseParser.ts +280 -0
  213. package/src/runtime/http-engine/parsers/StatusParser.ts +69 -0
  214. package/src/runtime/http-engine/response/ResponseProcessor.ts +46 -0
  215. package/src/runtime/http-engine/statistics/StatisticsProcessor.ts +52 -0
  216. package/src/runtime/http-engine/url/UrlProcessor.ts +55 -0
  217. package/src/runtime/http-runner/HttpRequestRunner.ts +3 -3
  218. package/src/runtime/node/InteropInterfaces.ts +3 -3
  219. package/src/runtime/node/ProjectRequestRunner.ts +2 -2
  220. package/src/runtime/node/ProjectRunner.ts +2 -2
  221. package/tests/servers/ProxyServer.ts +32 -19
  222. package/tests/servers/express-routes/ApiEndpoint.ts +24 -0
  223. package/tests/servers/express-routes/BasicAuthRoute.ts +36 -0
  224. package/tests/servers/express-routes/BearerAuthRoute.ts +35 -0
  225. package/tests/servers/express-routes/NTLMRoute.ts +2 -3
  226. package/tests/servers/express-routes/PostApi.ts +15 -2
  227. package/tests/servers/express-routes/RedirectsApi.ts +12 -1
  228. package/tests/servers/express-routes/ResponsesApi.ts +1 -1
  229. package/tests/servers/express-routes/StreamApi.ts +19 -0
  230. package/tests/servers/oauth2mock/ServerMock.js +1 -1
  231. package/tests/unit/modeling/domain_model_entities.spec.ts +306 -1
  232. package/tests/unit/runtime/http-engine/HttpResponseParser.spec.ts +337 -0
  233. package/tests/unit/runtime/http-engine/abort.spec.ts +4 -5
  234. package/tests/unit/runtime/http-engine/auth.spec.ts +7 -58
  235. package/tests/unit/runtime/http-engine/certificates/CertificateManager.spec.ts +482 -0
  236. package/tests/unit/runtime/http-engine/certificates.spec.ts +2 -2
  237. package/tests/unit/runtime/http-engine/compression/CompressionManager.spec.ts +498 -0
  238. package/tests/unit/runtime/http-engine/compression.spec.ts +3 -72
  239. package/tests/unit/runtime/http-engine/connections/ConnectionManager.spec.ts +379 -0
  240. package/tests/unit/runtime/http-engine/connections/DigestAuthHandler.spec.ts +164 -0
  241. package/tests/unit/runtime/http-engine/core_engine.spec.ts +561 -0
  242. package/tests/unit/runtime/http-engine/engine_statuses.spec.ts +2 -2
  243. package/tests/unit/runtime/http-engine/events.spec.ts +2 -2
  244. package/tests/unit/runtime/http-engine/headers.spec.ts +2 -88
  245. package/tests/unit/runtime/http-engine/hosts.spec.ts +2 -2
  246. package/tests/unit/runtime/http-engine/http-get.spec.ts +2 -2
  247. package/tests/unit/runtime/http-engine/http-post.spec.ts +2 -2
  248. package/tests/unit/runtime/http-engine/logger.spec.ts +0 -8
  249. package/tests/unit/runtime/http-engine/message.spec.ts +2 -194
  250. package/tests/unit/runtime/http-engine/params.spec.ts +4 -4
  251. package/tests/unit/runtime/http-engine/proxy.spec.ts +15 -14
  252. package/tests/unit/runtime/http-engine/redirects.spec.ts +2 -2
  253. package/tests/unit/runtime/http-engine/responses.spec.ts +170 -277
  254. package/tests/unit/runtime/http-engine/timeout.spec.ts +3 -3
  255. package/tests/unit/runtime/http-engine/timings.spec.ts +2 -2
  256. package/tests/unit/runtime/proxy/HttpProjectProxy.spec.ts +25 -28
  257. package/tests/unit/runtime/runners/project_runner.spec.ts +2 -2
  258. package/tests/unit/runtime/runners/request_runner.spec.ts +2 -2
  259. package/build/src/runtime/http-engine/HttpEngine.d.ts +0 -311
  260. package/build/src/runtime/http-engine/HttpEngine.d.ts.map +0 -1
  261. package/build/src/runtime/http-engine/HttpEngine.js +0 -802
  262. package/build/src/runtime/http-engine/HttpEngine.js.map +0 -1
  263. package/src/runtime/http-engine/HttpEngine.ts +0 -952
  264. package/tests/unit/runtime/http-engine/connecting.spec.ts +0 -140
@@ -1,45 +1,268 @@
1
- import net from 'net';
2
- import tls from 'tls';
3
- import http from 'http';
4
- import https from 'https';
5
- import { HttpEngine } from './HttpEngine.js';
1
+ import { EventEmitter } from 'events';
2
+ import { HttpRequest } from '../../models/HttpRequest.js';
3
+ import { SentRequest } from '../../models/SentRequest.js';
6
4
  import { Response } from '../../models/Response.js';
5
+ import { ErrorResponse } from '../../models/ErrorResponse.js';
6
+ import { RequestsSize } from '../../models/RequestsSize.js';
7
+ import { HttpResponse } from '../../models/HttpResponse.js';
8
+ import { ResponseRedirect } from '../../models/ResponseRedirect.js';
9
+ import { RequestLog } from '../../models/RequestLog.js';
7
10
  import { SerializableError } from '../../models/SerializableError.js';
11
+ import { ResponseAuthorization } from '../../models/ResponseAuthorization.js';
8
12
  import { Headers } from '../../lib/headers/Headers.js';
13
+ import { getPort } from './RequestUtils.js';
14
+ import { HttpResponseParser, RequestState } from './parsers/index.js';
15
+ import { ConnectionManager } from './connections/index.js';
16
+ import { AuthManager } from './auth/index.js';
17
+ import { MessageBuilder } from './message/index.js';
18
+ import { HttpEngineErrorFactory } from './errors/index.js';
19
+ import { isMethodWithoutBody, HEADER_PROXY_AUTHORIZATION } from './constants.js';
20
+ import { decompress } from './compression/index.js';
21
+ import { checkServerIdentity } from './certificates/index.js';
22
+ import { readUrl, getHostHeader, getRedirectLocation, isRedirectLoop } from './url/index.js';
23
+ import { processRedirectCookies } from './cookies/index.js';
24
+ import { computeStats } from './statistics/index.js';
25
+ import { processResponse } from './response/index.js';
26
+ import { getCodeMessage } from './HttpErrorCodes.js';
27
+ import { createLogger } from '../../lib/logging/index.js';
9
28
  import * as PayloadSupport from './PayloadSupport.js';
10
- import { addContentLength, getPort } from './RequestUtils.js';
11
- import { NtlmAuth } from './ntlm/NtlmAuth.js';
29
+ import { addContentLength } from './RequestUtils.js';
12
30
  import { PayloadSerializer } from '../../lib/transformers/PayloadSerializer.js';
13
- const nlBuffer = Buffer.from([13, 10]);
14
- const nlNlBuffer = Buffer.from([13, 10, 13, 10]);
15
- export var RequestState;
16
- (function (RequestState) {
17
- RequestState[RequestState["Status"] = 0] = "Status";
18
- RequestState[RequestState["Headers"] = 1] = "Headers";
19
- RequestState[RequestState["Body"] = 2] = "Body";
20
- RequestState[RequestState["Done"] = 3] = "Done";
21
- })(RequestState || (RequestState = {}));
31
+ import { ProxyAuthenticationError } from './errors/HttpEngineErrors.js';
32
+ export const mainPromiseSymbol = Symbol('mainPromise');
22
33
  /**
23
- * API Client's HTTP engine.
34
+ * API Client's HTTP engine with refactored parsing and connection handling.
24
35
  * An HTTP 1.1 engine working directly on the socket. It communicates with the remote machine and
25
36
  * collects stats about the request and response.
26
37
  */
27
- export class CoreEngine extends HttpEngine {
28
- state = RequestState.Status;
29
- rawHeaders;
30
- _hostTestReg = /^\s*host\s*:/im;
31
- responseInfo;
38
+ export class CoreEngine extends EventEmitter {
39
+ request;
40
+ opts;
41
+ logger;
42
+ /**
43
+ * The current sent request
44
+ */
45
+ sentRequest;
46
+ redirects = [];
47
+ /**
48
+ * When true the request has been aborted.
49
+ */
50
+ aborted = false;
51
+ /**
52
+ * Parsed value of the request URL.
53
+ */
54
+ uri;
55
+ socket;
56
+ /**
57
+ * Host header can be different than registered URL because of
58
+ * `hosts` rules.
59
+ * If a rule changes host value of the URL the original URL host value
60
+ * is used when generating the request and not overwritten one.
61
+ * This way virtual hosts can be tested using hosts.
62
+ */
63
+ hostHeader;
64
+ hostTestReg = /^\s*host\s*:/im;
65
+ /**
66
+ * Set when the request is redirected.
67
+ */
68
+ redirecting = false;
69
+ /**
70
+ * The response headers.
71
+ * The object may be empty when the response is not set.
72
+ */
73
+ currentHeaders = new Headers();
74
+ /**
75
+ * The response object build during the execution.
76
+ */
77
+ currentResponse;
78
+ /**
79
+ * Keeps the raw body in a temporary buffer while processing the response.
80
+ */
81
+ _rawBody;
82
+ stats = {};
83
+ auth;
84
+ mainResolver;
85
+ mainRejecter;
86
+ [mainPromiseSymbol];
87
+ _signal;
88
+ // CoreEngine specific properties
89
+ parser;
90
+ connectionManager;
91
+ authManager;
92
+ messageBuilder;
32
93
  hasProxy;
33
94
  isProxyTunnel;
34
95
  isProxySsl;
96
+ /**
97
+ * @return True if following redirects is allowed.
98
+ */
99
+ get followRedirects() {
100
+ const { opts } = this;
101
+ if (typeof opts.followRedirects === 'boolean') {
102
+ return opts.followRedirects;
103
+ }
104
+ return true;
105
+ }
106
+ /**
107
+ * The request timeout.
108
+ */
109
+ get timeout() {
110
+ const { opts } = this;
111
+ if (typeof opts.timeout === 'number') {
112
+ return opts.timeout;
113
+ }
114
+ return 0;
115
+ }
116
+ /**
117
+ * The abort signal to set on this request.
118
+ * Aborts the request when the signal fires.
119
+ */
120
+ get signal() {
121
+ return this._signal;
122
+ }
123
+ set signal(value) {
124
+ const old = this._signal;
125
+ if (old === value) {
126
+ return;
127
+ }
128
+ this._signal = value;
129
+ if (old) {
130
+ old.removeEventListener('abort', this._abortHandler);
131
+ }
132
+ if (value) {
133
+ value.addEventListener('abort', this._abortHandler);
134
+ }
135
+ }
35
136
  constructor(request, opts = {}) {
36
- super(request, opts);
37
- this.responseInfo = {
38
- chunked: false,
39
- };
137
+ super();
138
+ this.request = new HttpRequest({ ...request });
139
+ this.opts = opts;
140
+ this.logger = this.setupLogger(opts);
141
+ this.sentRequest = new SentRequest({ ...request, startTime: Date.now() });
142
+ this.uri = this.readUrl(request.url);
143
+ this.hostHeader = this.getHostHeader(request.url);
144
+ // CoreEngine specific initialization
40
145
  this.hasProxy = !!this.opts.proxy;
41
146
  this.isProxyTunnel = this.hasProxy && this.request.url.startsWith('https:');
42
147
  this.isProxySsl = !!this.opts.proxy && this.opts.proxy.startsWith('https:');
148
+ // Initialize the HTTP response parser
149
+ this.parser = new HttpResponseParser(this.logger, this.createParserCallbacks());
150
+ // Initialize the connection manager
151
+ this.connectionManager = new ConnectionManager(this.logger, this.stats);
152
+ // Initialize the auth manager
153
+ const proxyCredentials = {
154
+ proxyUsername: this.opts.proxyUsername,
155
+ proxyPassword: this.opts.proxyPassword,
156
+ proxyAuthorization: this.opts.proxyAuthorization,
157
+ };
158
+ this.authManager = new AuthManager({
159
+ logger: this.logger,
160
+ proxyCredentials,
161
+ });
162
+ // Initialize the message builder
163
+ this.messageBuilder = new MessageBuilder({
164
+ logger: this.logger,
165
+ request: this.request,
166
+ hasProxy: this.hasProxy,
167
+ isProxyTunnel: this.isProxyTunnel,
168
+ hostHeader: this.hostHeader,
169
+ });
170
+ this._abortHandler = this._abortHandler.bind(this);
171
+ if (opts.signal) {
172
+ this.signal = opts.signal;
173
+ }
174
+ }
175
+ /**
176
+ * Creates a logger object to log debug output.
177
+ */
178
+ setupLogger(opts = {}) {
179
+ if (opts.logger) {
180
+ return opts.logger;
181
+ }
182
+ return createLogger();
183
+ }
184
+ /**
185
+ * Updates the `uri` property from current request URL
186
+ */
187
+ readUrl(value) {
188
+ return readUrl(value, { hosts: this.opts.hosts });
189
+ }
190
+ /**
191
+ * Get host header for the request
192
+ */
193
+ getHostHeader(url) {
194
+ return getHostHeader(url, { hosts: this.opts.hosts });
195
+ }
196
+ /**
197
+ * Aborts current request.
198
+ * It emits `error` event
199
+ */
200
+ abort() {
201
+ this.logger.debug('Aborting the request...');
202
+ this.aborted = true;
203
+ if (!this.socket) {
204
+ return;
205
+ }
206
+ this.socket.removeAllListeners();
207
+ if (this.socket.destroyed) {
208
+ this.socket = undefined;
209
+ return;
210
+ }
211
+ this.socket.pause();
212
+ this.socket.destroy();
213
+ this.socket = undefined;
214
+ }
215
+ /**
216
+ * Handler for the `abort` event on the `AbortSignal`.
217
+ */
218
+ _abortHandler() {
219
+ const e = new SerializableError('Request aborted', 3);
220
+ this._errorRequest(e);
221
+ this.abort();
222
+ }
223
+ /**
224
+ * Create parser callbacks to handle parsed data
225
+ */
226
+ createParserCallbacks() {
227
+ return {
228
+ onStatusParsed: (status, statusText) => {
229
+ this.logger.debug('onStatusParsed called', status, statusText);
230
+ const response = Response.fromValues(status, statusText);
231
+ response.loadingTime = 0;
232
+ this.currentResponse = response;
233
+ },
234
+ onHeadersParsed: (headers) => {
235
+ this.logger.debug('onHeadersParsed called');
236
+ this.currentHeaders = headers;
237
+ if (this.currentResponse) {
238
+ this.currentResponse.headers = headers.toString();
239
+ }
240
+ },
241
+ onBodyComplete: (body) => {
242
+ this.logger.debug('onBodyComplete called');
243
+ this._rawBody = body;
244
+ this._reportResponse().catch((error) => {
245
+ this.logger.error('Error in _reportResponse:', error);
246
+ this._errorRequest({
247
+ message: `Response processing failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
248
+ });
249
+ });
250
+ },
251
+ onError: (error) => {
252
+ this.logger.debug('onError called', error);
253
+ this._errorRequest({
254
+ message: error.message || 'Unknown error occurred',
255
+ });
256
+ },
257
+ onAbort: () => {
258
+ this.logger.debug('onAbort called');
259
+ this.abort();
260
+ },
261
+ emit: (event, detail) => {
262
+ this.logger.debug('emit called', event, detail);
263
+ this.emit(event, detail);
264
+ },
265
+ };
43
266
  }
44
267
  /**
45
268
  * Sends the request
@@ -51,19 +274,27 @@ export class CoreEngine extends HttpEngine {
51
274
  }
52
275
  async sendRequest() {
53
276
  try {
54
- if (this.hasProxy) {
55
- await this.connectProxy();
56
- }
57
- else {
58
- await this.connect();
59
- }
60
- if (!this.socket || this.aborted) {
277
+ // Use the connection manager to establish the connection
278
+ const socket = await this.establishConnection();
279
+ if (!socket || this.aborted) {
61
280
  return;
62
281
  }
63
282
  const message = await this.prepareMessage();
64
283
  await this.writeMessage(message);
65
284
  }
66
285
  catch (cause) {
286
+ if (cause instanceof ProxyAuthenticationError) {
287
+ this.currentHeaders = new Headers(cause.response.headers);
288
+ const currentResponse = Response.fromValues(cause.response.statusCode || 401, cause.response.statusMessage, this.currentHeaders.toString());
289
+ currentResponse.loadingTime = 0;
290
+ this.currentResponse = currentResponse;
291
+ if (cause.head.length) {
292
+ this._rawBody = cause.head;
293
+ currentResponse.payload = PayloadSerializer.stringifyBuffer(cause.head);
294
+ }
295
+ this._publishResponse();
296
+ return;
297
+ }
67
298
  const e = cause;
68
299
  const err = new SerializableError(e.message, { cause: e });
69
300
  if (e.code || e.code === 0) {
@@ -74,46 +305,371 @@ export class CoreEngine extends HttpEngine {
74
305
  message: err.message,
75
306
  code: err.code,
76
307
  });
77
- throw cause;
308
+ }
309
+ }
310
+ /**
311
+ * Establish connection using the connection manager
312
+ */
313
+ async establishConnection() {
314
+ const port = getPort(this.uri.port, this.uri.protocol);
315
+ const host = this.uri.hostname;
316
+ const protocol = this.uri.protocol;
317
+ const socket = await this.connectionManager.createConnection(host, port, protocol, this.opts);
318
+ const { timeout } = this;
319
+ if (timeout > 0) {
320
+ socket.setTimeout(timeout);
321
+ }
322
+ this.socket = socket;
323
+ this._addSocketListeners(socket);
324
+ socket.resume();
325
+ return socket;
326
+ }
327
+ /**
328
+ * Decompresses received body if `content-encoding` header is set.
329
+ */
330
+ async decompress(body) {
331
+ return decompress(body, {
332
+ aborted: this.aborted,
333
+ headers: this.currentHeaders,
334
+ });
335
+ }
336
+ /**
337
+ * Prepares headers list to be send to the remote machine.
338
+ * If `defaultHeaders` option is set then it adds `user-agent` and `accept`
339
+ * headers.
340
+ */
341
+ prepareHeaders(headers, withPayload = false) {
342
+ if (this.opts.defaultHeaders) {
343
+ if (!headers.has('user-agent')) {
344
+ if (this.opts.defaultUserAgent) {
345
+ headers.set('user-agent', this.opts.defaultUserAgent);
346
+ }
347
+ else {
348
+ headers.set('user-agent', 'api client');
349
+ }
350
+ }
351
+ if (!headers.has('accept')) {
352
+ if (this.opts.defaultAccept) {
353
+ headers.set('accept', this.opts.defaultAccept);
354
+ }
355
+ else {
356
+ headers.set('accept', '*/*');
357
+ }
358
+ }
359
+ }
360
+ if (withPayload) {
361
+ if (!headers.has('content-type') && this.request.payloadOptions) {
362
+ if (this.request.payloadOptions.mime) {
363
+ headers.set('content-type', this.request.payloadOptions.mime);
364
+ }
365
+ }
78
366
  }
79
367
  }
80
368
  /**
81
369
  * Cleans the state after finished.
82
370
  */
83
371
  _cleanUp() {
84
- super._cleanUp();
85
- this.state = RequestState.Status;
86
- this.rawHeaders = undefined;
87
- this.responseInfo = {
88
- chunked: false,
89
- };
372
+ this.redirects = [];
373
+ this.currentHeaders = new Headers();
374
+ this.currentResponse = undefined;
375
+ this._rawBody = undefined;
376
+ this.redirecting = false;
377
+ this.stats = {};
378
+ this.mainRejecter = undefined;
379
+ this.mainResolver = undefined;
380
+ this._clearSocketEventListeners();
381
+ this.parser.reset();
90
382
  }
91
383
  /**
92
384
  * Cleans up the state for redirect.
93
385
  */
94
386
  _cleanUpRedirect() {
95
- super._cleanUpRedirect();
96
- this.state = RequestState.Status;
97
- this.rawHeaders = undefined;
98
- this.responseInfo = {
99
- chunked: false,
387
+ this.currentHeaders = new Headers();
388
+ this.currentResponse = undefined;
389
+ this._rawBody = undefined;
390
+ this.stats = {};
391
+ this._clearSocketEventListeners();
392
+ this.parser.reset();
393
+ }
394
+ /**
395
+ * Reports response when redirected.
396
+ */
397
+ _reportRedirect(status) {
398
+ const { request, currentHeaders } = this;
399
+ if (!currentHeaders) {
400
+ return false;
401
+ }
402
+ const rerUrl = currentHeaders.get('location');
403
+ // https://github.com/jarrodek/socket-fetch/issues/13
404
+ const redirectOptions = this.getRedirectOptions(status, request.method, rerUrl);
405
+ if (!redirectOptions.redirect) {
406
+ return false;
407
+ }
408
+ this.redirecting = true;
409
+ setTimeout(() => this._redirectRequest(redirectOptions));
410
+ return true;
411
+ }
412
+ /**
413
+ * Get redirect options based on status and method
414
+ */
415
+ getRedirectOptions(status, method, location) {
416
+ if (!location) {
417
+ return { redirect: false };
418
+ }
419
+ // 301, 302, 303, 307, 308 are redirect status codes
420
+ if (status >= 300 && status < 400) {
421
+ const forceGet = status === 301 || status === 302 || status === 303;
422
+ return { redirect: true, location, forceGet };
423
+ }
424
+ return { redirect: false };
425
+ }
426
+ /**
427
+ * Creates a response and adds it to the redirects list and redirects
428
+ * the request to the new location.
429
+ */
430
+ async _redirectRequest(options) {
431
+ if (this.followRedirects === false) {
432
+ this._publishResponse();
433
+ return;
434
+ }
435
+ const location = options.location && this.getRedirectLocation(options.location, this.request.url);
436
+ if (!location) {
437
+ this._errorRequest({ code: 302 });
438
+ return;
439
+ }
440
+ // check if this is infinite loop
441
+ if (this.isRedirectLoop(location, this.redirects)) {
442
+ this._errorRequest({ code: 310 });
443
+ return;
444
+ }
445
+ const detail = {
446
+ location,
447
+ returnValue: true,
100
448
  };
449
+ this.emit('beforeredirect', detail);
450
+ if (!detail.returnValue) {
451
+ this._publishResponse();
452
+ return;
453
+ }
454
+ try {
455
+ const responseCookies = this.currentHeaders.get('set-cookie');
456
+ const response = await this._createRedirectResponse(location);
457
+ this.redirects.push(response);
458
+ this._cleanUpRedirect();
459
+ if (responseCookies) {
460
+ this._processRedirectCookies(responseCookies, location);
461
+ }
462
+ this.redirecting = false;
463
+ this.request.url = location;
464
+ this.sentRequest.url = location;
465
+ if (options.forceGet) {
466
+ this.request.method = 'GET';
467
+ }
468
+ this.uri = this.readUrl(location);
469
+ this.hostHeader = this.getHostHeader(location);
470
+ // No idea why but without setTimeout the program loses it's
471
+ // scope after calling the function.
472
+ setTimeout(() => this.send());
473
+ }
474
+ catch (e) {
475
+ const error = e;
476
+ this._errorRequest({
477
+ message: (error && error.message) || 'Unknown error occurred',
478
+ });
479
+ }
480
+ }
481
+ /**
482
+ * Get redirect location
483
+ */
484
+ getRedirectLocation(location, currentUrl) {
485
+ return getRedirectLocation(location, currentUrl);
486
+ }
487
+ /**
488
+ * Check if redirect is a loop
489
+ */
490
+ isRedirectLoop(location, redirects) {
491
+ return isRedirectLoop(location, redirects);
492
+ }
493
+ /**
494
+ * @param location The redirect location.
495
+ * @return Redirect response object
496
+ */
497
+ async _createRedirectResponse(location) {
498
+ const { currentResponse = new Response() } = this;
499
+ const response = HttpResponse.fromValues(currentResponse.status, currentResponse.statusText, currentResponse.headers);
500
+ if (currentResponse.payload) {
501
+ response.payload = currentResponse.payload;
502
+ }
503
+ const body = await this.decompress(this._rawBody);
504
+ if (body) {
505
+ await response.writePayload(body);
506
+ currentResponse.payload = response.payload;
507
+ }
508
+ const redirect = ResponseRedirect.fromValues(location, response.toJSON(), this.stats.startTime, this.stats.responseTime);
509
+ redirect.timings = this._computeStats(this.stats);
510
+ return redirect;
511
+ }
512
+ /**
513
+ * Creates a response object
514
+ */
515
+ async _createResponse() {
516
+ if (this.aborted) {
517
+ throw new Error(`Request aborted.`);
518
+ }
519
+ const { currentResponse } = this;
520
+ if (!currentResponse) {
521
+ throw new Error(`Tried to create a response but no response data is set.`);
522
+ }
523
+ const { status } = currentResponse;
524
+ if (status === undefined) {
525
+ throw new Error(`The response status is empty.
526
+ It means that the successful connection wasn't made.
527
+ Check your request parameters.`);
528
+ }
529
+ const body = await this.decompress(this._rawBody);
530
+ const response = Response.fromValues(status, currentResponse.statusText, currentResponse.headers);
531
+ response.timings = this._computeStats(this.stats);
532
+ response.loadingTime = response.timings.total();
533
+ if (body) {
534
+ await response.writePayload(body);
535
+ currentResponse.payload = response.payload;
536
+ }
537
+ if (status === 401) {
538
+ this.logger.silly('Setting auth on the response...');
539
+ response.auth = this._getAuth();
540
+ }
541
+ return response;
542
+ }
543
+ /**
544
+ * Finishes the response with error message.
545
+ */
546
+ _errorRequest(opts) {
547
+ const { currentResponse } = this;
548
+ this.aborted = true;
549
+ let message;
550
+ if (opts.code && !opts.message) {
551
+ message = getCodeMessage(opts.code);
552
+ }
553
+ else if (opts.message) {
554
+ message = opts.message;
555
+ }
556
+ message = message || 'Unknown error occurred';
557
+ const error = new SerializableError(message, opts.code);
558
+ const log = RequestLog.fromRequest(this.sentRequest.toJSON());
559
+ const response = ErrorResponse.fromError(error);
560
+ log.response = response;
561
+ if (currentResponse && currentResponse.status) {
562
+ response.status = currentResponse.status;
563
+ response.statusText = currentResponse.statusText;
564
+ response.headers = currentResponse.headers;
565
+ response.payload = currentResponse.payload;
566
+ }
567
+ this.finalizeRequest(log);
568
+ this._cleanUp();
569
+ }
570
+ /**
571
+ * Generates authorization info object from response.
572
+ */
573
+ _getAuth() {
574
+ this.logger.debug('Getting auth from response headers');
575
+ let auth = this.currentHeaders.get('www-authenticate');
576
+ const result = new ResponseAuthorization();
577
+ if (auth) {
578
+ this.logger.silly('Auth header found', auth);
579
+ auth = auth.toLowerCase();
580
+ if (auth.indexOf('ntlm') !== -1) {
581
+ this.logger.silly('Detected NTLM authorization');
582
+ result.method = 'ntlm';
583
+ }
584
+ else if (auth.indexOf('basic') !== -1) {
585
+ result.method = 'basic';
586
+ this.logger.silly('Detected basic authorization');
587
+ }
588
+ else if (auth.indexOf('digest') !== -1) {
589
+ result.method = 'digest';
590
+ this.logger.silly('Detected digest authorization');
591
+ }
592
+ }
593
+ return result;
594
+ }
595
+ /**
596
+ * Creates HAR 1.2 timings object from stats.
597
+ */
598
+ _computeStats(stats) {
599
+ return computeStats(stats);
600
+ }
601
+ /**
602
+ * Handles cookie exchange when redirecting the request.
603
+ */
604
+ _processRedirectCookies(responseCookies, location) {
605
+ this.request.headers = processRedirectCookies(responseCookies, location, this.request.url, this.request.toHeadersString());
606
+ }
607
+ /**
608
+ * Checks certificate identity using TLS api.
609
+ */
610
+ _checkServerIdentity(host, cert) {
611
+ return checkServerIdentity(host, cert);
612
+ }
613
+ /**
614
+ * Clears event listeners of the socket object,
615
+ */
616
+ _clearSocketEventListeners() {
617
+ if (!this.socket) {
618
+ return;
619
+ }
620
+ this.socket.removeAllListeners('error');
621
+ this.socket.removeAllListeners('timeout');
622
+ this.socket.removeAllListeners('end');
623
+ }
624
+ /**
625
+ * Called with the `send()` function to initialize the main promise returned by the send function.
626
+ * The send function returns a promise that is resolved when the request finish.
627
+ */
628
+ wrapExecution() {
629
+ let promise;
630
+ if (this[mainPromiseSymbol]) {
631
+ promise = this[mainPromiseSymbol];
632
+ }
633
+ else {
634
+ promise = new Promise((resolve, reject) => {
635
+ this.mainResolver = resolve;
636
+ this.mainRejecter = reject;
637
+ });
638
+ this[mainPromiseSymbol] = promise;
639
+ }
640
+ return promise;
641
+ }
642
+ /**
643
+ * Called by the request finalizer or error finalized to report the response.
644
+ */
645
+ finalizeRequest(log) {
646
+ const { mainRejecter, mainResolver } = this;
647
+ if (!mainRejecter || !mainResolver) {
648
+ return;
649
+ }
650
+ if (log instanceof SerializableError) {
651
+ mainRejecter(log);
652
+ }
653
+ else {
654
+ mainResolver(log.toJSON());
655
+ }
656
+ this.mainRejecter = undefined;
657
+ this.mainResolver = undefined;
658
+ this[mainPromiseSymbol] = undefined;
101
659
  }
102
660
  /**
103
661
  * Prepares an HTTP message from API Client's request object.
104
- *
105
- * @returns Resolved promise to an `ArrayBuffer`.
106
662
  */
107
663
  async prepareMessage() {
108
664
  let payload = this.request.payload;
109
- if (['get', 'head'].includes(this.request.method.toLowerCase())) {
665
+ if (isMethodWithoutBody(this.request.method)) {
110
666
  payload = undefined;
111
667
  }
112
668
  const headers = new Headers(this.request.toHeadersString());
113
669
  this.prepareHeaders(headers, !!payload);
114
670
  const auth = this.hasProxy && !this.isProxyTunnel ? this._proxyAuthHeader() : undefined;
115
671
  if (auth) {
116
- headers.set('proxy-authorization', auth);
672
+ headers.set(HEADER_PROXY_AUTHORIZATION, auth);
117
673
  }
118
674
  const buffer = PayloadSupport.payloadToBuffer(headers, payload);
119
675
  if (buffer) {
@@ -121,7 +677,9 @@ export class CoreEngine extends HttpEngine {
121
677
  }
122
678
  this._handleAuthorization(headers);
123
679
  this.sentRequest.headers = headers.toString();
124
- const message = this._prepareMessage(headers, buffer);
680
+ // Update message builder with current host header
681
+ this.messageBuilder.updateHostHeader(this.hostHeader || '');
682
+ const message = this.messageBuilder.buildMessage(headers, buffer);
125
683
  if (this.auth) {
126
684
  // This restores altered by authorization original headers
127
685
  // so it can be safe to use when redirecting
@@ -134,15 +692,13 @@ export class CoreEngine extends HttpEngine {
134
692
  }
135
693
  /**
136
694
  * Sends a data to a socket.
137
- *
138
- * @param buffer HTTP message to send
139
695
  */
140
696
  writeMessage(buffer) {
141
697
  this.logger.debug(`Writing the message to the socket...`);
142
698
  let msg = buffer.toString();
143
699
  const limit = this.opts.sentMessageLimit;
144
700
  if (limit && limit > 0 && msg.length >= limit) {
145
- msg = msg.substr(0, limit);
701
+ msg = msg.substring(0, limit);
146
702
  msg += ' ...';
147
703
  }
148
704
  this.sentRequest.httpMessage = msg;
@@ -164,241 +720,26 @@ export class CoreEngine extends HttpEngine {
164
720
  });
165
721
  });
166
722
  }
167
- /**
168
- * Connects to a server and sends the message.
169
- *
170
- * @returns Promise resolved when socket is connected.
171
- */
172
- async connect() {
173
- const port = getPort(this.uri.port, this.uri.protocol);
174
- const host = this.uri.hostname;
175
- let socket;
176
- if (port === 443 || this.uri.protocol === 'https:') {
177
- socket = await this._connectTls(port, host);
178
- }
179
- else {
180
- socket = await this._connect(port, host);
181
- }
182
- const { timeout } = this;
183
- if (timeout > 0) {
184
- socket.setTimeout(timeout);
185
- }
186
- this.socket = socket;
187
- this._addSocketListeners(socket);
188
- socket.resume();
189
- return socket;
190
- }
191
- /**
192
- * Connects to a server and writes a message using insecure connection.
193
- *
194
- * @param port A port number to connect to.
195
- * @param host A host name to connect to
196
- * @returns A promise resolved when the message was sent to a server
197
- */
198
- _connect(port, host) {
199
- this.logger.debug('Opening an HTTP connection...');
200
- return new Promise((resolve, reject) => {
201
- this.stats.connectionTime = Date.now();
202
- const isIp = net.isIP(host);
203
- if (isIp) {
204
- this.stats.lookupTime = Date.now();
205
- }
206
- const client = net.createConnection(port, host, () => {
207
- this.logger.debug('HTTP connection established.');
208
- this.stats.connectedTime = Date.now();
209
- resolve(client);
210
- });
211
- client.pause();
212
- if (!isIp) {
213
- client.once('lookup', () => {
214
- this.stats.lookupTime = Date.now();
215
- });
216
- }
217
- client.once('error', (err) => reject(err));
218
- });
219
- }
220
- /**
221
- * Connects to a server and writes a message using secured connection.
222
- *
223
- * @param port A port number to connect to.
224
- * @param host A host name to connect to
225
- * @returns A promise resolved when the message was sent to a server
226
- */
227
- _connectTls(port, host) {
228
- this.logger.debug('Opening an SSL connection...');
229
- const { opts } = this;
230
- const options = {};
231
- const isIp = net.isIP(host);
232
- if (!isIp) {
233
- options.servername = host;
234
- }
235
- if (opts.validateCertificates) {
236
- options.checkServerIdentity = this._checkServerIdentity.bind(this);
237
- }
238
- else {
239
- options.rejectUnauthorized = false;
240
- options.checkServerIdentity = () => {
241
- return undefined;
242
- };
243
- // target.requestOCSP = false;
244
- }
245
- const certs = this.opts.certificates;
246
- if (Array.isArray(certs)) {
247
- certs.forEach((cert) => this._addClientCertificate(cert, options));
248
- }
249
- return new Promise((resolve, reject) => {
250
- const time = Date.now();
251
- this.stats.connectionTime = time;
252
- if (isIp) {
253
- this.stats.lookupTime = time;
254
- }
255
- const client = tls.connect(port, host, options, () => {
256
- this.logger.debug('SSL connection established.');
257
- const connectTime = Date.now();
258
- this.stats.connectedTime = connectTime;
259
- this.stats.secureStartTime = connectTime;
260
- resolve(client);
261
- });
262
- client.pause();
263
- client.once('error', (e) => reject(e));
264
- if (!isIp) {
265
- client.once('lookup', () => {
266
- this.stats.lookupTime = Date.now();
267
- });
268
- }
269
- client.once('secureConnect', () => {
270
- this.stats.secureConnectedTime = Date.now();
271
- });
272
- });
273
- }
274
- /**
275
- * Prepares a full HTTP message body
276
- *
277
- * @param httpHeaders The list ogf headers to append.
278
- * @param buffer The buffer with the HTTP message
279
- * @returns The Buffer of the HTTP message
280
- */
281
- _prepareMessage(httpHeaders, buffer) {
282
- this.logger.debug('Preparing an HTTP message...');
283
- const headers = [];
284
- // const search = this.uri.search;
285
- // let path = this.uri.pathname;
286
- // if (search) {
287
- // path += search;
288
- // }
289
- // headers.push(`${this.arcRequest.method} ${path} HTTP/1.1`);
290
- const status = this._createHttpStatus();
291
- this.logger.debug(`Created message status: ${status}`);
292
- headers.push(status);
293
- if (this._hostRequired()) {
294
- this.logger.debug(`Adding the "host" header: ${this.hostHeader}`);
295
- headers.push(`Host: ${this.hostHeader}`);
296
- }
297
- let str = headers.join('\r\n');
298
- const addHeaders = httpHeaders.toString();
299
- if (addHeaders) {
300
- this.logger.debug(`Adding headers to the request...`);
301
- str += '\r\n';
302
- str += PayloadSupport.normalizeString(addHeaders);
303
- }
304
- const startBuffer = Buffer.from(str, 'utf8');
305
- const endBuffer = Buffer.from(new Uint8Array([13, 10, 13, 10]));
306
- let body;
307
- let sum = startBuffer.length + endBuffer.length;
308
- if (buffer) {
309
- sum += buffer.length;
310
- body = Buffer.concat([startBuffer, endBuffer, buffer], sum);
311
- }
312
- else {
313
- body = Buffer.concat([startBuffer, endBuffer], sum);
314
- }
315
- this.logger.debug(`The message is ready.`);
316
- return body;
317
- }
318
- /**
319
- * Creates an HTTP status line for the message.
320
- * For proxy connections it, depending whether target is SSL or not, sets the path
321
- * as the full URL or just the authority.
322
- * @returns The generates status message.
323
- */
324
- _createHttpStatus() {
325
- const { request, uri, hasProxy, isProxyTunnel } = this;
326
- const parts = [request.method];
327
- if (hasProxy && !isProxyTunnel) {
328
- // if (isProxyTunnel) {
329
- // // when a tunnel then the target is over SSL so the default port is 443.
330
- // parts.push(`${uri.hostname}:${uri.port || 443}`);
331
- // } else {
332
- // parts.push(arcRequest.url);
333
- // }
334
- parts.push(request.url);
335
- }
336
- else {
337
- let path = uri.pathname;
338
- if (uri.search) {
339
- path += uri.search;
340
- }
341
- parts.push(path);
342
- }
343
- parts.push('HTTP/1.1');
344
- return parts.join(' ');
345
- }
346
- /**
347
- * Tests if current connection is required to add `host` header.
348
- * It returns `false` only if `host` header has been already provided.
349
- *
350
- * @returns True when the `host` header should be added to the headers list.
351
- */
352
- _hostRequired() {
353
- const headers = this.request.headers;
354
- if (typeof headers !== 'string') {
355
- return true;
356
- }
357
- return !this._hostTestReg.test(headers);
358
- }
359
723
  /**
360
724
  * Alters authorization header depending on the `auth` object
361
- * @param headers A headers object where to append headers when needed
362
725
  */
363
726
  _handleAuthorization(headers) {
364
727
  const { authorization } = this.opts;
365
- const enabled = Array.isArray(authorization) ? authorization.filter((i) => i.enabled) : [];
366
- if (!enabled.length) {
367
- return;
368
- }
369
- const ntlm = enabled.find((i) => i.type === 'ntlm');
370
- if (ntlm) {
371
- this._authorizeNtlm(ntlm.config, headers);
728
+ this.authManager.handleRequestAuthorization(headers, authorization, this.uri.host);
729
+ // Sync the auth state from AuthManager to parent class for compatibility
730
+ const authState = this.authManager.getAuthState();
731
+ if (authState) {
732
+ this.auth = authState;
372
733
  }
373
734
  }
374
735
  /**
375
- * Authorize the request with NTLM
376
- * @param authData Credentials to use
377
- * @param headers A headers object where to append headers if needed
736
+ * Generate proxy authorization header
378
737
  */
379
- _authorizeNtlm(authData, headers) {
380
- const init = { ...authData, url: this.request.url };
381
- const auth = new NtlmAuth(init);
382
- if (!this.auth) {
383
- this.auth = {
384
- method: 'ntlm',
385
- state: 0,
386
- headers: headers.toString(),
387
- };
388
- const msg = auth.createMessage1(this.uri.host);
389
- headers.set('Authorization', `NTLM ${msg.toBase64()}`);
390
- headers.set('Connection', 'keep-alive');
391
- }
392
- else if (this.auth && this.auth.state === 1) {
393
- const msg = auth.createMessage3(this.auth.challengeHeader, this.uri.host);
394
- this.auth.state = 2;
395
- headers.set('Authorization', `NTLM ${msg.toBase64()}`);
396
- }
738
+ _proxyAuthHeader() {
739
+ return this.authManager.generateProxyAuthHeader();
397
740
  }
398
741
  /**
399
742
  * Add event listeners to existing socket.
400
- * @param socket An instance of the socket.
401
- * @return The same socket. Used for chaining.
402
743
  */
403
744
  _addSocketListeners(socket) {
404
745
  let received = false;
@@ -412,9 +753,12 @@ export class CoreEngine extends HttpEngine {
412
753
  }
413
754
  data = Buffer.from(data);
414
755
  try {
415
- this._processSocketMessage(data);
756
+ // Use the new parser instead of the old parsing methods
757
+ this.parser.processData(data);
758
+ this.logger.debug('Parser state:', this.parser.getState());
416
759
  }
417
760
  catch (e) {
761
+ this.logger.error('Error in _addSocketListeners:', e);
418
762
  const err = e;
419
763
  this._errorRequest({
420
764
  message: err.message || 'Unknown error occurred',
@@ -423,50 +767,44 @@ export class CoreEngine extends HttpEngine {
423
767
  }
424
768
  });
425
769
  socket.once('timeout', () => {
426
- this.state = RequestState.Done;
427
- this._errorRequest(new Error('Connection timeout.'));
770
+ this.logger.debug(`Socket timeout occurred after ${this.timeout}ms.`);
771
+ this.parser.abort();
772
+ this._errorRequest(HttpEngineErrorFactory.connectionTimeout(this.timeout));
428
773
  socket.destroy();
429
774
  });
430
775
  socket.on('end', () => {
776
+ const endTime = Date.now();
431
777
  this.logger.debug(`Server connection ended.`);
432
778
  socket.removeAllListeners('timeout');
433
779
  socket.removeAllListeners('error');
434
- const endTime = Date.now();
780
+ socket.removeAllListeners('data');
435
781
  this.stats.lastReceivedTime = endTime;
436
782
  this.sentRequest.endTime = endTime;
437
- if (this.state !== RequestState.Done) {
783
+ // console.log(`Socket ended at ${endTime} in state`, this.parser.getState())
784
+ if (this.parser.getState() !== RequestState.Done) {
438
785
  if (!this.currentResponse) {
439
- this.logger.error(`Connection closed without receiving any data.`);
440
- // The parser haven't found the end of message so there was no message!
441
- const e = new SerializableError('Connection closed without receiving any data', 100);
786
+ const message = `Connection closed without receiving any data.`;
787
+ this.logger.error(message);
788
+ const e = HttpEngineErrorFactory.connectionError(message, 100);
442
789
  this._errorRequest(e);
443
790
  }
444
791
  else {
445
- // There is an issue with the response. Size mismatch? Anyway,
446
- // it tries to create a response from current data.
447
792
  this.emit('loadend');
448
793
  this._publishResponse();
449
794
  }
450
795
  }
451
796
  });
452
797
  socket.once('error', (err) => {
798
+ this.logger.error(`Socket error occurred: ${err.message}`);
453
799
  socket.removeAllListeners('timeout');
454
800
  this._errorRequest(err);
455
801
  });
456
802
  return socket;
457
803
  }
458
- /**
459
- * Processes response message chunk
460
- * @param buffer Message buffer
461
- */
462
- _processResponse(buffer) {
463
- this._processSocketMessage(buffer);
464
- this._reportResponse();
465
- }
466
804
  /**
467
805
  * Reports response after processing it.
468
806
  */
469
- _reportResponse() {
807
+ async _reportResponse() {
470
808
  this._clearSocketEventListeners();
471
809
  const { aborted, currentResponse } = this;
472
810
  if (aborted || !currentResponse) {
@@ -476,607 +814,115 @@ export class CoreEngine extends HttpEngine {
476
814
  const endTime = Date.now();
477
815
  this.stats.lastReceivedTime = endTime;
478
816
  this.sentRequest.endTime = endTime;
479
- if (status >= 300 && status < 400) {
480
- if (this.followRedirects && this._reportRedirect(status)) {
481
- this.closeClient();
482
- return;
483
- }
484
- }
485
- else if (status === 401 && this.auth) {
486
- switch (this.auth.method) {
487
- case 'ntlm':
488
- this.handleNtlmResponse();
817
+ const result = processResponse({
818
+ status,
819
+ currentResponse,
820
+ currentHeaders: this.currentHeaders,
821
+ followRedirects: this.followRedirects,
822
+ auth: this.auth,
823
+ aborted: this.aborted,
824
+ });
825
+ switch (result.action) {
826
+ case 'redirect':
827
+ if (this._reportRedirect(status)) {
828
+ this.closeClient();
489
829
  return;
490
- }
830
+ }
831
+ break;
832
+ case 'auth':
833
+ if (result.data?.method === 'ntlm') {
834
+ await this.handleNtlmResponse();
835
+ return;
836
+ }
837
+ break;
838
+ case 'publish':
839
+ this.closeClient();
840
+ this.emit('loadend');
841
+ this._publishResponse();
842
+ break;
843
+ case 'abort':
844
+ // Already aborted, do nothing
845
+ break;
491
846
  }
492
- this.closeClient();
493
- this.emit('loadend');
494
- this._publishResponse();
495
847
  }
496
848
  /**
497
849
  * Generate response object and publish it to the listeners.
498
850
  */
499
- _publishResponse() {
500
- this.state = RequestState.Done;
501
- if (!this._rawBody) {
502
- if (this.responseInfo.body) {
503
- this._rawBody = this.responseInfo.body;
504
- }
505
- else if (this.responseInfo.chunk) {
506
- this._rawBody = this.responseInfo.chunk;
507
- }
851
+ async _publishResponse() {
852
+ if (this.aborted) {
853
+ return;
508
854
  }
509
- return super._publishResponse();
510
- }
511
- /**
512
- * @param location The redirect location.
513
- * @return Redirect response object
514
- */
515
- async _createRedirectResponse(location) {
516
- const { currentResponse = new Response() } = this;
517
- this.currentResponse = currentResponse;
518
- if (!this.currentResponse.payload) {
519
- if (this._rawBody) {
520
- this.currentResponse.payload = PayloadSerializer.stringifyBuffer(this._rawBody);
521
- }
522
- else if (this.responseInfo.body) {
523
- this.currentResponse.payload = PayloadSerializer.stringifyBuffer(this.responseInfo.body);
524
- }
525
- else if (this.responseInfo.chunk) {
526
- this.currentResponse.payload = PayloadSerializer.stringifyBuffer(this.responseInfo.chunk);
855
+ try {
856
+ const response = await this._createResponse();
857
+ const result = RequestLog.fromRequestResponse(this.sentRequest.toJSON(), response.toJSON());
858
+ if (this.redirects.length) {
859
+ result.redirects = this.redirects;
860
+ }
861
+ result.size = new RequestsSize();
862
+ if (this.sentRequest.httpMessage) {
863
+ result.size.request = Buffer.from(this.sentRequest.httpMessage).length;
864
+ }
865
+ if (response.payload) {
866
+ if (typeof response.payload === 'string') {
867
+ result.size.response = response.payload.length;
868
+ }
869
+ else {
870
+ result.size.response = response.payload.data.length;
871
+ }
527
872
  }
873
+ this.finalizeRequest(result);
528
874
  }
529
- return super._createRedirectResponse(location);
875
+ catch (e) {
876
+ const error = e;
877
+ this.logger.error('Error in _publishResponse:', error);
878
+ this._errorRequest({
879
+ message: (error && error.message) || 'Unknown error occurred',
880
+ });
881
+ }
882
+ this.abort();
883
+ this._cleanUp();
530
884
  }
531
885
  /**
532
886
  * closes the connection, if any
533
887
  */
534
888
  closeClient() {
535
889
  if (this.socket && !this.socket.destroyed) {
890
+ this.logger.debug('Closing the socket connection...');
536
891
  this.socket.destroy();
537
892
  }
538
893
  }
539
894
  /**
540
895
  * Handles the response with NTLM authorization
541
896
  */
542
- handleNtlmResponse() {
543
- const { auth } = this;
544
- if (!auth) {
545
- throw new Error('No auth data.');
546
- }
547
- if (auth.state === 0) {
548
- if (this.currentHeaders.has('www-authenticate')) {
549
- auth.state = 1;
550
- auth.challengeHeader = this.currentHeaders.get('www-authenticate');
551
- this._cleanUpRedirect();
552
- this.prepareMessage().then((message) => this.writeMessage(message));
553
- return;
897
+ async handleNtlmResponse() {
898
+ this.logger.debug('Handling NTLM response...');
899
+ this.logger.debug('Current headers:', this.currentHeaders?.toString());
900
+ this.logger.debug('Auth state:', this.authManager.getAuthState());
901
+ const shouldRetry = this.authManager.handleNtlmResponse(this.currentHeaders);
902
+ this.logger.debug('Should retry NTLM:', shouldRetry);
903
+ if (shouldRetry) {
904
+ // Update the parent auth state to match AuthManager state
905
+ this.auth = this.authManager.getAuthState();
906
+ this._cleanUpRedirect();
907
+ try {
908
+ const message = await this.prepareMessage();
909
+ await this.writeMessage(message);
910
+ // Don't return here - let the socket listeners handle the response
911
+ }
912
+ catch (error) {
913
+ this.logger.error('Failed to retry NTLM authentication:', error);
914
+ this._errorRequest({
915
+ message: `NTLM retry failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
916
+ });
554
917
  }
918
+ return;
555
919
  }
920
+ this.logger.debug('NTLM authentication completed');
921
+ // Clear both auth states
922
+ this.authManager.clearAuthState();
556
923
  delete this.auth;
557
924
  this.emit('loadend');
558
925
  this._publishResponse();
559
926
  }
560
- /**
561
- * Process received message.
562
- *
563
- * @param data Received message.
564
- */
565
- _processSocketMessage(data) {
566
- if (this.aborted) {
567
- return;
568
- }
569
- if (this.state === RequestState.Done) {
570
- return;
571
- }
572
- let remaining = data;
573
- if (this.state === RequestState.Status) {
574
- remaining = this._processStatus(remaining);
575
- if (!remaining) {
576
- return;
577
- }
578
- }
579
- if (this.state === RequestState.Headers) {
580
- remaining = this._processHeaders(remaining);
581
- if (!remaining) {
582
- return;
583
- }
584
- }
585
- if (this.state === RequestState.Body) {
586
- this._processBody(remaining);
587
- return;
588
- }
589
- }
590
- /**
591
- * Read status line from the response.
592
- * This function will set `status` and `statusText` fields
593
- * and then will set `state` to HEADERS.
594
- *
595
- * @param data The received data
596
- */
597
- _processStatus(data) {
598
- if (this.aborted) {
599
- return;
600
- }
601
- const response = Response.fromValues(0);
602
- response.loadingTime = 0;
603
- this.currentResponse = response;
604
- if (!data) {
605
- return;
606
- }
607
- this.logger.info('Processing status');
608
- const index = data.indexOf(nlBuffer);
609
- let statusLine = data.slice(0, index).toString();
610
- data = data.slice(index + 2);
611
- statusLine = statusLine.replace(/HTTP\/\d(\.\d)?\s/, '');
612
- const delimiterPos = statusLine.indexOf(' ');
613
- let status;
614
- let msg = '';
615
- if (delimiterPos === -1) {
616
- status = statusLine;
617
- }
618
- else {
619
- status = statusLine.substr(0, delimiterPos);
620
- msg = statusLine.substr(delimiterPos + 1);
621
- }
622
- let typedStatus = Number(status);
623
- if (Number.isNaN(typedStatus)) {
624
- typedStatus = 0;
625
- }
626
- if (msg && msg.indexOf('\n') !== -1) {
627
- msg = msg.split('\n')[0];
628
- }
629
- const cr = this.currentResponse;
630
- cr.status = typedStatus;
631
- cr.statusText = msg;
632
- this.logger.info('Received status', typedStatus, msg);
633
- this.state = RequestState.Headers;
634
- return data;
635
- }
636
- /**
637
- * Read headers from the received data.
638
- *
639
- * @param data Received data
640
- * @returns Remaining data in the buffer.
641
- */
642
- _processHeaders(data) {
643
- if (this.aborted) {
644
- return;
645
- }
646
- if (!data) {
647
- this._parseHeaders();
648
- return;
649
- }
650
- this.logger.info('Processing headers');
651
- // Looking for end of headers section
652
- let index = data.indexOf(nlNlBuffer);
653
- let padding = 4;
654
- if (index === -1) {
655
- // It can also be 2x ASCII 10
656
- const _index = data.indexOf(Buffer.from([10, 10]));
657
- if (_index !== -1) {
658
- index = _index;
659
- padding = 2;
660
- }
661
- }
662
- // https://github.com/jarrodek/socket-fetch/issues/3
663
- const enterIndex = data.indexOf(nlBuffer);
664
- if (index === -1 && enterIndex !== 0) {
665
- // end in next chunk
666
- if (!this.rawHeaders) {
667
- this.rawHeaders = data;
668
- }
669
- else {
670
- const sum = this.rawHeaders.length + data.length;
671
- this.rawHeaders = Buffer.concat([this.rawHeaders, data], sum);
672
- }
673
- return;
674
- }
675
- if (enterIndex !== 0) {
676
- const headersArray = data.slice(0, index);
677
- if (!this.rawHeaders) {
678
- this.rawHeaders = headersArray;
679
- }
680
- else {
681
- const sum = this.rawHeaders.length + headersArray.length;
682
- this.rawHeaders = Buffer.concat([this.rawHeaders, headersArray], sum);
683
- }
684
- }
685
- this._parseHeaders(this.rawHeaders);
686
- delete this.rawHeaders;
687
- this.state = RequestState.Body;
688
- const start = index === -1 ? 0 : index;
689
- const move = enterIndex === 0 ? 2 : padding;
690
- data = data.slice(start + move);
691
- return this._postHeaders(data);
692
- }
693
- /**
694
- * Check the response headers and end the request if necessary.
695
- * @param data Current response data buffer
696
- */
697
- _postHeaders(data) {
698
- if (this.request.method === 'HEAD') {
699
- this._reportResponse();
700
- return;
701
- }
702
- if (data.length === 0) {
703
- if (this.currentResponse?.status === 204) {
704
- this._reportResponse();
705
- return;
706
- }
707
- if (this.currentHeaders.has('Content-Length')) {
708
- // If the server do not close the connection and clearly indicate that
709
- // there are no further data to receive the app can close the connection
710
- // and prepare the response.
711
- const length = Number(this.currentHeaders.get('Content-Length'));
712
- if (!Number.isNaN(length) && length === 0) {
713
- this._reportResponse();
714
- return;
715
- }
716
- }
717
- // See: https://github.com/advanced-rest-client/arc-electron/issues/106
718
- // The client should wait until the connection is closed instead of assuming it should end the request.
719
- // else if (!this.currentHeaders.has('Transfer-Encoding') || !this.currentHeaders.get('Transfer-Encoding')) {
720
- // // Fix for https://github.com/jarrodek/socket-fetch/issues/6
721
- // // There is no body in the response.
722
- // // this._reportResponse();
723
- // return;
724
- // }
725
- return;
726
- }
727
- return data;
728
- }
729
- /**
730
- * This function assumes that all headers has been read and it's
731
- * just before changing the status to BODY.
732
- */
733
- _parseHeaders(buffer) {
734
- let raw = '';
735
- if (buffer) {
736
- raw = buffer.toString();
737
- }
738
- ;
739
- this.currentResponse.headers = raw;
740
- this.logger.info('Received headers list', raw);
741
- const headers = new Headers(raw);
742
- this.currentHeaders = headers;
743
- if (headers.has('Content-Length')) {
744
- this.responseInfo.contentLength = Number(headers.get('Content-Length'));
745
- }
746
- if (headers.has('Transfer-Encoding')) {
747
- const tr = headers.get('Transfer-Encoding');
748
- if (tr === 'chunked') {
749
- this.responseInfo.chunked = true;
750
- }
751
- }
752
- const rawHeaders = headers.toString();
753
- const detail = {
754
- returnValue: true,
755
- value: rawHeaders,
756
- };
757
- this.emit('headersreceived', detail);
758
- if (!detail.returnValue) {
759
- this.abort();
760
- }
761
- }
762
- /**
763
- * @param data A data to process
764
- */
765
- _processBody(data) {
766
- if (this.aborted || !data) {
767
- return;
768
- }
769
- if (this.responseInfo.chunked) {
770
- this._processBodyChunked(data);
771
- }
772
- else {
773
- this._processBodyContentLength(data);
774
- }
775
- }
776
- _processBodyContentLength(data) {
777
- if (typeof this.responseInfo.contentLength === 'undefined') {
778
- this._errorRequest(new Error(`The content-length header of the response is missing.`));
779
- return;
780
- }
781
- if (!this.responseInfo.body) {
782
- this.responseInfo.body = data;
783
- if (data.length >= this.responseInfo.contentLength) {
784
- this._reportResponse();
785
- return;
786
- }
787
- return;
788
- }
789
- const sum = this.responseInfo.body.length + data.length;
790
- this.responseInfo.body = Buffer.concat([this.responseInfo.body, data], sum);
791
- if (this.responseInfo.body.length >= this.responseInfo.contentLength) {
792
- this._reportResponse();
793
- return;
794
- }
795
- }
796
- /**
797
- * @param data A latest data to process
798
- */
799
- _processBodyChunked(data) {
800
- if (!data) {
801
- return;
802
- }
803
- if (this.responseInfo.chunk) {
804
- data = Buffer.concat([this.responseInfo.chunk, data], this.responseInfo.chunk.length + data.length);
805
- this.responseInfo.chunk = undefined;
806
- }
807
- while (true) {
808
- if (this.responseInfo.chunkSize === 0 && data.indexOf(nlNlBuffer) === 0) {
809
- this._reportResponse();
810
- return;
811
- }
812
- if (!this.responseInfo.chunkSize) {
813
- data = this.readChunkSize(data);
814
- if (!data) {
815
- return;
816
- }
817
- if (!this.responseInfo.chunkSize && this.responseInfo.chunkSize !== 0) {
818
- // It may happen that node's buffer cuts the data
819
- // just before the chunk size.
820
- // It should proceed it in next portion of the data.
821
- this.responseInfo.chunk = data;
822
- return;
823
- }
824
- if (!this.responseInfo.chunkSize) {
825
- this._reportResponse();
826
- return;
827
- }
828
- }
829
- const size = Math.min(this.responseInfo.chunkSize, data.length);
830
- const sliced = data.slice(0, size);
831
- if (!this._rawBody) {
832
- this._rawBody = sliced;
833
- }
834
- else {
835
- const sum = size + this._rawBody.length;
836
- this._rawBody = Buffer.concat([this._rawBody, sliced], sum);
837
- }
838
- this.responseInfo.chunkSize -= size;
839
- if (data.length === 0) {
840
- // this.logger.warn('Next chunk will start with CRLF!');
841
- return;
842
- }
843
- data = data.slice(size + 2); // + CR
844
- if (data.length === 0) {
845
- // this.logger.info('No more data here. Waiting for new chunk');
846
- return;
847
- }
848
- }
849
- }
850
- /**
851
- * If response's Transfer-Encoding is 'chunked' read until next CR.
852
- * Everything before it is a chunk size.
853
- */
854
- readChunkSize(array) {
855
- if (this.aborted) {
856
- return;
857
- }
858
- let index = array.indexOf(nlBuffer);
859
- if (index === -1) {
860
- // not found in this portion of data.
861
- return array;
862
- }
863
- if (index === 0) {
864
- // Node's buffer cuts CRLF after the end of chunk data, without last CLCR,
865
- // here's to fix it.
866
- // It can be either new line from the last chunk or end of
867
- // the message where
868
- // the rest of the array is [13, 10, 48, 13, 10, 13, 10]
869
- if (array.indexOf(nlNlBuffer) === 0) {
870
- this.responseInfo.chunkSize = 0;
871
- return Buffer.alloc(0);
872
- }
873
- array = array.slice(index + 2);
874
- index = array.indexOf(nlBuffer);
875
- }
876
- // this.logger.info('Size index: ', index);
877
- const chunkSize = parseInt(array.slice(0, index).toString(), 16);
878
- if (Number.isNaN(chunkSize)) {
879
- this.responseInfo.chunkSize = undefined;
880
- return array.slice(index + 2);
881
- }
882
- this.responseInfo.chunkSize = chunkSize;
883
- return array.slice(index + 2);
884
- }
885
- /**
886
- * Connects to a server through a proxy. Depending on the proxy type the returned socket
887
- * is a socket created after creating a tunnel (SSL) or the proxy socket.
888
- *
889
- * @returns Promise resolved when socket is connected.
890
- */
891
- async connectProxy() {
892
- let socket;
893
- if (this.isProxyTunnel) {
894
- socket = await this.connectTunnel(this.isProxySsl);
895
- }
896
- else {
897
- socket = await this.proxyHttp(this.isProxySsl);
898
- }
899
- if (!socket) {
900
- return;
901
- }
902
- const { timeout } = this;
903
- if (timeout > 0) {
904
- socket.setTimeout(timeout);
905
- }
906
- this.socket = socket;
907
- this._addSocketListeners(socket);
908
- socket.resume();
909
- return socket;
910
- }
911
- /**
912
- * Creates a tunnel to a Proxy for SSL connections.
913
- * The returned socket is the one created after the tunnel is established.
914
- * @param proxyIsSsl Whether the proxy is an SSL connection.
915
- * @returns Promise resolved when socket is connected.
916
- */
917
- async connectTunnel(proxyIsSsl = false) {
918
- this.logger.debug(`Creating a tunnel through the proxy...`);
919
- const { proxy } = this.opts;
920
- const { url } = this.request;
921
- if (!proxy) {
922
- throw new Error(`No proxy configuration found.`);
923
- }
924
- let proxyUrl = proxy;
925
- if (proxyIsSsl && !proxyUrl.startsWith('https:')) {
926
- proxyUrl = `https://${proxyUrl}`;
927
- }
928
- else if (!proxyIsSsl && !proxyUrl.startsWith('http:')) {
929
- proxyUrl = `http://${proxyUrl}`;
930
- }
931
- const proxyUri = new URL(proxyUrl);
932
- const targetUrl = new URL(url);
933
- const proxyPort = proxyUri.port || (proxyIsSsl ? 443 : 80);
934
- const targetPort = targetUrl.port || 443; // target is always SSL so 443.
935
- const authority = `${targetUrl.hostname}:${targetPort}`;
936
- const connectOptions = {
937
- host: proxyUri.hostname,
938
- port: proxyPort,
939
- method: 'CONNECT',
940
- path: authority,
941
- headers: {
942
- host: authority,
943
- },
944
- };
945
- if (proxyIsSsl) {
946
- connectOptions.rejectUnauthorized = false;
947
- // @ts-expect-error This is correct!
948
- connectOptions.requestOCSP = false;
949
- }
950
- const auth = this._proxyAuthHeader();
951
- if (auth) {
952
- this.logger.debug(`Adding proxy authorization.`);
953
- connectOptions.headers['proxy-authorization'] = auth;
954
- }
955
- const lib = proxyIsSsl ? https : http;
956
- return new Promise((resolve, reject) => {
957
- this.stats.connectionTime = Date.now();
958
- const connectRequest = lib.request(connectOptions);
959
- connectRequest.once('socket', (socket) => {
960
- socket.on('lookup', () => {
961
- this.stats.lookupTime = Date.now();
962
- });
963
- });
964
- connectRequest.on('error', (err) => {
965
- this.logger.error('Error establishing proxy tunnel:', err);
966
- reject(new SerializableError(`Failed to establish proxy tunnel: ${err.message}`, err.code ? Number(err.code) : 111));
967
- });
968
- connectRequest.on('connect', async (res, socket, head) => {
969
- const time = Date.now();
970
- this.stats.connectedTime = time;
971
- this.stats.secureStartTime = time;
972
- if (typeof this.stats.lookupTime === 'undefined') {
973
- this.stats.lookupTime = time;
974
- }
975
- if (res.statusCode === 401) {
976
- this.currentHeaders = new Headers(res.headers);
977
- const currentResponse = Response.fromValues(res.statusCode, res.statusMessage, this.currentHeaders.toString());
978
- currentResponse.loadingTime = 0;
979
- this.currentResponse = currentResponse;
980
- if (head.length) {
981
- this._rawBody = head;
982
- currentResponse.payload = PayloadSerializer.stringifyBuffer(head);
983
- }
984
- connectRequest.destroy();
985
- resolve(undefined);
986
- setTimeout(() => {
987
- // const e = new NetError('The proxy requires authentication.', 127);
988
- this._publishResponse();
989
- });
990
- }
991
- else if (res.statusCode === 407) {
992
- await this.handleProxyAuthentication(res, socket);
993
- }
994
- else if (res.statusCode !== 200) {
995
- this.logger.debug(`The proxy tunnel ended with ${res.statusCode} status code. Erroring request.`);
996
- connectRequest.destroy();
997
- const errorMessage = res.statusMessage || `Proxy tunnel establishment failed with status code ${res.statusCode}`;
998
- const e = new SerializableError(errorMessage, res.statusCode);
999
- reject(e);
1000
- }
1001
- else {
1002
- this.logger.debug(`Established a proxy tunnel.`);
1003
- this.logger.debug(`Upgrading connection to SSL...`);
1004
- const tlsSocket = tls.connect({ socket, rejectUnauthorized: false }, () => {
1005
- this.logger.debug(`Connection upgraded to SSL.`);
1006
- resolve(tlsSocket);
1007
- });
1008
- tlsSocket.once('secureConnect', () => {
1009
- this.stats.secureConnectedTime = Date.now();
1010
- });
1011
- }
1012
- });
1013
- connectRequest.end();
1014
- });
1015
- }
1016
- async handleProxyAuthentication(res, socket) {
1017
- if (res.statusCode === 407) {
1018
- // Proxy Authentication Required
1019
- this.currentHeaders = new Headers(res.headers);
1020
- const authHeader = this.currentHeaders.get('proxy-authenticate');
1021
- if (authHeader && authHeader.startsWith('Digest')) {
1022
- // Parse the Digest challenge and update _proxyAuthHeader to generate the correct response.
1023
- this._errorRequest(new SerializableError(`Unsupported proxy authentication scheme: ${authHeader}`, 127));
1024
- socket.destroy();
1025
- // this._cleanUpRedirect(); // Might need adjustments
1026
- // const message = await this.prepareMessage();
1027
- // this.writeMessage(message);
1028
- }
1029
- else if (authHeader && authHeader.startsWith('Basic')) {
1030
- // Basic auth is usually handled directly in _proxyAuthHeader, so this might be redundant.
1031
- this._cleanUpRedirect(); // Might need adjustments
1032
- const message = await this.prepareMessage();
1033
- this.writeMessage(message);
1034
- }
1035
- else {
1036
- // Unsupported authentication scheme
1037
- this._errorRequest(new SerializableError(`Unsupported proxy authentication scheme: ${authHeader}`, 127));
1038
- socket.destroy();
1039
- }
1040
- }
1041
- }
1042
- /**
1043
- * Creates connection to a proxy for an HTTP (non-SSL) transport.
1044
- * This is the same as calling _connect or _connectTls but the target is the proxy and not the
1045
- * target URL. The message sent to the proxy server is different than the one sent
1046
- * to the target.
1047
- * @param proxyIsSsl
1048
- * @returns Promise resolved when socket is connected.
1049
- */
1050
- async proxyHttp(proxyIsSsl = false) {
1051
- this.logger.debug('Proxying an HTTP request...');
1052
- const { proxy } = this.opts;
1053
- if (!proxy) {
1054
- throw new Error(`No proxy configuration found.`);
1055
- }
1056
- let proxyUrl = proxy;
1057
- if (proxyIsSsl && !proxyUrl.startsWith('https:')) {
1058
- proxyUrl = `https://${proxyUrl}`;
1059
- }
1060
- else if (!proxyIsSsl && !proxyUrl.startsWith('http:')) {
1061
- proxyUrl = `http://${proxyUrl}`;
1062
- }
1063
- const proxyUri = new URL(proxyUrl);
1064
- const port = Number(proxyUri.port || 443);
1065
- const host = proxyUri.hostname;
1066
- let socket;
1067
- try {
1068
- if (proxyIsSsl) {
1069
- socket = await this._connectTls(port, host);
1070
- }
1071
- else {
1072
- socket = await this._connect(port, host);
1073
- }
1074
- }
1075
- catch (error) {
1076
- const err = error;
1077
- throw new SerializableError(`Failed to connect to proxy: ${err.message}`, err.code ? Number(err.code) : 112);
1078
- }
1079
- return socket;
1080
- }
1081
927
  }
1082
928
  //# sourceMappingURL=CoreEngine.js.map