diff --git a/uri.go b/uri.go index b55fda0a96..1f1fd57f07 100644 --- a/uri.go +++ b/uri.go @@ -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]. // diff --git a/uri_test.go b/uri_test.go index 8ee40f9ff8..814ec96242 100644 --- a/uri_test.go +++ b/uri_test.go @@ -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()