Skip to content

Commit

Permalink
net/url: add URL.Redacted() to return password-free string
Browse files Browse the repository at this point in the history
Returning an URL.String() without the password is very useful for
situations where the URL is supposed to be logged and the password is
not useful to be shown.

This method re-uses URL.String() but with the password scrubbed and
substituted for a "xxxxx" in order to make it obvious that there was a
password. If the URL had no password then no "xxxxx" will be shown.

Fixes golang#34855
  • Loading branch information
nrxr committed Apr 7, 2020
1 parent 718f553 commit e56499a
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 0 deletions.
10 changes: 10 additions & 0 deletions src/net/url/url.go
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,16 @@ func (u *URL) String() string {
return buf.String()
}

// Redacted is like String but replaces any password with "xxxxx".
// Only the password in u.URL is redacted.
func (u *URL) Redacted() string {
ru := *u
if _, has := ru.User.Password(); has {
ru.User = UserPassword(ru.User.Username(), "xxxxx")
}
return ru.String()
}

// Values maps a string key to a list of values.
// It is typically used for query parameters and form values.
// Unlike in the http.Header map, the keys in a Values map
Expand Down
89 changes: 89 additions & 0 deletions src/net/url/url_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,95 @@ func TestURLString(t *testing.T) {
}
}

var maskedURLTestsPtr = []struct {
url *URL
want string
shouldMask bool
}{
{
url: &URL{
Scheme: "http",
Host: "host.tld",
Path: "this:that",
User: UserPassword("user", "password"),
},
want: "http://user:xxxxx@host.tld/this:that",
shouldMask: true,
},
{
url: &URL{
Scheme: "http",
Host: "host.tld",
Path: "this:that",
User: User("user"),
},
want: "http://user@host.tld/this:that",
shouldMask: false,
},
}

func TestURLRedacted(t *testing.T) {
cases := []struct {
name string
url *URL
want string
}{
{
name: "non-blank Password",
url: &URL{
Scheme: "http",
Host: "host.tld",
Path: "this:that",
User: UserPassword("user", "password"),
},
want: "http://user:xxxxx@host.tld/this:that",
},
{
name: "blank Password",
url: &URL{
Scheme: "http",
Host: "host.tld",
Path: "this:that",
User: User("user"),
},
want: "http://user@host.tld/this:that",
},
{
name: "nil User",
url: &URL{
Scheme: "http",
Host: "host.tld",
Path: "this:that",
User: UserPassword("", "password"),
},
want: "http://:xxxxx@host.tld/this:that",
},
{
name: "blank Username, blank Password",
url: &URL{
Scheme: "http",
Host: "host.tld",
Path: "this:that",
},
want: "http://host.tld/this:that",
},
{
name: "empty URL",
url: &URL{},
want: "",
},
}

for _, tt := range cases {
t := t
t.Run(tt.name, func(t *testing.T) {
if g, w := tt.url.Redacted(), tt.want; g != w {
t.Fatalf("got: %q\nwant: %q", g, w)
}
})
}
}

type EscapeTest struct {
in string
out string
Expand Down

0 comments on commit e56499a

Please sign in to comment.