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