(response: IncomingMessageWithTimings)
| 957 | } |
| 958 | |
| 959 | private async _onResponseBase(response: IncomingMessageWithTimings): Promise<void> { |
| 960 | // This will be called e.g. when using cache so we need to check if this request has been aborted. |
| 961 | if (this.isAborted) { |
| 962 | return; |
| 963 | } |
| 964 | |
| 965 | const {options} = this; |
| 966 | const {url} = options; |
| 967 | |
| 968 | const nativeResponse = response as IncomingMessageWithTimings & {fromCache?: boolean}; |
| 969 | const statusCode = response.statusCode!; |
| 970 | const {method} = options; |
| 971 | const redirectLocationHeader = response.headers.location; |
| 972 | const redirectLocation = Array.isArray(redirectLocationHeader) ? redirectLocationHeader[0] : redirectLocationHeader; |
| 973 | const isRedirect = Boolean(redirectLocation && redirectCodes.has(statusCode)); |
| 974 | |
| 975 | // Skip decompression for responses that must not have bodies per RFC 9110: |
| 976 | // - HEAD responses (any status code) |
| 977 | // - 1xx (Informational): 100, 101, 102, 103, etc. |
| 978 | // - 204 (No Content) |
| 979 | // - 205 (Reset Content) |
| 980 | // - 304 (Not Modified) |
| 981 | const hasNoBody = method === 'HEAD' |
| 982 | || (statusCode >= 100 && statusCode < 200) |
| 983 | || statusCode === 204 |
| 984 | || statusCode === 205 |
| 985 | || statusCode === 304; |
| 986 | |
| 987 | const prepareResponse = (response: PlainResponse): PlainResponse => { |
| 988 | if (!Object.hasOwn(response, 'headers')) { |
| 989 | Object.defineProperty(response, 'headers', { |
| 990 | value: response.headers, |
| 991 | enumerable: true, |
| 992 | writable: true, |
| 993 | configurable: true, |
| 994 | }); |
| 995 | } |
| 996 | |
| 997 | response.statusMessage ||= http.STATUS_CODES[statusCode]; // eslint-disable-line @typescript-eslint/prefer-nullish-coalescing -- The status message can be empty. |
| 998 | response.url = stripUrlAuth(options.url!); |
| 999 | response.requestUrl = this.requestUrl!; |
| 1000 | response.redirectUrls = this.redirectUrls; |
| 1001 | response.request = this; |
| 1002 | response.isFromCache = nativeResponse.fromCache ?? false; |
| 1003 | response.ip = this.ip; |
| 1004 | response.retryCount = this.retryCount; |
| 1005 | response.ok = isResponseOk(response); |
| 1006 | |
| 1007 | return response; |
| 1008 | }; |
| 1009 | |
| 1010 | let typedResponse = prepareResponse(response as PlainResponse); |
| 1011 | // Redirect responses that will be followed are drained raw. Decompressing them can |
| 1012 | // turn an irrelevant redirect body into a client-side failure or decompression DoS. |
| 1013 | const shouldFollowRedirect = isRedirect && (typeof options.followRedirect === 'function' ? options.followRedirect(typedResponse) : options.followRedirect); |
| 1014 | |
| 1015 | if (options.decompress && !hasNoBody && !shouldFollowRedirect) { |
| 1016 | // When strictContentLength is enabled, track compressed bytes by listening to |
no test coverage detected