11cb0ef41Sopenharmony_ci'use strict';
21cb0ef41Sopenharmony_ci
31cb0ef41Sopenharmony_ciconst common = require('../common');
41cb0ef41Sopenharmony_ciconst fixtures = require('../common/fixtures');
51cb0ef41Sopenharmony_ciif (!common.hasCrypto)
61cb0ef41Sopenharmony_ci  common.skip('missing crypto');
71cb0ef41Sopenharmony_ciconst assert = require('assert');
81cb0ef41Sopenharmony_ciconst net = require('net');
91cb0ef41Sopenharmony_ciconst tls = require('tls');
101cb0ef41Sopenharmony_ciconst h2 = require('http2');
111cb0ef41Sopenharmony_ci
121cb0ef41Sopenharmony_ci// This test sets up an H2 proxy server, and tunnels a request over one of its streams
131cb0ef41Sopenharmony_ci// back to itself, via TLS, and then closes the TLS connection. On some Node versions
141cb0ef41Sopenharmony_ci// (v18 & v20 up to 20.5.1) the resulting JS Stream Socket fails to shutdown correctly
151cb0ef41Sopenharmony_ci// in this case, and crashes due to a null pointer in finishShutdown.
161cb0ef41Sopenharmony_ci
171cb0ef41Sopenharmony_ciconst tlsOptions = {
181cb0ef41Sopenharmony_ci  key: fixtures.readKey('agent1-key.pem'),
191cb0ef41Sopenharmony_ci  cert: fixtures.readKey('agent1-cert.pem'),
201cb0ef41Sopenharmony_ci  ALPNProtocols: ['h2']
211cb0ef41Sopenharmony_ci};
221cb0ef41Sopenharmony_ci
231cb0ef41Sopenharmony_ciconst netServer = net.createServer((socket) => {
241cb0ef41Sopenharmony_ci  socket.allowHalfOpen = false;
251cb0ef41Sopenharmony_ci  // ^ This allows us to trigger this reliably, but it's not strictly required
261cb0ef41Sopenharmony_ci  // for the bug and crash to happen, skipping this just fails elsewhere later.
271cb0ef41Sopenharmony_ci
281cb0ef41Sopenharmony_ci  h2Server.emit('connection', socket);
291cb0ef41Sopenharmony_ci});
301cb0ef41Sopenharmony_ci
311cb0ef41Sopenharmony_ciconst h2Server = h2.createSecureServer(tlsOptions, (req, res) => {
321cb0ef41Sopenharmony_ci  res.writeHead(200);
331cb0ef41Sopenharmony_ci  res.end();
341cb0ef41Sopenharmony_ci});
351cb0ef41Sopenharmony_ci
361cb0ef41Sopenharmony_cih2Server.on('connect', (req, res) => {
371cb0ef41Sopenharmony_ci  res.writeHead(200, {});
381cb0ef41Sopenharmony_ci  netServer.emit('connection', res.stream);
391cb0ef41Sopenharmony_ci});
401cb0ef41Sopenharmony_ci
411cb0ef41Sopenharmony_cinetServer.listen(0, common.mustCall(() => {
421cb0ef41Sopenharmony_ci  const proxyClient = h2.connect(`https://localhost:${netServer.address().port}`, {
431cb0ef41Sopenharmony_ci    rejectUnauthorized: false
441cb0ef41Sopenharmony_ci  });
451cb0ef41Sopenharmony_ci
461cb0ef41Sopenharmony_ci  const proxyReq = proxyClient.request({
471cb0ef41Sopenharmony_ci    ':method': 'CONNECT',
481cb0ef41Sopenharmony_ci    ':authority': 'example.com:443'
491cb0ef41Sopenharmony_ci  });
501cb0ef41Sopenharmony_ci
511cb0ef41Sopenharmony_ci  proxyReq.on('response', common.mustCall((response) => {
521cb0ef41Sopenharmony_ci    assert.strictEqual(response[':status'], 200);
531cb0ef41Sopenharmony_ci
541cb0ef41Sopenharmony_ci    // Create a TLS socket within the tunnel, and start sending a request:
551cb0ef41Sopenharmony_ci    const tlsSocket = tls.connect({
561cb0ef41Sopenharmony_ci      socket: proxyReq,
571cb0ef41Sopenharmony_ci      ALPNProtocols: ['h2'],
581cb0ef41Sopenharmony_ci      rejectUnauthorized: false
591cb0ef41Sopenharmony_ci    });
601cb0ef41Sopenharmony_ci
611cb0ef41Sopenharmony_ci    proxyReq.on('close', common.mustCall(() => {
621cb0ef41Sopenharmony_ci      proxyClient.close();
631cb0ef41Sopenharmony_ci      netServer.close();
641cb0ef41Sopenharmony_ci    }));
651cb0ef41Sopenharmony_ci
661cb0ef41Sopenharmony_ci    // Forcibly kill the TLS socket
671cb0ef41Sopenharmony_ci    tlsSocket.destroy();
681cb0ef41Sopenharmony_ci
691cb0ef41Sopenharmony_ci    // This results in an async error in affected Node versions, before the 'close' event
701cb0ef41Sopenharmony_ci  }));
711cb0ef41Sopenharmony_ci}));
72