UNPKG

3.96 kBJavaScriptView Raw
1var _ = require("underscore")
2var Net = require("net")
3var Tls = require("tls")
4var Http = require("http")
5var Https = require("https")
6var ClientRequest = Http.ClientRequest
7var Socket = Net.Socket
8var EventEmitter = require("events").EventEmitter
9var InternalSocket = require("./lib/internal_socket")
10var Stubs = require("./lib/stubs")
11var normalizeConnectArgs = Net._normalizeConnectArgs
12var createRequestAndResponse = Http._connectionListener
13module.exports = Mitm
14
15function Mitm() {
16 if (!(this instanceof Mitm))
17 return Mitm.apply(Object.create(Mitm.prototype), arguments).enable()
18
19 this.stubs = new Stubs
20 this.on("request", addCrossReferences)
21
22 return this
23}
24
25Mitm.prototype.on = EventEmitter.prototype.on
26Mitm.prototype.once = EventEmitter.prototype.once
27Mitm.prototype.off = EventEmitter.prototype.removeListener
28Mitm.prototype.removeListener = EventEmitter.prototype.removeListener
29Mitm.prototype.emit = EventEmitter.prototype.emit
30
31var NODE_0_10 = !!process.version.match(/^v0\.10\./)
32
33Mitm.prototype.enable = function() {
34 // Connect is called synchronously.
35 var netConnect = connect.bind(this, Net.connect)
36 this.stubs.stub(Net, "connect", netConnect)
37 this.stubs.stub(Net, "createConnection", netConnect)
38 this.stubs.stub(Http.Agent.prototype, "createConnection", netConnect)
39
40 if (NODE_0_10) {
41 // Node v0.10 sets createConnection on the object in the constructor.
42 this.stubs.stub(Http.globalAgent, "createConnection", netConnect)
43
44 // This will create a lot of sockets in tests, but that's the current price
45 // to pay until I find a better way to force a new socket for each
46 // connection.
47 this.stubs.stub(Http.globalAgent, "maxSockets", Infinity)
48 this.stubs.stub(Https.globalAgent, "maxSockets", Infinity)
49 }
50
51 // Fake a regular, non-SSL socket for now as TLSSocket requires more mocking.
52 this.stubs.stub(Tls, "connect", _.compose(authorize, netConnect))
53
54 // ClientRequest.prototype.onSocket is called synchronously from
55 // ClientRequest's consturctor and is a convenient place to hook into new
56 // ClientRequests.
57 var onSocket = _.compose(ClientRequest.prototype.onSocket, request.bind(this))
58 this.stubs.stub(ClientRequest.prototype, "onSocket", onSocket)
59
60 return this
61}
62
63Mitm.prototype.disable = function() {
64 return this.stubs.restore(), this
65}
66
67function connect(orig, opts, done) {
68 var args = normalizeConnectArgs(Array.prototype.slice.call(arguments, 1))
69 opts = args[0]; done = args[1]
70
71 var sockets = InternalSocket.pair()
72 var client = new Socket(_.defaults({handle: sockets[0]}, opts))
73 client.bypass = bypass
74
75 this.emit("connect", client, opts)
76 if (client.bypassed) return orig.call(this, opts, done)
77
78 // The callback is originally bound to the connect event in
79 // Socket.prototype.connect.
80 if (done) client.once("connect", done)
81
82 var server = client.server = new Socket({handle: sockets[1]})
83 this.emit("connection", server, opts)
84
85 // Emit connect in the next tick, otherwise it would be impossible to
86 // listen to it after calling Net.connect.
87 process.nextTick(client.emit.bind(client, "connect"))
88 process.nextTick(server.emit.bind(server, "connect"))
89
90 return client
91}
92
93function authorize(socket) {
94 return socket.authorized = true, socket
95}
96
97function bypass() { this.bypassed = true }
98
99function request(socket) {
100 if (!socket.server) return socket
101
102 // Node >= v0.10.24 < v0.11 will crash with: «Assertion failed:
103 // (!current_buffer), function Execute, file ../src/node_http_parser.cc, line
104 // 387.» if ServerResponse.prototype.write is called from within the
105 // "request" event handler. Call it in the next tick to work around that.
106 var self = this
107 if (NODE_0_10) {
108 self = Object.create(this)
109 self.emit = _.compose(process.nextTick, Function.bind.bind(this.emit, this))
110 }
111
112 return createRequestAndResponse.call(self, socket.server), socket
113}
114
115function addCrossReferences(req, res) { req.res = res; res.req = req }