diff --git a/cmd/tailscale/cli/cli.go b/cmd/tailscale/cli/cli.go index 6845cbd93..cbe937588 100644 --- a/cmd/tailscale/cli/cli.go +++ b/cmd/tailscale/cli/cli.go @@ -53,6 +53,12 @@ func outln(a ...any) { fmt.Fprintln(Stdout, a...) } +// ptrTo returns a pointer to the provided value. +// It's a convenience function to avoid having to write &value, +// or where the language doesn't allow it. +// Used primarily in tests. +func ptrTo[T any](v T) *T { return &v } + // ActLikeCLI reports whether a GUI application should act like the // CLI based on os.Args, GOOS, the context the process is running in // (pty, parent PID), etc. diff --git a/cmd/tailscale/cli/serve.go b/cmd/tailscale/cli/serve.go index 402b64da5..cc455c90c 100644 --- a/cmd/tailscale/cli/serve.go +++ b/cmd/tailscale/cli/serve.go @@ -285,7 +285,7 @@ func (e *serveEnv) runServe(ctx context.Context, args []string) error { } h.Proxy = t case "text": - h.Text = args[2] + h.Text = ptrTo(args[2]) default: fmt.Fprintf(os.Stderr, "error: unknown serve type %q\n\n", args[1]) return flag.ErrHelp @@ -547,8 +547,8 @@ func printWebStatusTree(sc *ipn.ServeConfig, hp ipn.HostPort) { return "path", h.Path case h.Proxy != "": return "proxy", h.Proxy - case h.Text != "": - return "text", "\"" + elipticallyTruncate(h.Text, 20) + "\"" + case h.Text != nil: + return "text", "\"" + elipticallyTruncate(*h.Text, 20) + "\"" } return "", "" } diff --git a/cmd/tailscale/cli/serve_test.go b/cmd/tailscale/cli/serve_test.go index e3269b674..e526a54e8 100644 --- a/cmd/tailscale/cli/serve_test.go +++ b/cmd/tailscale/cli/serve_test.go @@ -151,7 +151,7 @@ func TestServeConfigMutations(t *testing.T) { "/abc": {Proxy: "http://127.0.0.1:3001"}, }}, "foo.test.ts.net:10000": {Handlers: map[string]*ipn.HTTPHandler{ - "/": {Text: "hi"}, + "/": {Text: ptrTo("hi")}, }}, }, }, @@ -314,7 +314,7 @@ func TestServeConfigMutations(t *testing.T) { TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, Web: map[ipn.HostPort]*ipn.WebServerConfig{ "foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{ - "/": {Text: "hello"}, + "/": {Text: ptrTo("hello")}, }}, }, }, @@ -547,7 +547,7 @@ func TestServeConfigMutations(t *testing.T) { TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, Web: map[ipn.HostPort]*ipn.WebServerConfig{ "foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{ - "/tcp": {Text: "foo"}, + "/tcp": {Text: ptrTo("foo")}, }}, }, }, diff --git a/cmd/tailscale/cli/set_test.go b/cmd/tailscale/cli/set_test.go index 013896a4a..e7a7c45f1 100644 --- a/cmd/tailscale/cli/set_test.go +++ b/cmd/tailscale/cli/set_test.go @@ -13,8 +13,6 @@ import ( "tailscale.com/net/tsaddr" ) -func ptrTo[T any](v T) *T { return &v } - func TestCalcAdvertiseRoutesForSet(t *testing.T) { pfx := netip.MustParsePrefix tests := []struct { diff --git a/ipn/ipn_clone.go b/ipn/ipn_clone.go index 8971b7b90..d0ae68069 100644 --- a/ipn/ipn_clone.go +++ b/ipn/ipn_clone.go @@ -118,6 +118,10 @@ func (src *HTTPHandler) Clone() *HTTPHandler { } dst := new(HTTPHandler) *dst = *src + if dst.Text != nil { + dst.Text = new(string) + *dst.Text = *src.Text + } return dst } @@ -125,7 +129,7 @@ func (src *HTTPHandler) Clone() *HTTPHandler { var _HTTPHandlerCloneNeedsRegeneration = HTTPHandler(struct { Path string Proxy string - Text string + Text *string }{}) // Clone makes a deep copy of WebServerConfig. diff --git a/ipn/ipn_view.go b/ipn/ipn_view.go index c117148da..5ec360e73 100644 --- a/ipn/ipn_view.go +++ b/ipn/ipn_view.go @@ -290,13 +290,19 @@ func (v *HTTPHandlerView) UnmarshalJSON(b []byte) error { func (v HTTPHandlerView) Path() string { return v.ж.Path } func (v HTTPHandlerView) Proxy() string { return v.ж.Proxy } -func (v HTTPHandlerView) Text() string { return v.ж.Text } +func (v HTTPHandlerView) Text() *string { + if v.ж.Text == nil { + return nil + } + x := *v.ж.Text + return &x +} // A compilation failure here means this code must be regenerated, with the command at the top of this file. var _HTTPHandlerViewNeedsRegeneration = HTTPHandler(struct { Path string Proxy string - Text string + Text *string }{}) // View returns a readonly view of WebServerConfig. diff --git a/ipn/ipnlocal/serve.go b/ipn/ipnlocal/serve.go index caac249f1..5dd251054 100644 --- a/ipn/ipnlocal/serve.go +++ b/ipn/ipnlocal/serve.go @@ -395,9 +395,9 @@ func (b *LocalBackend) serveWebHandler(w http.ResponseWriter, r *http.Request) { http.NotFound(w, r) return } - if s := h.Text(); s != "" { + if s := h.Text(); s != nil { w.Header().Set("Content-Type", "text/plain; charset=utf-8") - io.WriteString(w, s) + io.WriteString(w, *s) return } if v := h.Path(); v != "" { diff --git a/ipn/serve.go b/ipn/serve.go index 93d6b077e..2e38fcd5d 100644 --- a/ipn/serve.go +++ b/ipn/serve.go @@ -65,7 +65,7 @@ type HTTPHandler struct { Path string `json:",omitempty"` // absolute path to directory or file to serve Proxy string `json:",omitempty"` // http://localhost:3000/, localhost:3030, 3030 - Text string `json:",omitempty"` // plaintext to serve (primarily for testing) + Text *string `json:",omitempty"` // plaintext to serve (primarily for testing) // TODO(bradfitz): bool to not enumerate directories? TTL on mapping for // temporary ones? Error codes? Redirects?