Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/protocols/http/httpclientpool/clientpool_test.go
4538 views
1
package httpclientpool
2
3
import (
4
"net/http"
5
"net/url"
6
"testing"
7
)
8
9
func TestNormalizeHost(t *testing.T) {
10
tests := []struct {
11
name string
12
scheme string
13
host string
14
want string
15
}{
16
// No port
17
{"http no port", "http", "example.com", "example.com"},
18
{"https no port", "https", "example.com", "example.com"},
19
20
// Default ports stripped
21
{"http default port 80", "http", "example.com:80", "example.com"},
22
{"https default port 443", "https", "example.com:443", "example.com"},
23
24
// Non-default ports preserved
25
{"http non-default port", "http", "example.com:8080", "example.com:8080"},
26
{"https non-default port", "https", "example.com:8443", "example.com:8443"},
27
28
// Cross-scheme default ports preserved (443 is not default for http)
29
{"http with port 443", "http", "example.com:443", "example.com:443"},
30
{"https with port 80", "https", "example.com:80", "example.com:80"},
31
32
// IP addresses
33
{"ipv4 no port", "http", "127.0.0.1", "127.0.0.1"},
34
{"ipv4 default port", "http", "127.0.0.1:80", "127.0.0.1"},
35
{"ipv4 non-default port", "http", "127.0.0.1:8080", "127.0.0.1:8080"},
36
{"ipv4 https default port", "https", "10.0.0.1:443", "10.0.0.1"},
37
38
// IPv6 addresses
39
{"ipv6 no port", "http", "[::1]", "[::1]"},
40
{"ipv6 default port http", "http", "[::1]:80", "[::1]"},
41
{"ipv6 default port https", "https", "[::1]:443", "[::1]"},
42
{"ipv6 non-default port", "https", "[::1]:8443", "[::1]:8443"},
43
{"ipv6 full address default port", "http", "[2001:db8::1]:80", "[2001:db8::1]"},
44
{"ipv6 full address non-default", "http", "[2001:db8::1]:9090", "[2001:db8::1]:9090"},
45
46
// Empty host
47
{"empty host", "http", "", ""},
48
}
49
for _, tt := range tests {
50
t.Run(tt.name, func(t *testing.T) {
51
u := &url.URL{Scheme: tt.scheme, Host: tt.host}
52
got := normalizeHost(u)
53
if got != tt.want {
54
t.Errorf("normalizeHost(%s://%s) = %q, want %q", tt.scheme, tt.host, got, tt.want)
55
}
56
})
57
}
58
}
59
60
func TestFollowSameHostRedirectWithPort(t *testing.T) {
61
tests := []struct {
62
name string
63
oldURL string
64
newURL string
65
shouldAllow bool
66
}{
67
// Basic same host
68
{"same host no port", "http://example.com/a", "http://example.com/b", true},
69
{"same host same path", "http://example.com/a", "http://example.com/a", true},
70
71
// Default port normalization (the bug from #4685)
72
{"old has :80 new does not", "http://example.com:80/a", "http://example.com/b", true},
73
{"new has :80 old does not", "http://example.com/a", "http://example.com:80/b", true},
74
{"both have :80", "http://example.com:80/a", "http://example.com:80/b", true},
75
{"old has :443 new does not https", "https://example.com:443/a", "https://example.com/b", true},
76
{"new has :443 old does not https", "https://example.com/a", "https://example.com:443/b", true},
77
{"both have :443 https", "https://example.com:443/a", "https://example.com:443/b", true},
78
79
// Relative redirect (host carried from original request)
80
{"relative redirect same host", "http://example.com/a", "http://example.com/target", true},
81
82
// IP address with default port
83
{"ip old has :80 new does not", "http://127.0.0.1:80/a", "http://127.0.0.1/b", true},
84
{"ip new has :80 old does not", "http://127.0.0.1/a", "http://127.0.0.1:80/b", true},
85
{"ip both no port", "http://127.0.0.1/a", "http://127.0.0.1/b", true},
86
87
// Different hosts should be blocked
88
{"different host", "http://example.com/a", "http://other.com/b", false},
89
{"different host with port", "http://example.com:80/a", "http://other.com:80/b", false},
90
{"different ip", "http://127.0.0.1/a", "http://127.0.0.2/b", false},
91
92
// Non-default ports
93
{"same non-default port", "http://example.com:8080/a", "http://example.com:8080/b", true},
94
{"different non-default port", "http://example.com:8080/a", "http://example.com:9090/b", false},
95
{"non-default vs no port", "http://example.com:8080/a", "http://example.com/b", false},
96
{"no port vs non-default", "http://example.com/a", "http://example.com:8080/b", false},
97
98
// Cross-scheme port (443 on http is non-default, should not strip)
99
{"http port 443 vs no port", "http://example.com:443/a", "http://example.com/b", false},
100
{"https port 80 vs no port", "https://example.com:80/a", "https://example.com/b", false},
101
102
// IPv6
103
{"ipv6 same host", "http://[::1]/a", "http://[::1]/b", true},
104
{"ipv6 default port normalization", "http://[::1]:80/a", "http://[::1]/b", true},
105
}
106
for _, tt := range tests {
107
t.Run(tt.name, func(t *testing.T) {
108
checkFn := makeCheckRedirectFunc(FollowSameHostRedirect, 10)
109
oldReq, _ := http.NewRequest("GET", tt.oldURL, nil)
110
newReq, _ := http.NewRequest("GET", tt.newURL, nil)
111
err := checkFn(newReq, []*http.Request{oldReq})
112
allowed := err == nil
113
if allowed != tt.shouldAllow {
114
t.Errorf("redirect from %q to %q: allowed=%v, want %v", tt.oldURL, tt.newURL, allowed, tt.shouldAllow)
115
}
116
})
117
}
118
}
119
120
func TestFollowSameHostRedirectViaHost(t *testing.T) {
121
// When via[0].Host is set (e.g. Host header override), it takes precedence over URL.Host
122
checkFn := makeCheckRedirectFunc(FollowSameHostRedirect, 10)
123
124
oldReq, _ := http.NewRequest("GET", "http://proxy.internal/a", nil)
125
oldReq.Host = "example.com"
126
newReq, _ := http.NewRequest("GET", "http://example.com/b", nil)
127
128
err := checkFn(newReq, []*http.Request{oldReq})
129
if err != nil {
130
t.Errorf("redirect should be allowed when via[0].Host matches new host, got err: %v", err)
131
}
132
133
// Mismatch: via[0].Host differs from redirect target
134
oldReq2, _ := http.NewRequest("GET", "http://proxy.internal/a", nil)
135
oldReq2.Host = "other.com"
136
newReq2, _ := http.NewRequest("GET", "http://example.com/b", nil)
137
138
err = checkFn(newReq2, []*http.Request{oldReq2})
139
if err == nil {
140
t.Errorf("redirect should be blocked when via[0].Host differs from new host")
141
}
142
}
143
144
func TestDontFollowRedirect(t *testing.T) {
145
checkFn := makeCheckRedirectFunc(DontFollowRedirect, 10)
146
oldReq, _ := http.NewRequest("GET", "http://example.com/a", nil)
147
newReq, _ := http.NewRequest("GET", "http://example.com/b", nil)
148
149
err := checkFn(newReq, []*http.Request{oldReq})
150
if err != http.ErrUseLastResponse {
151
t.Errorf("DontFollowRedirect should always return ErrUseLastResponse, got: %v", err)
152
}
153
}
154
155
func TestFollowAllRedirect(t *testing.T) {
156
checkFn := makeCheckRedirectFunc(FollowAllRedirect, 10)
157
158
// Same host
159
oldReq, _ := http.NewRequest("GET", "http://example.com/a", nil)
160
newReq, _ := http.NewRequest("GET", "http://example.com/b", nil)
161
if err := checkFn(newReq, []*http.Request{oldReq}); err != nil {
162
t.Errorf("FollowAllRedirect should allow same host redirect, got: %v", err)
163
}
164
165
// Different host
166
oldReq2, _ := http.NewRequest("GET", "http://example.com/a", nil)
167
newReq2, _ := http.NewRequest("GET", "http://other.com/b", nil)
168
if err := checkFn(newReq2, []*http.Request{oldReq2}); err != nil {
169
t.Errorf("FollowAllRedirect should allow cross-host redirect, got: %v", err)
170
}
171
}
172
173
func TestMaxRedirects(t *testing.T) {
174
// Exceeding explicit max
175
checkFn := makeCheckRedirectFunc(FollowAllRedirect, 2)
176
req, _ := http.NewRequest("GET", "http://example.com/c", nil)
177
via := make([]*http.Request, 3)
178
for i := range via {
179
via[i], _ = http.NewRequest("GET", "http://example.com/"+string(rune('a'+i)), nil)
180
}
181
if err := checkFn(req, via); err == nil {
182
t.Errorf("should block after exceeding maxRedirects=2 with 3 via requests")
183
}
184
185
// Within explicit max
186
checkFn2 := makeCheckRedirectFunc(FollowAllRedirect, 5)
187
via2 := make([]*http.Request, 3)
188
for i := range via2 {
189
via2[i], _ = http.NewRequest("GET", "http://example.com/"+string(rune('a'+i)), nil)
190
}
191
if err := checkFn2(req, via2); err != nil {
192
t.Errorf("should allow within maxRedirects=5 with 3 via requests, got: %v", err)
193
}
194
195
// maxRedirects=0 uses default (10)
196
checkFn3 := makeCheckRedirectFunc(FollowAllRedirect, 0)
197
via3 := make([]*http.Request, 11)
198
for i := range via3 {
199
via3[i], _ = http.NewRequest("GET", "http://example.com/x", nil)
200
}
201
if err := checkFn3(req, via3); err == nil {
202
t.Errorf("should block after exceeding default maxRedirects (10) with 11 via requests")
203
}
204
205
via4 := make([]*http.Request, 9)
206
for i := range via4 {
207
via4[i], _ = http.NewRequest("GET", "http://example.com/x", nil)
208
}
209
if err := checkFn3(req, via4); err != nil {
210
t.Errorf("should allow within default maxRedirects (10) with 9 via requests, got: %v", err)
211
}
212
}
213
214