Skip to content

Commit

Permalink
Add gosprintfhostport linter
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
stbenjam committed Apr 6, 2022
1 parent 85ac635 commit 28a808d
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 0 deletions.
1 change: 1 addition & 0 deletions go.mod
Expand Up @@ -153,6 +153,7 @@ require (
github.com/spf13/afero v1.6.0 // indirect
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/stbenjam/go-sprintf-host-port v0.0.0-20220406232701-e6c52ffc7e9c // indirect
github.com/stretchr/objx v0.1.1 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
github.com/tklauser/go-sysconf v0.3.10 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions pkg/golinters/gosprintfhostport.go
@@ -0,0 +1,19 @@
package golinters

import (
"github.com/stbenjam/go-sprintf-host-port/pkg/analyzer"
"golang.org/x/tools/go/analysis"

"github.com/golangci/golangci-lint/pkg/golinters/goanalysis"
)

func NewGoSprintfHostPort() *goanalysis.Linter {
a := analyzer.Analyzer

return goanalysis.NewLinter(
a.Name,
a.Doc,
[]*analysis.Analyzer{a},
nil,
).WithLoadMode(goanalysis.LoadModeSyntax)
}
5 changes: 5 additions & 0 deletions pkg/lint/lintersdb/manager.go
Expand Up @@ -407,6 +407,11 @@ func (m Manager) GetAllSupportedLinterConfigs() []*linter.Config {
WithPresets(linter.PresetStyle).
WithURL("https://github.com/jirfag/go-printf-func-name"),

linter.NewConfig(golinters.NewGoSprintfHostPort()).
WithSince("v1.46.0").
WithPresets(linter.PresetStyle).
WithURL("https://github.com/stbenjam/go-sprintf-host-port"),

linter.NewConfig(golinters.NewGosec(gosecCfg)).
WithSince("v1.0.0").
WithLoadForGoAnalysis().
Expand Down
41 changes: 41 additions & 0 deletions test/testdata/gosprintfhostport.go
@@ -0,0 +1,41 @@
package testdata

import (
"fmt"
"net"
)

func _() {
_ = fmt.Sprintf("gopher://%s/foo", net.JoinHostPort("foo", "80"))

_ = fmt.Sprintf("postgres://%s:%s@127.0.0.1/%s", "foo", "bar", "baz")

_ = fmt.Sprintf("http://%s/foo", net.JoinHostPort("foo", "80"))

_ = fmt.Sprintf("http://api.%s:6443/foo", "example.com")

_ = fmt.Sprintf("http://api.%s/foo", "example.com")

_ = fmt.Sprintf("telnet+ssl://%s/foo", net.JoinHostPort("foo", "80"))

_ = fmt.Sprintf("http://%s/foo:bar", net.JoinHostPort("foo", "80"))

_ = fmt.Sprintf("http://user:password@%s/foo:bar", net.JoinHostPort("foo", "80"))

_ = fmt.Sprintf("http://example.com:9211")

_ = fmt.Sprintf("gopher://%s:%d", "myHost", 70) // ERROR "host:port in url should be constructed with net.JoinHostPort and not directly with fmt.Sprintf"

_ = 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"

_ = 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"

_ = 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"

_ = fmt.Sprintf("https://%s:%d", "myHost", 8443) // ERROR "host:port in url should be constructed with net.JoinHostPort and not directly with fmt.Sprintf"

_ = fmt.Sprintf("https://%s:9211", "myHost") // ERROR "host:port in url should be constructed with net.JoinHostPort and not directly with fmt.Sprintf"

ip := "fd00::1"
_ = fmt.Sprintf("http://%s:1936/healthz", ip) // ERROR "host:port in url should be constructed with net.JoinHostPort and not directly with fmt.Sprintf"
}

0 comments on commit 28a808d

Please sign in to comment.