Passed
Pull Request — master (#68)
by Stefano
02:12
created

pkg/cmd/scan_integration_test.go   C

Size/Duplication

Total Lines 686
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
cc 53
eloc 467
dl 0
loc 686
rs 6.96
c 0
b 0
f 0

18 Methods

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