/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // This module is the stateful server side of test_http2.js and is meant // to have node be restarted in between each invocation var node_http2_root = '../node-http2'; if (process.env.NODE_HTTP2_ROOT) { node_http2_root = process.env.NODE_HTTP2_ROOT; } var http2 = require(node_http2_root); var fs = require('fs'); var url = require('url'); var crypto = require('crypto'); // Hook into the decompression code to log the decompressed name-value pairs var compression_module = node_http2_root + "/lib/protocol/compressor"; var http2_compression = require(compression_module); var HeaderSetDecompressor = http2_compression.HeaderSetDecompressor; var originalRead = HeaderSetDecompressor.prototype.read; var lastDecompressor; var decompressedPairs; HeaderSetDecompressor.prototype.read = function() { if (this != lastDecompressor) { lastDecompressor = this; decompressedPairs = []; } var pair = originalRead.apply(this, arguments); if (pair) { decompressedPairs.push(pair); } return pair; } var connection_module = node_http2_root + "/lib/protocol/connection"; var http2_connection = require(connection_module); var Connection = http2_connection.Connection; var originalClose = Connection.prototype.close; Connection.prototype.close = function (error, lastId) { if (lastId !== undefined) { this._lastIncomingStream = lastId; } originalClose.apply(this, arguments); } var framer_module = node_http2_root + "/lib/protocol/framer"; var http2_framer = require(framer_module); var Serializer = http2_framer.Serializer; var originalTransform = Serializer.prototype._transform; var newTransform = function (frame, encoding, done) { if (frame.type == 'DATA') { // Insert our empty DATA frame emptyFrame = {}; emptyFrame.type = 'DATA'; emptyFrame.data = new Buffer(0); emptyFrame.flags = []; emptyFrame.stream = frame.stream; var buffers = []; Serializer['DATA'](emptyFrame, buffers); Serializer.commonHeader(emptyFrame, buffers); for (var i = 0; i < buffers.length; i++) { this.push(buffers[i]); } // Reset to the original version for later uses Serializer.prototype._transform = originalTransform; } originalTransform.apply(this, arguments); }; function getHttpContent(path) { var content = '' + '' + 'HOORAY!' + 'You Win! (by requesting' + path + ')' + ''; return content; } function generateContent(size) { var content = ''; for (var i = 0; i < size; i++) { content += '0'; } return content; } /* This takes care of responding to the multiplexed request for us */ var m = { mp1res: null, mp2res: null, buf: null, mp1start: 0, mp2start: 0, checkReady: function() { if (this.mp1res != null && this.mp2res != null) { this.buf = generateContent(30*1024); this.mp1start = 0; this.mp2start = 0; this.send(this.mp1res, 0); setTimeout(this.send.bind(this, this.mp2res, 0), 5); } }, send: function(res, start) { var end = Math.min(start + 1024, this.buf.length); var content = this.buf.substring(start, end); res.write(content); if (end < this.buf.length) { setTimeout(this.send.bind(this, res, end), 10); } else { res.end(); } } }; var runlater = function() {}; runlater.prototype = { req : null, resp : null, onTimeout : function onTimeout() { this.resp.writeHead(200); this.resp.end("It's all good 750ms."); } }; var moreData = function() {}; moreData.prototype = { req : null, resp : null, iter: 3, onTimeout : function onTimeout() { // 1mb of data content = generateContent(1024*1024); this.resp.write(content); // 1mb chunk this.iter--; if (!this.iter) { this.resp.end(); } else { setTimeout(executeRunLater, 1, this); } } }; function executeRunLater(arg) { arg.onTimeout(); } var Compressor = http2_compression.Compressor; var HeaderSetCompressor = http2_compression.HeaderSetCompressor; var originalCompressHeaders = Compressor.prototype.compress; function insertSoftIllegalHpack(headers) { var originalCompressed = originalCompressHeaders.apply(this, headers); var illegalLiteral = new Buffer([ 0x00, // Literal, no index 0x08, // Name: not huffman encoded, 8 bytes long 0x3a, 0x69, 0x6c, 0x6c, 0x65, 0x67, 0x61, 0x6c, // :illegal 0x10, // Value: not huffman encoded, 16 bytes long // REALLY NOT LEGAL 0x52, 0x45, 0x41, 0x4c, 0x4c, 0x59, 0x20, 0x4e, 0x4f, 0x54, 0x20, 0x4c, 0x45, 0x47, 0x41, 0x4c ]); var newBufferLength = originalCompressed.length + illegalLiteral.length; var concatenated = new Buffer(newBufferLength); originalCompressed.copy(concatenated, 0); illegalLiteral.copy(concatenated, originalCompressed.length); return concatenated; } function insertHardIllegalHpack(headers) { var originalCompressed = originalCompressHeaders.apply(this, headers); // Now we have to add an invalid header var illegalIndexed = HeaderSetCompressor.integer(5000, 7); // The above returns an array of buffers, but there's only one buffer, so // get rid of the array. illegalIndexed = illegalIndexed[0]; // Set the first bit to 1 to signal this is an indexed representation illegalIndexed[0] |= 0x80; var newBufferLength = originalCompressed.length + illegalIndexed.length; var concatenated = new Buffer(newBufferLength); originalCompressed.copy(concatenated, 0); illegalIndexed.copy(concatenated, originalCompressed.length); return concatenated; } var h11required_conn = null; var h11required_header = "yes"; var didRst = false; var rstConnection = null; var illegalheader_conn = null; function handleRequest(req, res) { // We do this first to ensure nothing goes wonky in our tests that don't want // the headers to have something illegal in them Compressor.prototype.compress = originalCompressHeaders; var u = url.parse(req.url); var content = getHttpContent(u.pathname); var push, push1, push1a, push2, push3; // PushService tests. var pushPushServer1, pushPushServer2, pushPushServer3, pushPushServer4; if (req.httpVersionMajor === 2) { res.setHeader('X-Connection-Http2', 'yes'); res.setHeader('X-Http2-StreamId', '' + req.stream.id); } else { res.setHeader('X-Connection-Http2', 'no'); } if (u.pathname === '/exit') { res.setHeader('Content-Type', 'text/plain'); res.setHeader('Connection', 'close'); res.writeHead(200); res.end('ok'); process.exit(); } if (u.pathname === '/750ms') { var rl = new runlater(); rl.req = req; rl.resp = res; setTimeout(executeRunLater, 750, rl); return; } else if ((u.pathname === '/multiplex1') && (req.httpVersionMajor === 2)) { res.setHeader('Content-Type', 'text/plain'); res.writeHead(200); m.mp1res = res; m.checkReady(); return; } else if ((u.pathname === '/multiplex2') && (req.httpVersionMajor === 2)) { res.setHeader('Content-Type', 'text/plain'); res.writeHead(200); m.mp2res = res; m.checkReady(); return; } else if (u.pathname === "/header") { var val = req.headers["x-test-header"]; if (val) { res.setHeader("X-Received-Test-Header", val); } } else if (u.pathname === "/doubleheader") { res.setHeader('Content-Type', 'text/html'); res.writeHead(200); res.write(content); res.writeHead(200); res.end(); return; } else if (u.pathname === "/cookie_crumbling") { res.setHeader("X-Received-Header-Pairs", JSON.stringify(decompressedPairs)); } else if (u.pathname === "/push") { push = res.push('/push.js'); push.writeHead(200, { 'content-type': 'application/javascript', 'pushed' : 'yes', 'content-length' : 11, 'X-Connection-Http2': 'yes' }); push.end('// comments'); content = '