UNPKG

22.9 kBJavaScriptView Raw
1var _ = require("underscore")
2var Sinon = require("sinon")
3var Net = require("net")
4var Tls = require("tls")
5var Http = require("http")
6var Https = require("https")
7var Semver = require("semver")
8var IncomingMessage = Http.IncomingMessage
9var ServerResponse = Http.ServerResponse
10var ClientRequest = Http.ClientRequest
11var EventEmitter = require("events").EventEmitter
12var Mitm = require("..")
13var NODE_0_10 = Semver.satisfies(process.version, ">= 0.10 < 0.11")
14
15describe("Mitm", function() {
16 beforeEach(function() { Mitm.passthrough = false })
17
18 it("must return an instance of Mitm when called as a function", function() {
19 var mitm = Mitm()
20 mitm.must.be.an.instanceof(Mitm)
21 mitm.disable()
22 })
23
24 function mustConnect(module) {
25 describe("as connect", function() {
26 it("must return an instance of Net.Socket", function() {
27 var socket = module.connect({host: "foo", port: 80})
28 socket.must.be.an.instanceof(Net.Socket)
29 })
30
31 it("must return an instance of Net.Socket given port", function() {
32 module.connect(80).must.be.an.instanceof(Net.Socket)
33 })
34
35 it("must return an instance of Net.Socket given port and host",
36 function() {
37 module.connect(80, "10.0.0.1").must.be.an.instanceof(Net.Socket)
38 })
39
40 it("must emit connect on Mitm", function() {
41 var onConnect = Sinon.spy()
42 this.mitm.on("connect", onConnect)
43 var opts = {host: "foo"}
44 var socket = module.connect(opts)
45
46 onConnect.callCount.must.equal(1)
47 onConnect.firstCall.args[0].must.equal(socket)
48 onConnect.firstCall.args[1].must.equal(opts)
49 })
50
51 it("must emit connect on Mitm with options object given host and port",
52 function() {
53 var onConnect = Sinon.spy()
54 this.mitm.on("connect", onConnect)
55 var socket = module.connect(9, "127.0.0.1")
56
57 onConnect.callCount.must.equal(1)
58 onConnect.firstCall.args[0].must.equal(socket)
59 onConnect.firstCall.args[1].must.eql({host: "127.0.0.1", port: 9})
60 })
61
62 it("must emit connection on Mitm", function() {
63 var onConnection = Sinon.spy()
64 this.mitm.on("connection", onConnection)
65 var opts = {host: "foo"}
66 var socket = module.connect(opts)
67
68 onConnection.callCount.must.equal(1)
69 onConnection.firstCall.args[0].must.be.an.instanceof(Net.Socket)
70 onConnection.firstCall.args[0].must.not.equal(socket)
71 onConnection.firstCall.args[1].must.equal(opts)
72 })
73
74 it("must emit connect on socket in next ticks", function(done) {
75 var socket = module.connect({host: "foo"})
76 socket.on("connect", done.bind(null, null))
77 })
78
79 it("must call back on connect given callback", function(done) {
80 module.connect({host: "foo"}, done.bind(null, null))
81 })
82
83 it("must call back on connect given port and callback", function(done) {
84 module.connect(80, done.bind(null, null))
85 })
86
87 // This was a bug found on Apr 26, 2014 where the host argument was taken
88 // to be the callback because arguments weren't normalized to an options
89 // object.
90 it("must call back on connect given port, host and callback",
91 function(done) {
92 module.connect(80, "localhost", done.bind(null, null))
93 })
94
95 it("must intercept 127.0.0.1", function(done) {
96 var server; this.mitm.on("connection", function(s) { server = s })
97 var client = module.connect({host: "127.0.0.1"})
98 server.write("Hello")
99
100 client.setEncoding("utf8")
101 client.on("data", function(data) { data.must.equal("Hello") })
102 client.on("data", done.bind(null, null))
103 })
104
105 describe("when bypassed", function() {
106 beforeEach(function() { this.sinon = Sinon.sandbox.create() })
107 afterEach(function() { this.sinon.restore() })
108
109 it("must not intercept", function(done) {
110 this.mitm.on("connect", function(client) { client.bypass() })
111
112 module.connect({host: "127.0.0.1", port: 9}).on("error", function(err) {
113 err.must.be.an.instanceof(Error)
114 err.message.must.include("ECONNREFUSED")
115 done()
116 })
117 })
118
119 it("must call original module.connect", function() {
120 this.mitm.disable()
121
122 var connect = this.sinon.spy(module, "connect")
123 var mitm = Mitm()
124 mitm.on("connect", function(client) { client.bypass() })
125
126 try {
127 module.connect({host: "127.0.0.1", port: 9}).on("error", noop)
128 connect.callCount.must.equal(1)
129 connect.firstCall.args[0].must.eql({host: "127.0.0.1", port: 9})
130 }
131 // Working around Mocha's context bug(s) and poor design decision
132 // with a manual `finally`.
133 finally { mitm.disable() }
134 })
135
136 it("must not call back twice on connect given callback",
137 function(done) {
138 this.mitm.on("connect", function(client) { client.bypass() })
139
140 var onConnect = Sinon.spy()
141 var client = module.connect({host: "127.0.0.1", port: 9}, onConnect)
142
143 client.on("error", process.nextTick.bind(null, function() {
144 onConnect.callCount.must.equal(0)
145 done()
146 }))
147 })
148
149 it("must not emit connection", function() {
150 this.mitm.on("connect", function(client) { client.bypass() })
151 var onConnection = Sinon.spy()
152 this.mitm.on("connection", onConnection)
153 module.connect({host: "127.0.0.1", port: 9}).on("error", noop)
154 onConnection.callCount.must.equal(0)
155 })
156 })
157 })
158 }
159
160 describe("Net.connect", function() {
161 beforeEach(function() { this.mitm = Mitm() })
162 afterEach(function() { this.mitm.disable() })
163
164 mustConnect(Net)
165
166 if (!NODE_0_10)
167 it("must not return an instance of Tls.TLSSocket", function() {
168 var client = Net.connect({host: "foo", port: 80})
169 client.must.not.be.an.instanceof(Tls.TLSSocket)
170 })
171
172 it("must not set the encrypted property", function() {
173 Net.connect({host: "foo"}).must.not.have.property("encrypted")
174 })
175
176 it("must not set the authorized property", function() {
177 Net.connect({host: "foo"}).must.not.have.property("authorized")
178 })
179
180 it("must not emit secureConnect on client", function(done) {
181 var client = Net.connect({host: "foo"})
182 // Let Mocha raise an error when done called twice.
183 client.on("secureConnect", done.bind(null, null))
184 done()
185 })
186
187 it("must not emit secureConnect on server", function(done) {
188 var server; this.mitm.on("connection", function(s) { server = s })
189 Net.connect({host: "foo"})
190 // Let Mocha raise an error when done called twice.
191 server.on("secureConnect", done.bind(null, null))
192 done()
193 })
194
195 describe("Socket", function() {
196 describe(".prototype.write", function() {
197 it("must write to client from server", function(done) {
198 var server; this.mitm.on("connection", function(s) { server = s })
199 var client = Net.connect({host: "foo"})
200 server.write("Hello")
201
202 client.setEncoding("utf8")
203 client.on("data", function(data) { data.must.equal("Hello") })
204 client.on("data", done.bind(null, null))
205 })
206
207 it("must write to client from server in the next tick", function(done) {
208 var server; this.mitm.on("connection", function(s) { server = s })
209 var client = Net.connect({host: "foo"})
210
211 var ticked = false
212 client.once("data", function() { ticked.must.be.true(); done() })
213 server.write("Hello")
214 ticked = true
215 })
216
217 it("must write to server from client", function(done) {
218 var server; this.mitm.on("connection", function(s) { server = s })
219 var client = Net.connect({host: "foo"})
220 client.write("Hello")
221
222 server.setEncoding("utf8")
223 process.nextTick(function() { server.read().must.equal("Hello") })
224 process.nextTick(done)
225 })
226
227 it("must write to server from client in the next tick", function(done) {
228 var server; this.mitm.on("connection", function(s) { server = s })
229 var client = Net.connect({host: "foo"})
230
231 var ticked = false
232 server.once("data", function() { ticked.must.be.true(); done() })
233 client.write("Hello")
234 ticked = true
235 })
236
237 // Writing binary strings was introduced in Node v0.11.14.
238 // The test still passes for Node v0.10 and newer v0.11s, so let it be.
239 it("must write to server from client given binary", function(done) {
240 var server; this.mitm.on("connection", function(s) { server = s })
241 var client = Net.connect({host: "foo"})
242 client.write("Hello", "binary")
243
244 server.setEncoding("binary")
245 process.nextTick(function() { server.read().must.equal("Hello") })
246 process.nextTick(done)
247 })
248
249 // Writing latin1 strings was introduced in v6.4.
250 // https://github.com/nodejs/node/commit/28071a130e2137bd14d0762a25f0ad83b7a28259
251 if (Semver.satisfies(process.version, ">= 6.4"))
252 it("must write to server from client given latin1", function(done) {
253 var server; this.mitm.on("connection", function(s) { server = s })
254 var client = Net.connect({host: "foo"})
255 client.write("Hello", "latin1")
256
257 server.setEncoding("latin1")
258 process.nextTick(function() { server.read().must.equal("Hello") })
259 process.nextTick(done)
260 })
261
262 it("must write to server from client given a buffer", function(done) {
263 var server; this.mitm.on("connection", function(s) { server = s })
264 var client = Net.connect({host: "foo"})
265 client.write(new Buffer("Hello"))
266
267 server.setEncoding("utf8")
268 process.nextTick(function() { server.read().must.equal("Hello") })
269 process.nextTick(done)
270 })
271
272 it("must write to server from client given a UTF-8 string",
273 function(done) {
274 var server; this.mitm.on("connection", function(s) { server = s })
275 var client = Net.connect({host: "foo"})
276 client.write("Hello", "utf8")
277
278 server.setEncoding("utf8")
279 process.nextTick(function() { server.read().must.equal("Hello") })
280 process.nextTick(done)
281 })
282
283 it("must write to server from client given a ASCII string",
284 function(done) {
285 var server; this.mitm.on("connection", function(s) { server = s })
286 var client = Net.connect({host: "foo"})
287 client.write("Hello", "ascii")
288
289 server.setEncoding("utf8")
290 process.nextTick(function() { server.read().must.equal("Hello") })
291 process.nextTick(done)
292 })
293
294 it("must write to server from client given a UCS-2 string",
295 function(done) {
296 var server; this.mitm.on("connection", function(s) { server = s })
297 var client = Net.connect({host: "foo"})
298 client.write("Hello", "ucs2")
299
300 process.nextTick(function() {
301 server.setEncoding("ucs2")
302 server.read().must.equal("H\u0000e\u0000l\u0000l\u0000o\u0000")
303 done()
304 })
305 })
306 })
307
308 describe(".prototype.end", function() {
309 it("must emit end when closed on server", function(done) {
310 var server; this.mitm.on("connection", function(s) { server = s })
311 var client = Net.connect({host: "foo"})
312 server.end()
313 client.on("end", done)
314 })
315 })
316
317 describe(".prototype.ref", function() {
318 it("must allow calling on client", function() {
319 Net.connect({host: "foo"}).ref()
320 })
321
322 it("must allow calling on server", function() {
323 var server; this.mitm.on("connection", function(s) { server = s })
324 Net.connect({host: "foo"})
325 server.ref()
326 })
327 })
328
329 describe(".prototype.unref", function() {
330 it("must allow calling on client", function() {
331 Net.connect({host: "foo"}).unref()
332 })
333
334 it("must allow calling on server", function() {
335 var server; this.mitm.on("connection", function(s) { server = s })
336 Net.connect({host: "foo"})
337 server.unref()
338 })
339 })
340 })
341 })
342
343 describe("Net.createConnection", function() {
344 beforeEach(function() { this.mitm = Mitm() })
345 afterEach(function() { this.mitm.disable() })
346
347 it("must be equal to Net.connect", function() {
348 Net.createConnection.must.equal(Net.connect)
349 })
350 })
351
352 describe("Tls.connect", function() {
353 beforeEach(function() { this.mitm = Mitm() })
354 afterEach(function() { this.mitm.disable() })
355
356 mustConnect(Tls)
357
358 if (!NODE_0_10)
359 it("must return an instance of Tls.TLSSocket", function() {
360 Tls.connect({host: "foo", port: 80}).must.be.an.instanceof(Tls.TLSSocket)
361 })
362
363 if (!NODE_0_10)
364 it("must return an instance of Tls.TLSSocket given port", function() {
365 Tls.connect(80).must.be.an.instanceof(Tls.TLSSocket)
366 })
367
368 if (!NODE_0_10)
369 it("must return an instance of Tls.TLSSocket given port and host",
370 function() {
371 Tls.connect(80, "10.0.0.1").must.be.an.instanceof(Tls.TLSSocket)
372 })
373
374 it("must emit secureConnect in next ticks", function(done) {
375 var socket = Tls.connect({host: "foo"})
376 socket.on("secureConnect", done.bind(null, null))
377 })
378
379 it("must emit secureConnect after connect in next ticks", function(done) {
380 var socket = Tls.connect({host: "foo"})
381
382 socket.on("connect", function() {
383 socket.on("secureConnect", done.bind(null, null))
384 })
385 })
386
387 it("must not emit secureConnect on server", function(done) {
388 var server; this.mitm.on("connection", function(s) { server = s })
389 Tls.connect({host: "foo"})
390 // Let Mocha raise an error when done called twice.
391 server.on("secureConnect", done.bind(null, null))
392 done()
393 })
394
395 it("must call back on secureConnect", function(done) {
396 var connected = false
397
398 var client = Tls.connect({host: "foo"}, function() {
399 connected.must.be.true()
400 done()
401 })
402
403 client.on("connect", function() { connected = true })
404 })
405
406 it("must set encrypted true", function() {
407 Tls.connect({host: "foo"}).encrypted.must.be.true()
408 })
409
410 it("must set authorized true", function() {
411 Tls.connect({host: "foo"}).authorized.must.be.true()
412 })
413 })
414
415 function mustRequest(request) {
416 describe("as request", function() {
417 beforeEach(function() { this.mitm.disable() })
418 beforeEach(function() { this.mitm = Mitm() })
419 afterEach(function() { this.mitm.disable() })
420
421 it("must return ClientRequest", function() {
422 request({host: "foo"}).must.be.an.instanceof(ClientRequest)
423 })
424
425 it("must emit connect on Mitm", function() {
426 var onConnect = Sinon.spy()
427 this.mitm.on("connect", onConnect)
428 request({host: "foo"})
429 onConnect.callCount.must.equal(1)
430 })
431
432 it("must emit connect on Mitm after multiple connections", function() {
433 var onConnect = Sinon.spy()
434 this.mitm.on("connect", onConnect)
435 request({host: "foo"})
436 request({host: "foo"})
437 request({host: "foo"})
438 onConnect.callCount.must.equal(3)
439 })
440
441 it("must emit connection on Mitm", function() {
442 var onConnection = Sinon.spy()
443 this.mitm.on("connection", onConnection)
444 request({host: "foo"})
445 onConnection.callCount.must.equal(1)
446 })
447
448 it("must emit connection on Mitm after multiple connections", function() {
449 var onConnection = Sinon.spy()
450 this.mitm.on("connection", onConnection)
451 request({host: "foo"})
452 request({host: "foo"})
453 request({host: "foo"})
454 onConnection.callCount.must.equal(3)
455 })
456
457 it("must emit request on Mitm", function(done) {
458 var client = request({host: "foo"})
459 client.end()
460
461 this.mitm.on("request", function(req, res) {
462 req.must.be.an.instanceof(IncomingMessage)
463 req.must.not.equal(client)
464 res.must.be.an.instanceof(ServerResponse)
465 done()
466 })
467 })
468
469 it("must emit request on Mitm after multiple requests", function(done) {
470 request({host: "foo"}).end()
471 request({host: "foo"}).end()
472 request({host: "foo"}).end()
473 this.mitm.on("request", _.after(3, done.bind(null, null)))
474 })
475
476 it("must emit socket on request in next ticks", function(done) {
477 var client = request({host: "foo"})
478 client.on("socket", done.bind(null, null))
479 })
480
481 // https://github.com/moll/node-mitm/pull/25
482 it("must emit connect after socket event", function(done) {
483 var client = request({host: "foo"})
484
485 client.on("socket", function(socket) {
486 socket.on("connect", done.bind(null, null))
487 })
488 })
489
490 describe("when bypassed", function() {
491 it("must not intercept", function(done) {
492 this.mitm.on("connect", function(client) { client.bypass() })
493 request({host: "127.0.0.1"}).on("error", function(err) {
494 err.must.be.an.instanceof(Error)
495 err.message.must.include("ECONNREFUSED")
496 done()
497 })
498 })
499
500 it("must not emit request", function(done) {
501 this.mitm.on("connect", function(client) { client.bypass() })
502 var onRequest = Sinon.spy()
503 this.mitm.on("request", onRequest)
504 request({host: "127.0.0.1"}).on("error", function(err) {
505 onRequest.callCount.must.equal(0)
506 done()
507 })
508 })
509 })
510 })
511 }
512
513 describe("via Http.request", function() {
514 mustRequest(Http.request)
515 })
516
517 describe("via Https.request", function() {
518 beforeEach(function() { this.mitm = Mitm() })
519 afterEach(function() { this.mitm.disable() })
520
521 mustRequest(Https.request)
522
523 // https://github.com/moll/node-mitm/pull/25
524 it("must emit secureConnect after socket event", function(done) {
525 var client = Https.request({host: "foo"})
526
527 client.on("socket", function(socket) {
528 socket.on("secureConnect", done.bind(null, null))
529 })
530 })
531 })
532
533 describe("via Http.Agent", function() {
534 mustRequest(function(opts) {
535 return Http.request(_.extend({agent: new Http.Agent}, opts))
536 })
537 })
538
539 describe("via Https.Agent", function() {
540 mustRequest(function(opts) {
541 return Https.request(_.extend({agent: new Https.Agent}, opts))
542 })
543 })
544
545 describe("IncomingMessage", function() {
546 beforeEach(function() { this.mitm = Mitm() })
547 afterEach(function() { this.mitm.disable() })
548
549 it("must have URL", function(done) {
550 Http.request({host: "foo", path: "/foo"}).end()
551
552 this.mitm.on("request", function(req) {
553 req.url.must.equal("/foo")
554 done()
555 })
556 })
557
558 it("must have headers", function(done) {
559 var req = Http.request({host: "foo"})
560 req.setHeader("Content-Type", "application/json")
561 req.end()
562
563 this.mitm.on("request", function(req) {
564 req.headers["content-type"].must.equal("application/json")
565 done()
566 })
567 })
568
569 it("must have body", function(done) {
570 var client = Http.request({host: "foo", method: "POST"})
571 client.write("Hello")
572
573 this.mitm.on("request", function(req, res) {
574 req.setEncoding("utf8")
575 req.on("data", function(data) { data.must.equal("Hello"); done() })
576 })
577 })
578
579 it("must have a reference to the ServerResponse", function(done) {
580 Http.request({host: "foo", method: "POST"}).end()
581 this.mitm.on("request", function(req, res) { req.res.must.equal(res) })
582 this.mitm.on("request", done.bind(null, null))
583 })
584 })
585
586 describe("ServerResponse", function() {
587 beforeEach(function() { this.mitm = Mitm() })
588 afterEach(function() { this.mitm.disable() })
589
590 it("must respond with status, headers and body", function(done) {
591 this.mitm.on("request", function(req, res) {
592 res.statusCode = 442
593 res.setHeader("Content-Type", "application/json")
594 res.end("Hi!")
595 })
596
597 Http.request({host: "foo"}).on("response", function(res) {
598 res.statusCode.must.equal(442)
599 res.headers["content-type"].must.equal("application/json")
600 res.setEncoding("utf8")
601 res.once("data", function(data) { data.must.equal("Hi!"); done() })
602 }).end()
603 })
604
605 it("must have a reference to the IncomingMessage", function(done) {
606 Http.request({host: "foo", method: "POST"}).end()
607 this.mitm.on("request", function(req, res) { res.req.must.equal(req) })
608 this.mitm.on("request", done.bind(null, null))
609 })
610
611 describe(".prototype.write", function() {
612 it("must make clientRequest emit response", function(done) {
613 var req = Http.request({host: "foo"})
614 req.end()
615 this.mitm.on("request", function(req, res) { res.write("Test") })
616 req.on("response", done.bind(null, null))
617 })
618
619 // Under Node v0.10 it's the writeQueueSize that's checked to see if
620 // the callback can be called.
621 it("must call given callback", function(done) {
622 Http.request({host: "foo"}).end()
623 this.mitm.on("request", function(req, res) { res.write("Test", done) })
624 })
625 })
626
627 describe(".prototype.end", function() {
628 it("must make ClientRequest emit response", function(done) {
629 var client = Http.request({host: "foo"})
630 client.end()
631 this.mitm.on("request", function(req, res) { res.end() })
632 client.on("response", done.bind(null, null))
633 })
634
635 // In an app of mine Node v0.11.7 did not emit the end event, but
636 // v0.11.11 did. I'll investigate properly if this becomes a problem in
637 // later Node versions.
638 it("must make IncomingMessage emit end", function(done) {
639 var client = Http.request({host: "foo"})
640 client.end()
641 this.mitm.on("request", function(req, res) { res.end() })
642
643 client.on("response", function(res) {
644 res.on("data", noop)
645 res.on("end", done)
646 })
647 })
648 })
649 })
650
651 describe(".prototype.addListener", function() {
652 it("must be an alias to EventEmitter.prototype.addListener", function() {
653 Mitm.prototype.addListener.must.equal(EventEmitter.prototype.addListener)
654 })
655 })
656
657 describe(".prototype.off", function() {
658 it("must be an alias to EventEmitter.prototype.removeListener", function() {
659 Mitm.prototype.off.must.equal(EventEmitter.prototype.removeListener)
660 })
661 })
662})
663
664function noop() {}