1// Copyright Joyent, Inc. and other Node contributors. 2// 3// Permission is hereby granted, free of charge, to any person obtaining a 4// copy of this software and associated documentation files (the 5// "Software"), to deal in the Software without restriction, including 6// without limitation the rights to use, copy, modify, merge, publish, 7// distribute, sublicense, and/or sell copies of the Software, and to permit 8// persons to whom the Software is furnished to do so, subject to the 9// following conditions: 10// 11// The above copyright notice and this permission notice shall be included 12// in all copies or substantial portions of the Software. 13// 14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20// USE OR OTHER DEALINGS IN THE SOFTWARE. 21 22'use strict'; 23const common = require('../common'); 24if (!common.hasCrypto) 25 common.skip('missing crypto'); 26 27// This test ensures that the data received through tls over http tunnel 28// is same as what is sent. 29 30const assert = require('assert'); 31const https = require('https'); 32const net = require('net'); 33const http = require('http'); 34const fixtures = require('../common/fixtures'); 35 36let gotRequest = false; 37 38const key = fixtures.readKey('agent1-key.pem'); 39const cert = fixtures.readKey('agent1-cert.pem'); 40 41const options = { key, cert }; 42 43const server = https.createServer(options, common.mustCall((req, res) => { 44 console.log('SERVER: got request'); 45 res.writeHead(200, { 46 'content-type': 'text/plain' 47 }); 48 console.log('SERVER: sending response'); 49 res.end('hello world\n'); 50})); 51 52const proxy = net.createServer((clientSocket) => { 53 console.log('PROXY: got a client connection'); 54 55 let serverSocket = null; 56 57 clientSocket.on('data', (chunk) => { 58 if (!serverSocket) { 59 // Verify the CONNECT request 60 assert.strictEqual(chunk.toString(), 61 `CONNECT localhost:${server.address().port} ` + 62 'HTTP/1.1\r\n' + 63 'Proxy-Connections: keep-alive\r\n' + 64 `Host: localhost:${proxy.address().port}\r\n` + 65 'Connection: close\r\n\r\n'); 66 67 console.log('PROXY: got CONNECT request'); 68 console.log('PROXY: creating a tunnel'); 69 70 // create the tunnel 71 serverSocket = net.connect(server.address().port, common.mustCall(() => { 72 console.log('PROXY: replying to client CONNECT request'); 73 74 // Send the response 75 clientSocket.write('HTTP/1.1 200 OK\r\nProxy-Connections: keep' + 76 '-alive\r\nConnections: keep-alive\r\nVia: ' + 77 `localhost:${proxy.address().port}\r\n\r\n`); 78 })); 79 80 serverSocket.on('data', (chunk) => { 81 clientSocket.write(chunk); 82 }); 83 84 serverSocket.on('end', common.mustCall(() => { 85 clientSocket.destroy(); 86 })); 87 } else { 88 serverSocket.write(chunk); 89 } 90 }); 91 92 clientSocket.on('end', () => { 93 serverSocket.destroy(); 94 }); 95}); 96 97server.listen(0); 98 99proxy.listen(0, common.mustCall(() => { 100 console.log('CLIENT: Making CONNECT request'); 101 102 const req = http.request({ 103 port: proxy.address().port, 104 method: 'CONNECT', 105 path: `localhost:${server.address().port}`, 106 headers: { 107 'Proxy-Connections': 'keep-alive' 108 } 109 }); 110 req.useChunkedEncodingByDefault = false; // for v0.6 111 req.on('response', onResponse); // for v0.6 112 req.on('upgrade', onUpgrade); // for v0.6 113 req.on('connect', onConnect); // for v0.7 or later 114 req.end(); 115 116 function onResponse(res) { 117 // Very hacky. This is necessary to avoid http-parser leaks. 118 res.upgrade = true; 119 } 120 121 function onUpgrade(res, socket, head) { 122 // Hacky. 123 process.nextTick(() => { 124 onConnect(res, socket, head); 125 }); 126 } 127 128 function onConnect(res, socket, header) { 129 assert.strictEqual(res.statusCode, 200); 130 console.log('CLIENT: got CONNECT response'); 131 132 // detach the socket 133 socket.removeAllListeners('data'); 134 socket.removeAllListeners('close'); 135 socket.removeAllListeners('error'); 136 socket.removeAllListeners('drain'); 137 socket.removeAllListeners('end'); 138 socket.ondata = null; 139 socket.onend = null; 140 socket.ondrain = null; 141 142 console.log('CLIENT: Making HTTPS request'); 143 144 https.get({ 145 path: '/foo', 146 key: key, 147 cert: cert, 148 socket: socket, // reuse the socket 149 agent: false, 150 rejectUnauthorized: false 151 }, (res) => { 152 assert.strictEqual(res.statusCode, 200); 153 154 res.on('data', common.mustCall((chunk) => { 155 assert.strictEqual(chunk.toString(), 'hello world\n'); 156 console.log('CLIENT: got HTTPS response'); 157 gotRequest = true; 158 })); 159 160 res.on('end', common.mustCall(() => { 161 proxy.close(); 162 server.close(); 163 })); 164 }).on('error', (er) => { 165 // We're ok with getting ECONNRESET in this test, but it's 166 // timing-dependent, and thus unreliable. Any other errors 167 // are just failures, though. 168 if (er.code !== 'ECONNRESET') 169 throw er; 170 }).end(); 171 } 172})); 173 174process.on('exit', () => { 175 assert.ok(gotRequest); 176}); 177