1'use strict';
2
3const {
4  hasCrypto,
5  mustCall,
6  mustNotCall,
7  skip
8} = require('../common');
9if (!hasCrypto)
10  skip('missing crypto');
11
12const {
13  deepStrictEqual,
14  strictEqual,
15  throws
16} = require('assert');
17const {
18  createSecureServer,
19  createServer,
20  connect
21} = require('http2');
22const Countdown = require('../common/countdown');
23
24const { readKey } = require('../common/fixtures');
25
26const key = readKey('agent8-key.pem', 'binary');
27const cert = readKey('agent8-cert.pem', 'binary');
28const ca = readKey('fake-startcom-root-cert.pem', 'binary');
29
30{
31  const server = createSecureServer({ key, cert });
32  server.on('stream', mustCall((stream) => {
33    stream.session.origin('https://example.org/a/b/c',
34                          new URL('https://example.com'));
35    stream.respond();
36    stream.end('ok');
37  }));
38  server.on('session', mustCall((session) => {
39    session.origin('https://foo.org/a/b/c', new URL('https://bar.org'));
40
41    // Won't error, but won't send anything
42    session.origin();
43
44    [0, true, {}, []].forEach((input) => {
45      throws(
46        () => session.origin(input),
47        {
48          code: 'ERR_INVALID_ARG_TYPE',
49          name: 'TypeError'
50        }
51      );
52    });
53
54    [new URL('foo://bar'), 'foo://bar'].forEach((input) => {
55      throws(
56        () => session.origin(input),
57        {
58          code: 'ERR_HTTP2_INVALID_ORIGIN',
59          name: 'TypeError'
60        }
61      );
62    });
63
64    ['not a valid url'].forEach((input) => {
65      throws(
66        () => session.origin(input),
67        {
68          code: 'ERR_INVALID_URL',
69          name: 'TypeError'
70        }
71      );
72    });
73    const longInput = `http://foo.bar${'a'.repeat(16383)}`;
74    throws(
75      () => session.origin(longInput),
76      {
77        code: 'ERR_HTTP2_ORIGIN_LENGTH',
78        name: 'TypeError'
79      }
80    );
81  }));
82
83  server.listen(0, mustCall(() => {
84    const originSet = [`https://localhost:${server.address().port}`];
85    const client = connect(originSet[0], { ca });
86    const checks = [
87      ['https://foo.org', 'https://bar.org'],
88      ['https://example.org', 'https://example.com'],
89    ];
90
91    const countdown = new Countdown(3, () => {
92      client.close();
93      server.close();
94    });
95
96    client.on('origin', mustCall((origins) => {
97      const check = checks.shift();
98      originSet.push(...check);
99      deepStrictEqual(client.originSet, originSet);
100      deepStrictEqual(origins, check);
101      countdown.dec();
102    }, 2));
103
104    client.request().on('close', mustCall(() => countdown.dec())).resume();
105  }));
106}
107
108// Test automatically sending origin on connection start
109{
110  const origins = ['https://foo.org/a/b/c', 'https://bar.org'];
111  const server = createSecureServer({ key, cert, origins });
112  server.on('stream', mustCall((stream) => {
113    stream.respond();
114    stream.end('ok');
115  }));
116
117  server.listen(0, mustCall(() => {
118    const check = ['https://foo.org', 'https://bar.org'];
119    const originSet = [`https://localhost:${server.address().port}`];
120    const client = connect(originSet[0], { ca });
121
122    const countdown = new Countdown(2, () => {
123      client.close();
124      server.close();
125    });
126
127    client.on('origin', mustCall((origins) => {
128      originSet.push(...check);
129      deepStrictEqual(client.originSet, originSet);
130      deepStrictEqual(origins, check);
131      countdown.dec();
132    }));
133
134    client.request().on('close', mustCall(() => countdown.dec())).resume();
135  }));
136}
137
138// If return status is 421, the request origin must be removed from the
139// originSet
140{
141  const server = createSecureServer({ key, cert });
142  server.on('stream', mustCall((stream) => {
143    stream.respond({ ':status': 421 });
144    stream.end();
145  }));
146  server.on('session', mustCall((session) => {
147    session.origin('https://foo.org');
148  }));
149
150  server.listen(0, mustCall(() => {
151    const origin = `https://localhost:${server.address().port}`;
152    const client = connect(origin, { ca });
153
154    client.on('origin', mustCall((origins) => {
155      deepStrictEqual(client.originSet, [origin, 'https://foo.org']);
156      const req = client.request({ ':authority': 'foo.org' });
157      req.on('response', mustCall((headers) => {
158        strictEqual(headers[':status'], 421);
159        deepStrictEqual(client.originSet, [origin]);
160      }));
161      req.resume();
162      req.on('close', mustCall(() => {
163        client.close();
164        server.close();
165      }));
166    }, 1));
167  }));
168}
169
170// Origin is ignored on plain text HTTP/2 connections... server will still
171// send them, but client will ignore them.
172{
173  const server = createServer();
174  server.on('stream', mustCall((stream) => {
175    stream.session.origin('https://example.org',
176                          new URL('https://example.com'));
177    stream.respond();
178    stream.end('ok');
179  }));
180  server.listen(0, mustCall(() => {
181    const client = connect(`http://localhost:${server.address().port}`);
182    client.on('origin', mustNotCall());
183    strictEqual(client.originSet, undefined);
184    const req = client.request();
185    req.resume();
186    req.on('close', mustCall(() => {
187      client.close();
188      server.close();
189    }));
190  }));
191}
192