Skip to content

Commit fc0aa46

Browse files
authored
client: encode the authority by default (#6428)
1 parent 11feb0a commit fc0aa46

File tree

3 files changed

+98
-2
lines changed

3 files changed

+98
-2
lines changed

clientconn.go

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1818,6 +1818,61 @@ func parseTarget(target string) (resolver.Target, error) {
18181818
return resolver.Target{URL: *u}, nil
18191819
}
18201820

1821+
func encodeAuthority(authority string) string {
1822+
const upperhex = "0123456789ABCDEF"
1823+
1824+
// Return for characters that must be escaped as per
1825+
// Valid chars are mentioned here:
1826+
// https://datatracker.ietf.org/doc/html/rfc3986#section-3.2
1827+
shouldEscape := func(c byte) bool {
1828+
// Alphanum are always allowed.
1829+
if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || '0' <= c && c <= '9' {
1830+
return false
1831+
}
1832+
switch c {
1833+
case '-', '_', '.', '~': // Unreserved characters
1834+
return false
1835+
case '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=': // Subdelim characters
1836+
return false
1837+
case ':', '[', ']', '@': // Authority related delimeters
1838+
return false
1839+
}
1840+
// Everything else must be escaped.
1841+
return true
1842+
}
1843+
1844+
hexCount := 0
1845+
for i := 0; i < len(authority); i++ {
1846+
c := authority[i]
1847+
if shouldEscape(c) {
1848+
hexCount++
1849+
}
1850+
}
1851+
1852+
if hexCount == 0 {
1853+
return authority
1854+
}
1855+
1856+
required := len(authority) + 2*hexCount
1857+
t := make([]byte, required)
1858+
1859+
j := 0
1860+
// This logic is a barebones version of escape in the go net/url library.
1861+
for i := 0; i < len(authority); i++ {
1862+
switch c := authority[i]; {
1863+
case shouldEscape(c):
1864+
t[j] = '%'
1865+
t[j+1] = upperhex[c>>4]
1866+
t[j+2] = upperhex[c&15]
1867+
j += 3
1868+
default:
1869+
t[j] = authority[i]
1870+
j++
1871+
}
1872+
}
1873+
return string(t)
1874+
}
1875+
18211876
// Determine channel authority. The order of precedence is as follows:
18221877
// - user specified authority override using `WithAuthority` dial option
18231878
// - creds' notion of server name for the authentication handshake
@@ -1868,7 +1923,11 @@ func (cc *ClientConn) determineAuthority() error {
18681923
// the channel authority given the user's dial target. For resolvers
18691924
// which don't implement this interface, we will use the endpoint from
18701925
// "scheme://authority/endpoint" as the default authority.
1871-
cc.authority = endpoint
1926+
// Escape the endpoint to handle use cases where the endpoint
1927+
// might not be a valid authority by default.
1928+
// For example an endpoint which has multiple paths like
1929+
// 'a/b/c', which is not a valid authority by default.
1930+
cc.authority = encodeAuthority(endpoint)
18721931
}
18731932
channelz.Infof(logger, cc.channelzID, "Channel authority set to %q", cc.authority)
18741933
return nil

clientconn_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1221,3 +1221,40 @@ func stayConnected(cc *ClientConn) {
12211221
}
12221222
}
12231223
}
1224+
1225+
func (s) TestURLAuthorityEscape(t *testing.T) {
1226+
tests := []struct {
1227+
name string
1228+
authority string
1229+
want string
1230+
}{
1231+
{
1232+
name: "ipv6_authority",
1233+
authority: "[::1]",
1234+
want: "[::1]",
1235+
},
1236+
{
1237+
name: "with_user_and_host",
1238+
authority: "userinfo@host:10001",
1239+
want: "userinfo@host:10001",
1240+
},
1241+
{
1242+
name: "with_multiple_slashes",
1243+
authority: "projects/123/network/abc/service",
1244+
want: "projects%2F123%2Fnetwork%2Fabc%2Fservice",
1245+
},
1246+
{
1247+
name: "all_possible_allowed_chars",
1248+
authority: "abc123-._~!$&'()*+,;=@:[]",
1249+
want: "abc123-._~!$&'()*+,;=@:[]",
1250+
},
1251+
}
1252+
1253+
for _, test := range tests {
1254+
t.Run(test.name, func(t *testing.T) {
1255+
if got, want := encodeAuthority(test.authority), test.want; got != want {
1256+
t.Errorf("encodeAuthority(%s) = %s, want %s", test.authority, got, test.want)
1257+
}
1258+
})
1259+
}
1260+
}

test/authority_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ var authorityTests = []authorityTest{
126126
name: "UnixPassthrough",
127127
address: "/tmp/sock.sock",
128128
target: "passthrough:///unix:///tmp/sock.sock",
129-
authority: "unix:///tmp/sock.sock",
129+
authority: "unix:%2F%2F%2Ftmp%2Fsock.sock",
130130
dialTargetWant: "unix:///tmp/sock.sock",
131131
},
132132
{

0 commit comments

Comments
 (0)