Passed
Push — master ( 829c59...8c7abf )
by Stefano
02:13
created

usesToIgnoreShouldErr   A

Complexity

Conditions 2

Size

Total Lines 29
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

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