Fix username:password@ validation in urls (#2080)

This commit is contained in:
Erik Dubbelboer
2025-10-05 08:10:23 +08:00
committed by GitHub
parent ede09fad73
commit 75dcdb8bba
2 changed files with 62 additions and 1 deletions
+24 -1
View File
@@ -298,8 +298,11 @@ func (u *URI) parse(host, uri []byte, isTLS bool) error {
u.SetSchemeBytes(strHTTPS)
}
if n := bytes.IndexByte(host, '@'); n >= 0 {
if n := bytes.LastIndexByte(host, '@'); n >= 0 {
auth := host[:n]
if !validUserinfo(auth) {
return ErrorInvalidURI
}
host = host[n+1:]
if n := bytes.IndexByte(auth, ':'); n >= 0 {
@@ -356,6 +359,26 @@ func (u *URI) parse(host, uri []byte, isTLS bool) error {
return nil
}
func validUserinfo(userinfo []byte) bool {
for _, c := range userinfo {
switch {
case 'A' <= c && c <= 'Z':
continue
case 'a' <= c && c <= 'z':
continue
case '0' <= c && c <= '9':
continue
}
switch c {
case '-', '.', '_', ':', '~', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', '%', '@':
continue
default:
return false
}
}
return true
}
// parseHost parses host as an authority without user
// information. That is, as host[:port].
//
+38
View File
@@ -151,6 +151,44 @@ func testURIUpdate(t *testing.T, base, update, result string) {
}
}
func TestURIRejectInvalidUserinfo(t *testing.T) {
t.Parallel()
bad := []string{
"http://[normal.com@]vulndetector.com/",
"http://normal.com[user@vulndetector].com/",
"http://normal.com[@]vulndetector.com/",
}
for _, raw := range bad {
var u URI
if err := u.Parse(nil, []byte(raw)); err == nil {
t.Fatalf("expected error parsing %q", raw)
}
}
}
func TestURIAllowAtInUserinfo(t *testing.T) {
t.Parallel()
var u URI
if err := u.Parse(nil, []byte("http://user:p@ss@example.com/")); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got := string(u.Host()); got != "example.com" {
t.Fatalf("unexpected host %q", got)
}
if got := string(u.Username()); got != "user" {
t.Fatalf("unexpected username %q", got)
}
if got := string(u.Password()); got != "p@ss" {
t.Fatalf("unexpected password %q", got)
}
}
func TestURIPathNormalize(t *testing.T) {
if runtime.GOOS == "windows" {
t.SkipNow()