├── .gitignore ├── .travis.yml ├── README.md ├── lib └── deceiver.js ├── package.json └── test └── api-test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | coverage 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | 5 | node_js: 6 | - "0.10" 7 | - "0.12" 8 | - "4" 9 | - "6" 10 | - "stable" 11 | 12 | script: 13 | - npm run lint 14 | - npm test 15 | - npm run coverage 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HTTP Deceiver 2 | 3 | [![Build Status](https://travis-ci.org/spdy-http2/http-deceiver.svg?branch=master)](http://travis-ci.org/spdy-http2/http-deceiver) 4 | [![NPM version](https://badge.fury.io/js/http-deceiver.svg)](http://badge.fury.io/js/http-deceiver) 5 | [![dependencies Status](https://david-dm.org/spdy-http2/http-deceiver/status.svg?style=flat-square)](https://david-dm.org/spdy-http2/http-deceiver) 6 | [![Standard - JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg?style=flat-square)](http://standardjs.com/) 7 | [![Waffle](https://img.shields.io/badge/track-waffle-blue.svg?style=flat-square)](https://waffle.io/spdy-http2/node-spdy) 8 | 9 | > Deceive! 10 | 11 | ## Usage 12 | 13 | ### Examples 14 | 15 | `soon™` 16 | 17 | ### API 18 | 19 | `soon™` 20 | 21 | ## LICENSE 22 | 23 | This software is licensed under the MIT License. 24 | 25 | Copyright Fedor Indutny, 2015. 26 | 27 | Permission is hereby granted, free of charge, to any person obtaining a 28 | copy of this software and associated documentation files (the 29 | "Software"), to deal in the Software without restriction, including 30 | without limitation the rights to use, copy, modify, merge, publish, 31 | distribute, sublicense, and/or sell copies of the Software, and to permit 32 | persons to whom the Software is furnished to do so, subject to the 33 | following conditions: 34 | 35 | The above copyright notice and this permission notice shall be included 36 | in all copies or substantial portions of the Software. 37 | 38 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 39 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 40 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 41 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 42 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 43 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 44 | USE OR OTHER DEALINGS IN THE SOFTWARE. 45 | -------------------------------------------------------------------------------- /lib/deceiver.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | 3 | var Buffer = require('buffer').Buffer 4 | 5 | // Node.js version 6 | var mode = /^v0\.8\./.test(process.version) 7 | ? 'rusty' 8 | : /^v0\.(9|10)\./.test(process.version) 9 | ? 'old' 10 | : /^v0\.12\./.test(process.version) 11 | ? 'normal' 12 | : 'modern' 13 | 14 | var HTTPParser 15 | 16 | var methods 17 | var reverseMethods 18 | 19 | // var kOnHeaders 20 | var kOnHeadersComplete 21 | var kOnMessageComplete 22 | var kOnBody 23 | if (mode === 'normal' || mode === 'modern') { 24 | HTTPParser = process.binding('http_parser').HTTPParser 25 | methods = HTTPParser.methods 26 | 27 | // v6 28 | if (!methods) { 29 | methods = process.binding('http_parser').methods 30 | } 31 | 32 | reverseMethods = {} 33 | 34 | methods.forEach(function (method, index) { 35 | reverseMethods[method] = index 36 | }) 37 | 38 | // kOnHeaders = HTTPParser.kOnHeaders | 0 39 | kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0 40 | kOnMessageComplete = HTTPParser.kOnMessageComplete | 0 41 | kOnBody = HTTPParser.kOnBody | 0 42 | } else { 43 | // kOnHeaders = 'onHeaders' 44 | kOnHeadersComplete = 'onHeadersComplete' 45 | kOnMessageComplete = 'onMessageComplete' 46 | kOnBody = 'onBody' 47 | } 48 | 49 | function Deceiver (socket, options) { 50 | this.socket = socket 51 | this.options = options || {} 52 | this.isClient = this.options.isClient 53 | } 54 | module.exports = Deceiver 55 | 56 | Deceiver.create = function create (stream, options) { 57 | return new Deceiver(stream, options) 58 | } 59 | 60 | Deceiver.prototype._toHeaderList = function _toHeaderList (object) { 61 | var out = [] 62 | var keys = Object.keys(object) 63 | 64 | for (var i = 0; i < keys.length; i++) { 65 | out.push(keys[i], object[keys[i]]) 66 | } 67 | 68 | return out 69 | } 70 | 71 | Deceiver.prototype._isUpgrade = function _isUpgrade (request) { 72 | return request.method === 'CONNECT' || 73 | request.headers.upgrade || 74 | request.headers.connection && 75 | /(^|\W)upgrade(\W|$)/i.test(request.headers.connection) 76 | } 77 | 78 | // TODO(indutny): support CONNECT 79 | if (mode === 'modern') { 80 | /* 81 | function parserOnHeadersComplete(versionMajor, versionMinor, headers, method, 82 | url, statusCode, statusMessage, upgrade, 83 | shouldKeepAlive) { 84 | */ 85 | Deceiver.prototype.emitRequest = function emitRequest (request) { 86 | var parser = this.socket.parser 87 | assert(parser, 'No parser present') 88 | 89 | parser.execute = null 90 | 91 | var self = this 92 | var method = reverseMethods[request.method] 93 | parser.execute = function execute () { 94 | self._skipExecute(this) 95 | this[kOnHeadersComplete](1, 96 | 1, 97 | self._toHeaderList(request.headers), 98 | method, 99 | request.path, 100 | 0, 101 | '', 102 | self._isUpgrade(request), 103 | true) 104 | return 0 105 | } 106 | 107 | this._emitEmpty() 108 | } 109 | 110 | Deceiver.prototype.emitResponse = function emitResponse (response) { 111 | var parser = this.socket.parser 112 | assert(parser, 'No parser present') 113 | 114 | parser.execute = null 115 | 116 | var self = this 117 | parser.execute = function execute () { 118 | self._skipExecute(this) 119 | this[kOnHeadersComplete](1, 120 | 1, 121 | self._toHeaderList(response.headers), 122 | response.path, 123 | response.code, 124 | response.status, 125 | response.reason || '', 126 | self._isUpgrade(response), 127 | true) 128 | return 0 129 | } 130 | 131 | this._emitEmpty() 132 | } 133 | } else { 134 | /* 135 | `function parserOnHeadersComplete(info) {` 136 | 137 | info = { .versionMajor, .versionMinor, .url, .headers, .method, 138 | .statusCode, .statusMessage, .upgrade, .shouldKeepAlive } 139 | */ 140 | Deceiver.prototype.emitRequest = function emitRequest (request) { 141 | var parser = this.socket.parser 142 | assert(parser, 'No parser present') 143 | 144 | var method = request.method 145 | if (reverseMethods) { 146 | method = reverseMethods[method] 147 | } 148 | 149 | var info = { 150 | versionMajor: 1, 151 | versionMinor: 1, 152 | url: request.path, 153 | headers: this._toHeaderList(request.headers), 154 | method: method, 155 | statusCode: 0, 156 | statusMessage: '', 157 | upgrade: this._isUpgrade(request), 158 | shouldKeepAlive: true 159 | } 160 | 161 | var self = this 162 | parser.execute = function execute () { 163 | self._skipExecute(this) 164 | this[kOnHeadersComplete](info) 165 | return 0 166 | } 167 | 168 | this._emitEmpty() 169 | } 170 | 171 | Deceiver.prototype.emitResponse = function emitResponse (response) { 172 | var parser = this.socket.parser 173 | assert(parser, 'No parser present') 174 | 175 | var info = { 176 | versionMajor: 1, 177 | versionMinor: 1, 178 | url: response.path, 179 | headers: this._toHeaderList(response.headers), 180 | method: false, 181 | statusCode: response.status, 182 | statusMessage: response.reason || '', 183 | upgrade: this._isUpgrade(response), 184 | shouldKeepAlive: true 185 | } 186 | 187 | var self = this 188 | parser.execute = function execute () { 189 | self._skipExecute(this) 190 | this[kOnHeadersComplete](info) 191 | return 0 192 | } 193 | 194 | this._emitEmpty() 195 | } 196 | } 197 | 198 | Deceiver.prototype._skipExecute = function _skipExecute (parser) { 199 | var self = this 200 | var oldExecute = parser.constructor.prototype.execute 201 | var oldFinish = parser.constructor.prototype.finish 202 | 203 | parser.execute = null 204 | parser.finish = null 205 | 206 | parser.execute = function execute (buffer, start, len) { 207 | // Parser reuse 208 | if (this.socket !== self.socket) { 209 | this.execute = oldExecute 210 | this.finish = oldFinish 211 | return this.execute(buffer, start, len) 212 | } 213 | 214 | if (start !== undefined) { 215 | buffer = buffer.slice(start, start + len) 216 | } 217 | self.emitBody(buffer) 218 | return len 219 | } 220 | 221 | parser.finish = function finish () { 222 | // Parser reuse 223 | if (this.socket !== self.socket) { 224 | this.execute = oldExecute 225 | this.finish = oldFinish 226 | return this.finish() 227 | } 228 | 229 | this.execute = oldExecute 230 | this.finish = oldFinish 231 | self.emitMessageComplete() 232 | } 233 | } 234 | 235 | Deceiver.prototype.emitBody = function emitBody (buffer) { 236 | var parser = this.socket.parser 237 | assert(parser, 'No parser present') 238 | 239 | parser[kOnBody](buffer, 0, buffer.length) 240 | } 241 | 242 | Deceiver.prototype._emitEmpty = function _emitEmpty () { 243 | // Emit data to force out handling of UPGRADE 244 | var empty = new Buffer(0) 245 | if (this.socket.ondata) { this.socket.ondata(empty, 0, 0) } else { 246 | this.socket.emit('data', empty) 247 | } 248 | } 249 | 250 | Deceiver.prototype.emitMessageComplete = function emitMessageComplete () { 251 | var parser = this.socket.parser 252 | assert(parser, 'No parser present') 253 | 254 | parser[kOnMessageComplete]() 255 | } 256 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "http-deceiver", 3 | "version": "1.2.7", 4 | "description": "Deceive HTTP parser", 5 | "main": "lib/deceiver.js", 6 | "scripts": { 7 | "lint": "standard", 8 | "test": "mocha --reporter=spec test/*-test.js", 9 | "coverage": "istanbul cover node_modules/.bin/_mocha -- --reporter=spec test/**/*-test.js" 10 | }, 11 | "pre-commit": [ 12 | "lint", 13 | "test" 14 | ], 15 | "repository": { 16 | "type": "git", 17 | "url": "git+ssh://git@github.com/spdy-http2/http-deceiver.git" 18 | }, 19 | "keywords": [ 20 | "http", 21 | "net", 22 | "deceive" 23 | ], 24 | "author": "Fedor Indutny ", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/spdy-http2/http-deceiver/issues" 28 | }, 29 | "homepage": "https://github.com/spdy-http2/http-deceiver#readme", 30 | "devDependencies": { 31 | "handle-thing": "^1.0.1", 32 | "istanbul": "^0.4.5", 33 | "mocha": "^2.2.5", 34 | "pre-commit": "^1.2.2", 35 | "readable-stream": "^2.0.1", 36 | "standard": "^8.6.0", 37 | "stream-pair": "^1.0.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/api-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | var assert = require('assert') 4 | var net = require('net') 5 | var http = require('http') 6 | var streamPair = require('stream-pair') 7 | var thing = require('handle-thing') 8 | 9 | var httpDeceiver = require('../') 10 | 11 | describe('HTTP Deceiver', function () { 12 | var handle 13 | var pair 14 | var socket 15 | var deceiver 16 | 17 | beforeEach(function () { 18 | pair = streamPair.create() 19 | handle = thing.create(pair.other) 20 | socket = new net.Socket({ handle: handle }) 21 | 22 | // For v0.8 23 | socket.readable = true 24 | socket.writable = true 25 | 26 | deceiver = httpDeceiver.create(socket) 27 | }) 28 | 29 | it('should emit request', function (done) { 30 | var server = http.createServer() 31 | server.emit('connection', socket) 32 | 33 | server.on('request', function (req, res) { 34 | assert.equal(req.method, 'PUT') 35 | assert.equal(req.url, '/hello') 36 | assert.deepEqual(req.headers, { a: 'b' }) 37 | 38 | done() 39 | }) 40 | 41 | deceiver.emitRequest({ 42 | method: 'PUT', 43 | path: '/hello', 44 | headers: { 45 | a: 'b' 46 | } 47 | }) 48 | }) 49 | 50 | it('should emit response', function (done) { 51 | var agent = new http.Agent() 52 | agent.createConnection = function createConnection () { 53 | return socket 54 | } 55 | /* var client = */ http.request({ 56 | method: 'POST', 57 | path: '/ok', 58 | agent: agent 59 | }, function (res) { 60 | assert.equal(res.statusCode, 421) 61 | assert.deepEqual(res.headers, { a: 'b' }) 62 | 63 | done() 64 | }) 65 | 66 | process.nextTick(function () { 67 | deceiver.emitResponse({ 68 | status: 421, 69 | reason: 'F', 70 | headers: { 71 | a: 'b' 72 | } 73 | }) 74 | }) 75 | }) 76 | 77 | it('should override .execute and .finish', function (done) { 78 | var server = http.createServer() 79 | server.emit('connection', socket) 80 | 81 | server.on('request', function (req, res) { 82 | assert.equal(req.method, 'PUT') 83 | assert.equal(req.url, '/hello') 84 | assert.deepEqual(req.headers, { a: 'b' }) 85 | 86 | var actual = '' 87 | req.on('data', function (chunk) { 88 | actual += chunk 89 | }) 90 | req.once('end', function () { 91 | assert.equal(actual, 'hello world') 92 | done() 93 | }) 94 | }) 95 | 96 | deceiver.emitRequest({ 97 | method: 'PUT', 98 | path: '/hello', 99 | headers: { 100 | a: 'b' 101 | } 102 | }) 103 | 104 | pair.write('hello') 105 | pair.end(' world') 106 | }) 107 | 108 | it('should work with reusing parser', function (done) { 109 | var server = http.createServer() 110 | server.emit('connection', socket) 111 | 112 | function secondRequest () { 113 | pair = streamPair.create() 114 | handle = thing.create(pair.other) 115 | socket = new net.Socket({ handle: handle }) 116 | 117 | // For v0.8 118 | socket.readable = true 119 | socket.writable = true 120 | 121 | server.emit('connection', socket) 122 | 123 | pair.end('PUT /second HTTP/1.1\r\nContent-Length:11\r\n\r\nhello world') 124 | } 125 | 126 | server.on('request', function (req, res) { 127 | var actual = '' 128 | req.on('data', function (chunk) { 129 | actual += chunk 130 | }) 131 | req.once('end', function () { 132 | assert.equal(actual, 'hello world') 133 | 134 | if (req.url === '/first') { 135 | secondRequest() 136 | } else { 137 | done() 138 | } 139 | }) 140 | }) 141 | 142 | deceiver.emitRequest({ 143 | method: 'PUT', 144 | path: '/first', 145 | headers: { 146 | a: 'b' 147 | } 148 | }) 149 | 150 | pair.write('hello') 151 | pair.end(' world') 152 | }) 153 | 154 | it('should emit CONNECT request', function (done) { 155 | var server = http.createServer() 156 | server.emit('connection', socket) 157 | 158 | server.on('connect', function (req, socket, bodyHead) { 159 | assert.equal(req.method, 'CONNECT') 160 | assert.equal(req.url, '/hello') 161 | 162 | done() 163 | }) 164 | 165 | deceiver.emitRequest({ 166 | method: 'CONNECT', 167 | path: '/hello', 168 | headers: { 169 | } 170 | }) 171 | }) 172 | 173 | it('should emit Upgrade request', function (done) { 174 | var server = http.createServer() 175 | server.emit('connection', socket) 176 | 177 | server.on('upgrade', function (req, socket, bodyHead) { 178 | assert.equal(req.method, 'POST') 179 | assert.equal(req.url, '/hello') 180 | 181 | socket.on('data', function (chunk) { 182 | assert.equal(chunk + '', 'hm') 183 | done() 184 | }) 185 | }) 186 | 187 | deceiver.emitRequest({ 188 | method: 'POST', 189 | path: '/hello', 190 | headers: { 191 | 'upgrade': 'websocket' 192 | } 193 | }) 194 | 195 | pair.write('hm') 196 | }) 197 | 198 | it('should emit Upgrade response', function (done) { 199 | var agent = new http.Agent() 200 | agent.createConnection = function createConnection () { 201 | return socket 202 | } 203 | var client = http.request({ 204 | method: 'POST', 205 | path: '/ok', 206 | headers: { 207 | connection: 'upgrade', 208 | upgrade: 'websocket' 209 | }, 210 | agent: agent 211 | }, function (res) { 212 | assert(false) 213 | }) 214 | client.on('upgrade', function (res, socket) { 215 | assert.equal(res.statusCode, 421) 216 | done() 217 | }) 218 | 219 | process.nextTick(function () { 220 | deceiver.emitResponse({ 221 | status: 421, 222 | reason: 'F', 223 | headers: { 224 | upgrade: 'websocket' 225 | } 226 | }) 227 | }) 228 | }) 229 | }) 230 | --------------------------------------------------------------------------------