1'use strict';
2
3const common = require('../common');
4if (!common.hasCrypto)
5  common.skip('missing crypto');
6const assert = require('assert');
7const net = require('net');
8const http2 = require('http2');
9
10const {
11  HTTP2_HEADER_METHOD,
12  HTTP2_HEADER_AUTHORITY,
13  HTTP2_HEADER_SCHEME,
14  HTTP2_HEADER_PATH,
15  NGHTTP2_CONNECT_ERROR,
16  NGHTTP2_REFUSED_STREAM
17} = http2.constants;
18
19const server = net.createServer(common.mustCall((socket) => {
20  let data = '';
21  socket.setEncoding('utf8');
22  socket.on('data', (chunk) => data += chunk);
23  socket.on('end', common.mustCall(() => {
24    assert.strictEqual(data, 'hello');
25  }));
26  socket.on('close', common.mustCall());
27  socket.end('hello');
28}));
29
30server.listen(0, common.mustCall(() => {
31
32  const port = server.address().port;
33
34  const proxy = http2.createServer();
35  proxy.on('stream', common.mustCall((stream, headers) => {
36    if (headers[HTTP2_HEADER_METHOD] !== 'CONNECT') {
37      stream.close(NGHTTP2_REFUSED_STREAM);
38      return;
39    }
40    const auth = new URL(`tcp://${headers[HTTP2_HEADER_AUTHORITY]}`);
41    assert.strictEqual(auth.hostname, 'localhost');
42    assert.strictEqual(+auth.port, port);
43    const socket = net.connect(auth.port, auth.hostname, () => {
44      stream.respond();
45      socket.pipe(stream);
46      stream.pipe(socket);
47    });
48    socket.on('close', common.mustCall());
49    socket.on('error', (error) => {
50      stream.close(NGHTTP2_CONNECT_ERROR);
51    });
52  }));
53
54  proxy.listen(0, () => {
55    const client = http2.connect(`http://localhost:${proxy.address().port}`);
56
57    // Confirm that :authority is required and :scheme & :path are forbidden
58    assert.throws(
59      () => client.request({
60        [HTTP2_HEADER_METHOD]: 'CONNECT'
61      }),
62      {
63        code: 'ERR_HTTP2_CONNECT_AUTHORITY',
64        message: ':authority header is required for CONNECT requests'
65      }
66    );
67    assert.throws(
68      () => client.request({
69        [HTTP2_HEADER_METHOD]: 'CONNECT',
70        [HTTP2_HEADER_AUTHORITY]: `localhost:${port}`,
71        [HTTP2_HEADER_SCHEME]: 'http'
72      }),
73      {
74        code: 'ERR_HTTP2_CONNECT_SCHEME',
75        message: 'The :scheme header is forbidden for CONNECT requests'
76      }
77    );
78    assert.throws(
79      () => client.request({
80        [HTTP2_HEADER_METHOD]: 'CONNECT',
81        [HTTP2_HEADER_AUTHORITY]: `localhost:${port}`,
82        [HTTP2_HEADER_PATH]: '/'
83      }),
84      {
85        code: 'ERR_HTTP2_CONNECT_PATH',
86        message: 'The :path header is forbidden for CONNECT requests'
87      }
88    );
89
90    // valid CONNECT request
91    const req = client.request({
92      [HTTP2_HEADER_METHOD]: 'CONNECT',
93      [HTTP2_HEADER_AUTHORITY]: `localhost:${port}`,
94    });
95
96    req.on('response', common.mustCall());
97    let data = '';
98    req.setEncoding('utf8');
99    req.on('data', (chunk) => data += chunk);
100    req.on('end', common.mustCall(() => {
101      assert.strictEqual(data, 'hello');
102      client.close();
103      proxy.close();
104      server.close();
105    }));
106    req.end('hello');
107  });
108}));
109