1'use strict';
2
3const common = require('../common');
4const http = require('http');
5const net = require('net');
6const assert = require('assert');
7
8// The maximum http chunk extension size is set in `src/node_http_parser.cc`.
9// These tests assert that once the extension size is reached, an HTTP 413
10// response is returned.
11// Currently, the max size is set to 16KiB (16384).
12
13// Verify that chunk extensions are limited in size when sent all together.
14{
15  const server = http.createServer((req, res) => {
16    req.on('end', () => {
17      res.writeHead(200, { 'Content-Type': 'text/plain' });
18      res.end('bye');
19    });
20
21    req.resume();
22  });
23
24  server.listen(0, () => {
25    const port = server.address().port;
26    const sock = net.connect(port);
27    let data = '';
28
29    sock.on('data', (chunk) => data += chunk.toString('utf-8'));
30
31    sock.on('end', common.mustCall(function() {
32      assert.strictEqual(data, 'HTTP/1.1 413 Payload Too Large\r\nConnection: close\r\n\r\n');
33      server.close();
34    }));
35
36    sock.end('' +
37      'GET / HTTP/1.1\r\n' +
38      `Host: localhost:${port}\r\n` +
39      'Transfer-Encoding: chunked\r\n\r\n' +
40      '2;' + 'a'.repeat(17000) + '\r\n' + // Chunk size + chunk ext + CRLF
41      'AA\r\n' + // Chunk data
42      '0\r\n' + // Last chunk
43      '\r\n' // End of http message
44    );
45  });
46}
47
48// Verify that chunk extensions are limited in size when sent in parts
49{
50  const server = http.createServer((req, res) => {
51    req.on('end', () => {
52      res.writeHead(200, { 'Content-Type': 'text/plain' });
53      res.end('bye');
54    });
55
56    req.resume();
57  });
58
59  server.listen(0, () => {
60    const port = server.address().port;
61    const sock = net.connect(port);
62    let data = '';
63
64    sock.on('data', (chunk) => data += chunk.toString('utf-8'));
65
66    sock.on('end', common.mustCall(function() {
67      assert.strictEqual(data, 'HTTP/1.1 413 Payload Too Large\r\nConnection: close\r\n\r\n');
68      server.close();
69    }));
70
71    sock.write('' +
72    'GET / HTTP/1.1\r\n' +
73    `Host: localhost:${port}\r\n` +
74    'Transfer-Encoding: chunked\r\n\r\n' +
75    '2;' // Chunk size + start of chunk-extension
76    );
77
78    sock.write('A'.repeat(8500)); // Write half of the chunk-extension
79
80    queueMicrotask(() => {
81      sock.write('A'.repeat(8500) + '\r\n' + // Remaining half of the chunk-extension
82      'AA\r\n' + // Chunk data
83      '0\r\n' + // Last chunk
84      '\r\n' // End of http message
85      );
86    });
87  });
88}
89
90// Verify the chunk extensions is correctly reset after a chunk
91{
92  const server = http.createServer((req, res) => {
93    req.on('end', () => {
94      res.writeHead(200, { 'content-type': 'text/plain', 'connection': 'close', 'date': 'now' });
95      res.end('bye');
96    });
97
98    req.resume();
99  });
100
101  server.listen(0, () => {
102    const port = server.address().port;
103    const sock = net.connect(port);
104    let data = '';
105
106    sock.on('data', (chunk) => data += chunk.toString('utf-8'));
107
108    sock.on('end', common.mustCall(function() {
109      assert.strictEqual(
110        data,
111        'HTTP/1.1 200 OK\r\n' +
112        'content-type: text/plain\r\n' +
113        'connection: close\r\n' +
114        'date: now\r\n' +
115        'Transfer-Encoding: chunked\r\n' +
116        '\r\n' +
117        '3\r\n' +
118        'bye\r\n' +
119        '0\r\n' +
120        '\r\n',
121      );
122
123      server.close();
124    }));
125
126    sock.end('' +
127      'GET / HTTP/1.1\r\n' +
128      `Host: localhost:${port}\r\n` +
129      'Transfer-Encoding: chunked\r\n\r\n' +
130      '2;' + 'A'.repeat(10000) + '=bar\r\nAA\r\n' +
131      '2;' + 'A'.repeat(10000) + '=bar\r\nAA\r\n' +
132      '2;' + 'A'.repeat(10000) + '=bar\r\nAA\r\n' +
133      '0\r\n\r\n'
134    );
135  });
136}
137