From 4a3b9c24fb6725345e2ac9a1bf74bad1cbf38519 Mon Sep 17 00:00:00 2001 From: nrxr Date: Wed, 13 Nov 2019 21:26:30 -0400 Subject: [PATCH] net/url: add URL.Redacted() to return password-free string 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 #34855 --- src/net/url/url.go | 10 ++++++ src/net/url/url_test.go | 68 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/src/net/url/url.go b/src/net/url/url.go index 6480d4b432242b..74b1ccec1e8caa 100644 --- a/src/net/url/url.go +++ b/src/net/url/url.go @@ -821,6 +821,16 @@ func (u *URL) String() string { return buf.String() } +// Redacted returns a password-redacted version of the String() method, only if +// the password exists. The password is substituted by "xxxxx". +func (u *URL) Redacted() string { + rdkt := *u + if _, has := rdkt.User.Password(); has { + rdkt.User = UserPassword(rdkt.User.Username(), "xxxxx") + } + return rdkt.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 diff --git a/src/net/url/url_test.go b/src/net/url/url_test.go index 79fd3d5c79f37f..bb4660128a46b2 100644 --- a/src/net/url/url_test.go +++ b/src/net/url/url_test.go @@ -765,6 +765,74 @@ 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, + }, +} + +var redactedURLTests = []struct { + url *URL + want string +}{ + { + url: &URL{ + Scheme: "http", + Host: "host.tld", + Path: "this:that", + User: UserPassword("user", "password"), + }, + want: "http://user:xxxxx@host.tld/this:that", + }, + { + url: &URL{ + Scheme: "http", + Host: "host.tld", + Path: "this:that", + User: User("user"), + }, + want: "http://user@host.tld/this:that", + }, +} + +func TestURLRedacted(t *testing.T) { + for _, tt := range redactedURLTests { + if got := tt.url.Redacted(); got != tt.want { + t.Errorf("%+v.Redacted() = %q; want %q", tt.url, got, tt.want) + } + } + for _, tt := range maskedURLTestsPtr { + if got := tt.url.Redacted(); got != tt.want { + t.Errorf("%+v.Redacted() = %q; want %q", tt.url, got, tt.want) + } + + if tt.shouldMask && tt.url.Redacted() == tt.url.String() { + t.Errorf("%+v.Redacted() password should differ to %+v.String()", tt.url, tt.url) + } + } +} + type EscapeTest struct { in string out string