Skip to content

Commit 031f4a6

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 031f4a6

File tree

5 files changed

+69
-0
lines changed

5 files changed

+69
-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

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

0 commit comments

Comments
 (0)