UNPKG

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