Skip to content

Commit 28a808d

Browse files
committed
Add gosprintfhostport linter
The Go linter go-sprintf-host-port checks that sprintf is not used to construct a host:port combination in a URL. A frequent pattern is for a developer to construct a URL like this: ```go fmt.Sprintf("http://%s:%d/foo", host, port) ``` However, if "host" is an IPv6 address like 2001:4860:4860::8888, the URL constructed will be invalid. IPv6 addresses must be bracketed, like this: ``` http://[2001:4860:4860::8888]:9443 ``` The linter is naive, and really only looks for the most obvious cases, but where it's possible to infer that a URL is being constructed with Sprintf containing a :, this informs the user to use net.JoinHostPort instead. Running it against some real world code bases like OpenShift and Kubernetes has found a number of cases that would break in IPv6 environments.
1 parent 85ac635 commit 28a808d

File tree

5 files changed

+68
-0
lines changed

5 files changed

+68
-0
lines changed

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ require (
153153
github.com/spf13/afero v1.6.0 // indirect
154154
github.com/spf13/cast v1.4.1 // indirect
155155
github.com/spf13/jwalterweatherman v1.1.0 // indirect
156+
github.com/stbenjam/go-sprintf-host-port v0.0.0-20220406232701-e6c52ffc7e9c // indirect
156157
github.com/stretchr/objx v0.1.1 // indirect
157158
github.com/subosito/gotenv v1.2.0 // indirect
158159
github.com/tklauser/go-sysconf v0.3.10 // indirect

go.sum

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/golinters/gosprintfhostport.go

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package golinters
2+
3+
import (
4+
"github.com/stbenjam/go-sprintf-host-port/pkg/analyzer"
5+
"golang.org/x/tools/go/analysis"
6+
7+
"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
8+
)
9+
10+
func NewGoSprintfHostPort() *goanalysis.Linter {
11+
a := analyzer.Analyzer
12+
13+
return goanalysis.NewLinter(
14+
a.Name,
15+
a.Doc,
16+
[]*analysis.Analyzer{a},
17+
nil,
18+
).WithLoadMode(goanalysis.LoadModeSyntax)
19+
}

pkg/lint/lintersdb/manager.go

+5
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,11 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
407407
WithPresets(linter.PresetStyle).
408408
WithURL("https://github.com/jirfag/go-printf-func-name"),
409409

410+
linter.NewConfig(golinters.NewGoSprintfHostPort()).
411+
WithSince("v1.46.0").
412+
WithPresets(linter.PresetStyle).
413+
WithURL("https://github.com/stbenjam/go-sprintf-host-port"),
414+
410415
linter.NewConfig(golinters.NewGosec(gosecCfg)).
411416
WithSince("v1.0.0").
412417
WithLoadForGoAnalysis().

test/testdata/gosprintfhostport.go

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package testdata
2+
3+
import (
4+
"fmt"
5+
"net"
6+
)
7+
8+
func _() {
9+
_ = fmt.Sprintf("gopher://%s/foo", net.JoinHostPort("foo", "80"))
10+
11+
_ = fmt.Sprintf("postgres://%s:%[email protected]/%s", "foo", "bar", "baz")
12+
13+
_ = fmt.Sprintf("http://%s/foo", net.JoinHostPort("foo", "80"))
14+
15+
_ = fmt.Sprintf("http://api.%s:6443/foo", "example.com")
16+
17+
_ = fmt.Sprintf("http://api.%s/foo", "example.com")
18+
19+
_ = fmt.Sprintf("telnet+ssl://%s/foo", net.JoinHostPort("foo", "80"))
20+
21+
_ = fmt.Sprintf("http://%s/foo:bar", net.JoinHostPort("foo", "80"))
22+
23+
_ = fmt.Sprintf("http://user:password@%s/foo:bar", net.JoinHostPort("foo", "80"))
24+
25+
_ = fmt.Sprintf("http://example.com:9211")
26+
27+
_ = fmt.Sprintf("gopher://%s:%d", "myHost", 70) // ERROR "host:port in url should be constructed with net.JoinHostPort and not directly with fmt.Sprintf"
28+
29+
_ = fmt.Sprintf("telnet+ssl://%s:%d", "myHost", 23) // ERROR "host:port in url should be constructed with net.JoinHostPort and not directly with fmt.Sprintf"
30+
31+
_ = fmt.Sprintf("https://user@%s:%d", "myHost", 8443) // ERROR "host:port in url should be constructed with net.JoinHostPort and not directly with fmt.Sprintf"
32+
33+
_ = fmt.Sprintf("postgres://%s:%s@%s:5050/%s", "foo", "bar", "baz", "qux") // ERROR "host:port in url should be constructed with net.JoinHostPort and not directly with fmt.Sprintf"
34+
35+
_ = fmt.Sprintf("https://%s:%d", "myHost", 8443) // ERROR "host:port in url should be constructed with net.JoinHostPort and not directly with fmt.Sprintf"
36+
37+
_ = fmt.Sprintf("https://%s:9211", "myHost") // ERROR "host:port in url should be constructed with net.JoinHostPort and not directly with fmt.Sprintf"
38+
39+
ip := "fd00::1"
40+
_ = fmt.Sprintf("http://%s:1936/healthz", ip) // ERROR "host:port in url should be constructed with net.JoinHostPort and not directly with fmt.Sprintf"
41+
}

0 commit comments

Comments
 (0)