diff --git a/ipn/ipnlocal/serve.go b/ipn/ipnlocal/serve.go index 55f1b02ec..6d9eaf23e 100644 --- a/ipn/ipnlocal/serve.go +++ b/ipn/ipnlocal/serve.go @@ -12,6 +12,7 @@ "errors" "fmt" "io" + "mime" "net" "net/http" "net/http/httputil" @@ -25,6 +26,7 @@ "sync" "sync/atomic" "time" + "unicode/utf8" "golang.org/x/net/http2" "tailscale.com/ipn" @@ -717,12 +719,27 @@ func (b *LocalBackend) addTailscaleIdentityHeaders(r *httputil.ProxyRequest) { // Only currently set for nodes with user identities. return } - r.Out.Header.Set("Tailscale-User-Login", user.LoginName) - r.Out.Header.Set("Tailscale-User-Name", user.DisplayName) + r.Out.Header.Set("Tailscale-User-Login", encTailscaleHeaderValue(user.LoginName)) + r.Out.Header.Set("Tailscale-User-Name", encTailscaleHeaderValue(user.DisplayName)) r.Out.Header.Set("Tailscale-User-Profile-Pic", user.ProfilePicURL) r.Out.Header.Set("Tailscale-Headers-Info", "https://tailscale.com/s/serve-headers") } +// encTailscaleHeaderValue cleans or encodes as necessary v, to be suitable in +// an HTTP header value. See +// https://github.com/tailscale/tailscale/issues/11603. +// +// If v is not a valid UTF-8 string, it returns an empty string. +// If v is a valid ASCII string, it returns v unmodified. +// If v is a valid UTF-8 string with non-ASCII characters, it returns a +// RFC 2047 Q-encoded string. +func encTailscaleHeaderValue(v string) string { + if !utf8.ValidString(v) { + return "" + } + return mime.QEncoding.Encode("utf-8", v) +} + // serveWebHandler is an http.HandlerFunc that maps incoming requests to the // correct *http. func (b *LocalBackend) serveWebHandler(w http.ResponseWriter, r *http.Request) { diff --git a/ipn/ipnlocal/serve_test.go b/ipn/ipnlocal/serve_test.go index 45bd4bc2c..c294502f1 100644 --- a/ipn/ipnlocal/serve_test.go +++ b/ipn/ipnlocal/serve_test.go @@ -823,3 +823,21 @@ func Test_isGRPCContentType(t *testing.T) { } } } + +func TestEncTailscaleHeaderValue(t *testing.T) { + tests := []struct { + in string + want string + }{ + {"", ""}, + {"Alice Smith", "Alice Smith"}, + {"Bad\xffUTF-8", ""}, + {"Krūmiņa", "=?utf-8?q?Kr=C5=ABmi=C5=86a?="}, + } + for _, tt := range tests { + got := encTailscaleHeaderValue(tt.in) + if got != tt.want { + t.Errorf("encTailscaleHeaderValue(%q) = %q, want %q", tt.in, got, tt.want) + } + } +}