Skip to content

Commit 3379b53

Browse files
committed
grpcutil: add ValidateTargetURI function
Fixes #8747 Add an internal API to validate gRPC target URI strings. The function checks that the target parses as a valid RFC 3986 URI and that a resolver is registered for its scheme. RELEASE NOTES: n/a
1 parent 2abe1f0 commit 3379b53

File tree

2 files changed

+131
-0
lines changed

2 files changed

+131
-0
lines changed

internal/grpcutil/target.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
*
3+
* Copyright 2025 gRPC authors.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
19+
package grpcutil
20+
21+
import (
22+
"fmt"
23+
"net/url"
24+
25+
"google.golang.org/grpc/resolver"
26+
)
27+
28+
// ValidateTargetURI validates that target is a valid RFC 3986 URI
29+
// and that a resolver is registered for its scheme.
30+
func ValidateTargetURI(target string) error {
31+
u, err := url.Parse(target)
32+
if err != nil {
33+
return fmt.Errorf("invalid target URI %q: %w", target, err)
34+
}
35+
36+
if u.Scheme == "" {
37+
return fmt.Errorf("target URI %q has no scheme", target)
38+
}
39+
40+
if resolver.Get(u.Scheme) == nil {
41+
return fmt.Errorf("no resolver registered for scheme %q", u.Scheme)
42+
}
43+
44+
return nil
45+
}

internal/grpcutil/target_test.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
*
3+
* Copyright 2025 gRPC authors.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
19+
package grpcutil
20+
21+
import (
22+
"strings"
23+
"testing"
24+
25+
_ "google.golang.org/grpc/resolver/dns" // Register dns resolver
26+
_ "google.golang.org/grpc/resolver/passthrough" // Register passthrough resolver
27+
)
28+
29+
func TestValidateTargetURI(t *testing.T) {
30+
tests := []struct {
31+
name string
32+
target string
33+
wantErr bool
34+
errContain string
35+
}{
36+
{
37+
name: "valid dns scheme",
38+
target: "dns:///example.com:443",
39+
wantErr: false,
40+
},
41+
{
42+
name: "valid passthrough scheme",
43+
target: "passthrough:///localhost:8080",
44+
wantErr: false,
45+
},
46+
{
47+
name: "missing scheme",
48+
target: "/path/to/socket",
49+
wantErr: true,
50+
errContain: "has no scheme",
51+
},
52+
{
53+
name: "host:port parsed as scheme",
54+
target: "example.com:443",
55+
wantErr: true,
56+
errContain: "no resolver registered for scheme",
57+
},
58+
{
59+
name: "unregistered scheme",
60+
target: "unknown:///example.com:443",
61+
wantErr: true,
62+
errContain: "no resolver registered for scheme",
63+
},
64+
{
65+
name: "invalid URI with control character",
66+
target: "dns:///example\x00.com",
67+
wantErr: true,
68+
errContain: "invalid target URI",
69+
},
70+
}
71+
72+
for _, tt := range tests {
73+
t.Run(tt.name, func(t *testing.T) {
74+
err := ValidateTargetURI(tt.target)
75+
if (err != nil) != tt.wantErr {
76+
t.Errorf("ValidateTargetURI(%q) error = %v, wantErr %v", tt.target, err, tt.wantErr)
77+
return
78+
}
79+
if tt.wantErr && tt.errContain != "" {
80+
if !strings.Contains(err.Error(), tt.errContain) {
81+
t.Errorf("ValidateTargetURI(%q) error = %v, want error containing %q", tt.target, err, tt.errContain)
82+
}
83+
}
84+
})
85+
}
86+
}

0 commit comments

Comments
 (0)