(agent, socket, options, tunnelConfig, afterSocket)
| 211 | }; |
| 212 | |
| 213 | function establishTunnel(agent, socket, options, tunnelConfig, afterSocket) { |
| 214 | const { proxyTunnelPayload } = tunnelConfig; |
| 215 | // By default, the socket is in paused mode. Read to look for the 200 |
| 216 | // connection established response. |
| 217 | function read() { |
| 218 | let chunk; |
| 219 | while ((chunk = socket.read()) !== null) { |
| 220 | if (onProxyData(chunk) !== -1) { |
| 221 | break; |
| 222 | } |
| 223 | } |
| 224 | socket.on('readable', read); |
| 225 | } |
| 226 | |
| 227 | function cleanup() { |
| 228 | socket.removeListener('end', onProxyEnd); |
| 229 | socket.removeListener('error', onProxyError); |
| 230 | socket.removeListener('readable', read); |
| 231 | socket.setTimeout(0); // Clear the timeout for the tunnel establishment. |
| 232 | } |
| 233 | |
| 234 | function onProxyError(err) { |
| 235 | debug('onProxyError', err); |
| 236 | cleanup(); |
| 237 | afterSocket(err, socket); |
| 238 | } |
| 239 | |
| 240 | // Read the headers from the chunks and check for the status code. If it fails we |
| 241 | // clean up the socket and return an error. Otherwise we establish the tunnel. |
| 242 | let buffer = ''; |
| 243 | function onProxyData(chunk) { |
| 244 | const str = chunk.toString(); |
| 245 | debug('onProxyData', str); |
| 246 | buffer += str; |
| 247 | const headerEndIndex = buffer.indexOf('\r\n\r\n'); |
| 248 | if (headerEndIndex === -1) return headerEndIndex; |
| 249 | const statusLine = buffer.substring(0, buffer.indexOf('\r\n')); |
| 250 | const statusCode = statusLine.split(' ')[1]; |
| 251 | if (statusCode !== '200') { |
| 252 | debug(`onProxyData receives ${statusCode}, cleaning up`); |
| 253 | cleanup(); |
| 254 | const targetHost = proxyTunnelPayload.split('\r')[0].split(' ')[1]; |
| 255 | const message = `Failed to establish tunnel to ${targetHost} via ${agent[kProxyConfig].href}: ${statusLine}`; |
| 256 | const err = new ERR_PROXY_TUNNEL(message); |
| 257 | err.statusCode = NumberParseInt(statusCode); |
| 258 | afterSocket(err, socket); |
| 259 | } else { |
| 260 | // https://datatracker.ietf.org/doc/html/rfc9110#CONNECT |
| 261 | // RFC 9110 says that it can be 2xx but in the real world, proxy clients generally only |
| 262 | // accepts 200. |
| 263 | // Proxy servers are not supposed to send anything after the headers - the payload must be |
| 264 | // be empty. So after this point we will proceed with the tunnel e.g. starting TLS handshake. |
| 265 | debug('onProxyData receives 200, establishing tunnel'); |
| 266 | cleanup(); |
| 267 | |
| 268 | // Reuse the tunneled socket to perform the TLS handshake with the endpoint, |
| 269 | // then send the request. |
| 270 | const { requestOptions } = tunnelConfig; |
no test coverage detected
searching dependent graphs…