Passed
Push — master ( 938cbd...107211 )
by Stefano
01:21
created

municateWithServerHavingInvalidSSLCertificates   A

Complexity

Conditions 2

Size

Total Lines 27
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 19
nop 1
dl 0
loc 27
rs 9.45
c 0
b 0
f 0
1
package cmd_test
2
3
import (
4
	"io/ioutil"
5
	"net"
6
	"net/http"
7
	"net/http/httptest"
8
	"os"
9
	"sync"
10
	"syscall"
11
	"testing"
12
	"time"
13
14
	"github.com/armon/go-socks5"
15
	"github.com/stefanoj3/dirstalk/pkg/common/test"
16
	"github.com/stretchr/testify/assert"
17
)
18
19
const socks5TestServerHost = "127.0.0.1:8899"
20
21
func TestScanCommand(t *testing.T) {
22
	logger, loggerBuffer := test.NewLogger()
23
24
	c := createCommand(logger)
25
	assert.NotNil(t, c)
26
27
	testServer, serverAssertion := test.NewServerWithAssertion(
28
		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
29
			if r.URL.Path == "/test/" {
30
				w.WriteHeader(http.StatusOK)
31
				return
32
			}
33
			if r.URL.Path == "/potato" {
34
				w.WriteHeader(http.StatusOK)
35
				return
36
			}
37
38
			if r.URL.Path == "/test/test/" {
39
				http.Redirect(w, r, "/potato", http.StatusMovedPermanently)
40
				return
41
			}
42
43
			w.WriteHeader(http.StatusNotFound)
44
		}),
45
	)
46
	defer testServer.Close()
47
48
	err := executeCommand(
49
		c,
50
		"scan",
51
		testServer.URL,
52
		"--dictionary",
53
		"testdata/dict2.txt",
54
		"-v",
55
		"--http-statuses-to-ignore",
56
		"404",
57
		"--http-timeout",
58
		"300",
59
	)
60
	assert.NoError(t, err)
61
62
	assert.Equal(t, 17, serverAssertion.Len())
63
64
	requestsMap := map[string]string{}
65
66
	serverAssertion.Range(func(_ int, r http.Request) {
67
		requestsMap[r.URL.Path] = r.Method
68
	})
69
70
	expectedRequests := map[string]string{
71
		"/test/":               http.MethodGet,
72
		"/test/home":           http.MethodGet,
73
		"/test/blabla":         http.MethodGet,
74
		"/test/home/index.php": http.MethodGet,
75
		"/potato":              http.MethodGet,
76
77
		"/potato/test/":          http.MethodGet,
78
		"/potato/home":           http.MethodGet,
79
		"/potato/home/index.php": http.MethodGet,
80
		"/potato/blabla":         http.MethodGet,
81
82
		"/test/test/test/":          http.MethodGet,
83
		"/test/test/home":           http.MethodGet,
84
		"/test/test/home/index.php": http.MethodGet,
85
		"/test/test/blabla":         http.MethodGet,
86
87
		"/test/test/": http.MethodGet,
88
89
		"/home":           http.MethodGet,
90
		"/blabla":         http.MethodGet,
91
		"/home/index.php": http.MethodGet,
92
	}
93
94
	assert.Equal(t, expectedRequests, requestsMap)
95
96
	expectedResultTree := `/
97
├── potato
98
└── test
99
    └── test
100
101
`
102
103
	assert.Contains(t, loggerBuffer.String(), expectedResultTree)
104
}
105
106
func TestScanShouldWriteOutput(t *testing.T) {
107
	logger, _ := test.NewLogger()
108
109
	c := createCommand(logger)
110
	assert.NotNil(t, c)
111
112
	testServer, _ := test.NewServerWithAssertion(
113
		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
114
			if r.URL.Path == "/home" {
115
				w.WriteHeader(http.StatusOK)
116
				return
117
			}
118
119
			w.WriteHeader(http.StatusNotFound)
120
		}),
121
	)
122
	defer testServer.Close()
123
124
	outputFilename := test.RandStringRunes(10)
125
	outputFilename = "testdata/out/" + outputFilename + ".txt"
126
127
	defer func() {
128
		err := os.Remove(outputFilename)
129
		if err != nil {
130
			panic("failed to remove file create during test: " + err.Error())
131
		}
132
	}()
133
134
	err := executeCommand(
135
		c,
136
		"scan",
137
		testServer.URL,
138
		"--dictionary",
139
		"testdata/dict2.txt",
140
		"--out",
141
		outputFilename,
142
	)
143
	assert.NoError(t, err)
144
145
	//nolint:gosec
146
	file, err := os.Open(outputFilename)
147
	assert.NoError(t, err)
148
149
	b, err := ioutil.ReadAll(file)
150
	assert.NoError(t, err, "failed to read file content")
151
152
	expected := `{"Target":{"Path":"home","Method":"GET","Depth":3},"StatusCode":200,"URL":{"Scheme":"http","Opaque":"","User":null,"Host":"` +
153
		testServer.Listener.Addr().String() +
154
		`","Path":"/home","RawPath":"","ForceQuery":false,"RawQuery":"","Fragment":""}}
155
`
156
	assert.Equal(t, expected, string(b))
157
158
	assert.NoError(t, file.Close(), "failed to close file")
159
}
160
161
func TestScanInvalidOutputFileShouldErr(t *testing.T) {
162
	logger, _ := test.NewLogger()
163
164
	c := createCommand(logger)
165
	assert.NotNil(t, c)
166
167
	testServer, _ := test.NewServerWithAssertion(
168
		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
169
			if r.URL.Path == "/home" {
170
				w.WriteHeader(http.StatusOK)
171
				return
172
			}
173
174
			w.WriteHeader(http.StatusNotFound)
175
		}),
176
	)
177
	defer testServer.Close()
178
179
	err := executeCommand(
180
		c,
181
		"scan",
182
		testServer.URL,
183
		"--dictionary",
184
		"testdata/dict2.txt",
185
		"--out",
186
		"/root/blabla/123/gibberish/123",
187
	)
188
	assert.Error(t, err)
189
	assert.Contains(t, err.Error(), "failed to create output saver")
190
}
191
192
func TestScanWithInvalidStatusesToIgnoreShouldErr(t *testing.T) {
193
	logger, _ := test.NewLogger()
194
195
	c := createCommand(logger)
196
	assert.NotNil(t, c)
197
198
	testServer, serverAssertion := test.NewServerWithAssertion(
199
		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}),
200
	)
201
	defer testServer.Close()
202
203
	err := executeCommand(
204
		c,
205
		"scan",
206
		testServer.URL,
207
		"--dictionary",
208
		"testdata/dict2.txt",
209
		"-v",
210
		"--http-statuses-to-ignore",
211
		"300,gibberish,404",
212
		"--http-timeout",
213
		"300",
214
	)
215
	assert.Error(t, err)
216
	assert.Contains(t, err.Error(), "strconv.Atoi: parsing")
217
	assert.Contains(t, err.Error(), "gibberish")
218
219
	assert.Equal(t, 0, serverAssertion.Len())
220
}
221
222
func TestScanWithNoTargetShouldErr(t *testing.T) {
223
	logger, _ := test.NewLogger()
224
225
	c := createCommand(logger)
226
	assert.NotNil(t, c)
227
228
	err := executeCommand(c, "scan", "--dictionary", "testdata/dict2.txt")
229
	assert.Error(t, err)
230
	assert.Contains(t, err.Error(), "no URL provided")
231
}
232
233
func TestScanWithInvalidTargetShouldErr(t *testing.T) {
234
	logger, _ := test.NewLogger()
235
236
	c := createCommand(logger)
237
	assert.NotNil(t, c)
238
239
	err := executeCommand(c, "scan", "--dictionary", "testdata/dict2.txt", "localhost%%2")
240
	assert.Error(t, err)
241
	assert.Contains(t, err.Error(), "invalid URI")
242
}
243
244
func TestScanCommandCanBeInterrupted(t *testing.T) {
245
	logger, loggerBuffer := test.NewLogger()
246
247
	c := createCommand(logger)
248
	assert.NotNil(t, c)
249
250
	testServer, _ := test.NewServerWithAssertion(
251
		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
252
			time.Sleep(time.Millisecond * 650)
253
254
			if r.URL.Path == "/test/" {
255
				w.WriteHeader(http.StatusOK)
256
				return
257
			}
258
259
			w.WriteHeader(http.StatusNotFound)
260
		}),
261
	)
262
	defer testServer.Close()
263
264
	go func() {
265
		time.Sleep(time.Millisecond * 200)
266
		_ = syscall.Kill(syscall.Getpid(), syscall.SIGINT) //nolint:errcheck
267
	}()
268
269
	err := executeCommand(
270
		c,
271
		"scan",
272
		testServer.URL,
273
		"--dictionary",
274
		"testdata/dict2.txt",
275
		"-v",
276
		"--http-timeout",
277
		"900",
278
	)
279
	assert.NoError(t, err)
280
281
	assert.Contains(t, loggerBuffer.String(), "Received sigint")
282
}
283
284
func TestScanWithRemoteDictionary(t *testing.T) {
285
	logger, _ := test.NewLogger()
286
287
	c := createCommand(logger)
288
	assert.NotNil(t, c)
289
290
	dictionaryServer := httptest.NewServer(
291
		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
292
			dict := `home
293
home/index.php
294
blabla
295
`
296
			w.WriteHeader(http.StatusOK)
297
			_, _ = w.Write([]byte(dict)) //nolint:errcheck
298
		}),
299
	)
300
	defer dictionaryServer.Close()
301
302
	testServer, serverAssertion := test.NewServerWithAssertion(
303
		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
304
			w.WriteHeader(http.StatusNotFound)
305
		}),
306
	)
307
	defer testServer.Close()
308
309
	err := executeCommand(
310
		c,
311
		"scan",
312
		testServer.URL,
313
		"--dictionary",
314
		dictionaryServer.URL,
315
		"--http-timeout",
316
		"300",
317
	)
318
	assert.NoError(t, err)
319
320
	assert.Equal(t, 3, serverAssertion.Len())
321
}
322
323
func TestScanWithUserAgentFlag(t *testing.T) {
324
	const testUserAgent = "my_test_user_agent"
325
326
	logger, loggerBuffer := test.NewLogger()
327
328
	c := createCommand(logger)
329
	assert.NotNil(t, c)
330
331
	testServer, serverAssertion := test.NewServerWithAssertion(
332
		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
333
			w.WriteHeader(http.StatusNotFound)
334
		}),
335
	)
336
	defer testServer.Close()
337
338
	err := executeCommand(
339
		c,
340
		"scan",
341
		testServer.URL,
342
		"--user-agent",
343
		testUserAgent,
344
		"--dictionary",
345
		"testdata/dict.txt",
346
		"--http-timeout",
347
		"300",
348
	)
349
	assert.NoError(t, err)
350
351
	assert.Equal(t, 3, serverAssertion.Len())
352
	serverAssertion.Range(func(_ int, r http.Request) {
353
		assert.Equal(t, testUserAgent, r.Header.Get("User-Agent"))
354
	})
355
356
	// to ensure we print the user agent to the cli
357
	assert.Contains(t, loggerBuffer.String(), testUserAgent)
358
}
359
360
func TestScanWithCookies(t *testing.T) {
361
	logger, loggerBuffer := test.NewLogger()
362
363
	c := createCommand(logger)
364
	assert.NotNil(t, c)
365
366
	testServer, serverAssertion := test.NewServerWithAssertion(
367
		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}),
368
	)
369
	defer testServer.Close()
370
371
	err := executeCommand(
372
		c,
373
		"scan",
374
		testServer.URL,
375
		"--cookie",
376
		"name1=val1",
377
		"--cookie",
378
		"name2=val2",
379
		"--dictionary",
380
		"testdata/dict.txt",
381
		"--http-timeout",
382
		"300",
383
	)
384
	assert.NoError(t, err)
385
386
	serverAssertion.Range(func(_ int, r http.Request) {
387
		assert.Equal(t, 2, len(r.Cookies()))
388
389
		assert.Equal(t, r.Cookies()[0].Name, "name1")
390
		assert.Equal(t, r.Cookies()[0].Value, "val1")
391
392
		assert.Equal(t, r.Cookies()[1].Name, "name2")
393
		assert.Equal(t, r.Cookies()[1].Value, "val2")
394
	})
395
396
	// to ensure we print the cookies to the cli
397
	assert.Contains(t, loggerBuffer.String(), "name1=val1")
398
	assert.Contains(t, loggerBuffer.String(), "name2=val2")
399
}
400
401
func TestWhenProvidingCookiesInWrongFormatShouldErr(t *testing.T) {
402
	const malformedCookie = "gibberish"
403
404
	logger, _ := test.NewLogger()
405
406
	c := createCommand(logger)
407
	assert.NotNil(t, c)
408
409
	testServer, serverAssertion := test.NewServerWithAssertion(
410
		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
411
			w.WriteHeader(http.StatusNotFound)
412
		}),
413
	)
414
	defer testServer.Close()
415
416
	err := executeCommand(
417
		c,
418
		"scan",
419
		testServer.URL,
420
		"--cookie",
421
		malformedCookie,
422
		"--dictionary",
423
		"testdata/dict.txt",
424
	)
425
	assert.Error(t, err)
426
	assert.Contains(t, err.Error(), "cookie format is invalid")
427
	assert.Contains(t, err.Error(), malformedCookie)
428
429
	assert.Equal(t, 0, serverAssertion.Len())
430
}
431
432
func TestScanWithCookieJar(t *testing.T) {
433
	const (
434
		serverCookieName  = "server_cookie_name"
435
		serverCookieValue = "server_cookie_value"
436
	)
437
438
	logger, _ := test.NewLogger()
439
440
	c := createCommand(logger)
441
	assert.NotNil(t, c)
442
443
	once := sync.Once{}
444
	testServer, serverAssertion := test.NewServerWithAssertion(
445
		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
446
			once.Do(func() {
447
				http.SetCookie(
448
					w,
449
					&http.Cookie{
450
						Name:    serverCookieName,
451
						Value:   serverCookieValue,
452
						Expires: time.Now().AddDate(0, 1, 0),
453
					},
454
				)
455
			})
456
		}),
457
	)
458
459
	defer testServer.Close() //nolint:errcheck
460
461
	err := executeCommand(
462
		c,
463
		"scan",
464
		testServer.URL,
465
		"--use-cookie-jar",
466
		"--dictionary",
467
		"testdata/dict.txt",
468
		"--http-timeout",
469
		"300",
470
		"-t",
471
		"1",
472
	)
473
	assert.NoError(t, err)
474
475
	serverAssertion.Range(func(index int, r http.Request) {
476
		if index == 0 { // first request should have no cookies
477
			assert.Equal(t, 0, len(r.Cookies()))
478
			return
479
		}
480
481
		assert.Equal(t, 1, len(r.Cookies()))
482
		assert.Equal(t, r.Cookies()[0].Name, serverCookieName)
483
		assert.Equal(t, r.Cookies()[0].Value, serverCookieValue)
484
	})
485
}
486
487
func TestScanWithUnknownFlagShouldErr(t *testing.T) {
488
	logger, _ := test.NewLogger()
489
490
	c := createCommand(logger)
491
	assert.NotNil(t, c)
492
493
	testServer, serverAssertion := test.NewServerWithAssertion(
494
		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}),
495
	)
496
	defer testServer.Close()
497
498
	err := executeCommand(
499
		c,
500
		"scan",
501
		testServer.URL,
502
		"--gibberishflag",
503
		"--dictionary",
504
		"testdata/dict.txt",
505
	)
506
	assert.Error(t, err)
507
	assert.Contains(t, err.Error(), "unknown flag")
508
509
	assert.Equal(t, 0, serverAssertion.Len())
510
}
511
512
func TestScanWithHeaders(t *testing.T) {
513
	logger, loggerBuffer := test.NewLogger()
514
515
	c := createCommand(logger)
516
	assert.NotNil(t, c)
517
518
	testServer, serverAssertion := test.NewServerWithAssertion(
519
		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}),
520
	)
521
	defer testServer.Close()
522
523
	err := executeCommand(
524
		c,
525
		"scan",
526
		testServer.URL,
527
		"--header",
528
		"Accept-Language: en-US,en;q=0.5",
529
		"--header",
530
		`"Authorization: Bearer 123"`,
531
		"--dictionary",
532
		"testdata/dict.txt",
533
		"--http-timeout",
534
		"300",
535
	)
536
	assert.NoError(t, err)
537
538
	serverAssertion.Range(func(_ int, r http.Request) {
539
		assert.Equal(t, 2, len(r.Header))
540
541
		assert.Equal(t, "en-US,en;q=0.5", r.Header.Get("Accept-Language"))
542
		assert.Equal(t, "Bearer 123", r.Header.Get("Authorization"))
543
	})
544
545
	// to ensure we print the headers to the cli
546
	assert.Contains(t, loggerBuffer.String(), "Accept-Language")
547
	assert.Contains(t, loggerBuffer.String(), "Authorization")
548
	assert.Contains(t, loggerBuffer.String(), "Bearer 123")
549
}
550
551
func TestScanWithMalformedHeaderShouldErr(t *testing.T) {
552
	const malformedHeader = "gibberish"
553
554
	logger, _ := test.NewLogger()
555
556
	c := createCommand(logger)
557
	assert.NotNil(t, c)
558
559
	testServer, serverAssertion := test.NewServerWithAssertion(
560
		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}),
561
	)
562
	defer testServer.Close()
563
564
	err := executeCommand(
565
		c,
566
		"scan",
567
		testServer.URL,
568
		"--header",
569
		"Accept-Language: en-US,en;q=0.5",
570
		"--header",
571
		malformedHeader,
572
		"--dictionary",
573
		"testdata/dict.txt",
574
	)
575
	assert.Error(t, err)
576
	assert.Contains(t, err.Error(), malformedHeader)
577
	assert.Contains(t, err.Error(), "header is in invalid format")
578
579
	assert.Equal(t, 0, serverAssertion.Len())
580
}
581
582
func TestStartScanWithSocks5ShouldFindResultsWhenAServerIsAvailable(t *testing.T) {
583
	logger, _ := test.NewLogger()
584
585
	c := createCommand(logger)
586
	assert.NotNil(t, c)
587
588
	testServer, serverAssertion := test.NewServerWithAssertion(
589
		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
590
			w.WriteHeader(http.StatusNotFound)
591
		}),
592
	)
593
	defer testServer.Close()
594
595
	socks5Server := startSocks5TestServer(t)
596
	defer socks5Server.Close() //nolint:errcheck
597
598
	err := executeCommand(
599
		c,
600
		"scan",
601
		testServer.URL,
602
		"--dictionary",
603
		"testdata/dict.txt",
604
		"-v",
605
		"--http-timeout",
606
		"300",
607
		"--socks5",
608
		socks5TestServerHost,
609
	)
610
	assert.NoError(t, err)
611
612
	assert.Equal(t, 3, serverAssertion.Len())
613
}
614
615
func TestShouldFailToScanWithAnUnreachableSocks5Server(t *testing.T) {
616
	logger, loggerBuffer := test.NewLogger()
617
618
	c := createCommand(logger)
619
	assert.NotNil(t, c)
620
621
	testServer, serverAssertion := test.NewServerWithAssertion(
622
		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
623
			w.WriteHeader(http.StatusNotFound)
624
		}),
625
	)
626
	defer testServer.Close()
627
628
	socks5Server := startSocks5TestServer(t)
629
	defer socks5Server.Close() //nolint:errcheck
630
631
	err := executeCommand(
632
		c,
633
		"scan",
634
		testServer.URL,
635
		"--dictionary",
636
		"testdata/dict.txt",
637
		"-v",
638
		"--http-timeout",
639
		"300",
640
		"--socks5",
641
		"127.0.0.1:9555", // invalid
642
	)
643
	assert.NoError(t, err)
644
645
	assert.Equal(t, 0, serverAssertion.Len())
646
	assert.Contains(t, loggerBuffer.String(), "failed to perform request")
647
	assert.Contains(t, loggerBuffer.String(), "socks connect tcp")
648
	assert.Contains(t, loggerBuffer.String(), "connect: connection refused")
649
}
650
651
func TestShouldFailToStartWithAnInvalidSocks5Address(t *testing.T) {
652
	logger, _ := test.NewLogger()
653
654
	c := createCommand(logger)
655
	assert.NotNil(t, c)
656
657
	testServer, serverAssertion := test.NewServerWithAssertion(
658
		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
659
			w.WriteHeader(http.StatusNotFound)
660
		}),
661
	)
662
	defer testServer.Close()
663
664
	err := executeCommand(
665
		c,
666
		"scan",
667
		testServer.URL,
668
		"--dictionary",
669
		"testdata/dict.txt",
670
		"-v",
671
		"--http-timeout",
672
		"300",
673
		"--socks5",
674
		"localhost%%2", // invalid
675
	)
676
	assert.Error(t, err)
677
	assert.Contains(t, err.Error(), "invalid URL escape")
678
679
	assert.Equal(t, 0, serverAssertion.Len())
680
}
681
682
func TestScanShouldFailToCommunicateWithServerHavingInvalidSSLCertificates(t *testing.T) {
683
	logger, loggerBuffer := test.NewLogger()
684
685
	c := createCommand(logger)
686
	assert.NotNil(t, c)
687
688
	testServer, serverAssertion := test.NewTSLServerWithAssertion(
689
		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
690
			w.WriteHeader(http.StatusOK)
691
		}),
692
	)
693
	defer testServer.Close()
694
695
	err := executeCommand(
696
		c,
697
		"scan",
698
		testServer.URL,
699
		"--dictionary",
700
		"testdata/dict2.txt",
701
		"--scan-depth",
702
		"1",
703
	)
704
	assert.NoError(t, err)
705
706
	assert.Equal(t, 0, serverAssertion.Len())
707
708
	assert.Contains(t, loggerBuffer.String(), "certificate signed by unknown authority")
709
}
710
711
func TestScanShouldBeAbleToSkipSSLCertificatesCheck(t *testing.T) {
712
	logger, loggerBuffer := test.NewLogger()
713
714
	c := createCommand(logger)
715
	assert.NotNil(t, c)
716
717
	testServer, serverAssertion := test.NewTSLServerWithAssertion(
718
		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
719
			w.WriteHeader(http.StatusOK)
720
		}),
721
	)
722
	defer testServer.Close()
723
724
	err := executeCommand(
725
		c,
726
		"scan",
727
		testServer.URL,
728
		"--dictionary",
729
		"testdata/dict2.txt",
730
		"--scan-depth",
731
		"1",
732
		"--no-check-certificate",
733
	)
734
	assert.NoError(t, err)
735
736
	assert.Equal(t, 16, serverAssertion.Len())
737
738
	assert.NotContains(t, loggerBuffer.String(), "certificate signed by unknown authority")
739
}
740
741
func startSocks5TestServer(t *testing.T) net.Listener {
742
	conf := &socks5.Config{}
743
744
	server, err := socks5.New(conf)
745
	if err != nil {
746
		t.Fatalf("failed to create socks5: %s", err.Error())
747
	}
748
749
	listener, err := net.Listen("tcp", socks5TestServerHost)
750
	if err != nil {
751
		t.Fatalf("failed to create listener: %s", err.Error())
752
	}
753
754
	go func() {
755
		// Create SOCKS5 proxy on localhost port 8000
756
		if err := server.Serve(listener); err != nil {
757
			t.Logf("socks5 stopped serving: %s", err.Error())
758
		}
759
	}()
760
761
	return listener
762
}
763