diff --git a/client/web/qnap.go b/client/web/qnap.go index b3c0879c1..d3b1d8dd7 100644 --- a/client/web/qnap.go +++ b/client/web/qnap.go @@ -16,8 +16,6 @@ import ( "net/url" ) -const qnapPrefix = "/cgi-bin/qpkg/Tailscale/index.cgi/" - // authorizeQNAP authenticates the logged-in QNAP user and verifies // that they are authorized to use the web client. It returns true if the // request was handled and no further processing is required. diff --git a/client/web/src/api.ts b/client/web/src/api.ts index 2d2a18f35..b922eb858 100644 --- a/client/web/src/api.ts +++ b/client/web/src/api.ts @@ -23,7 +23,7 @@ export function apiFetch( const url = `api${endpoint}${search ? `?${search}` : ""}` var contentType: string - if (unraidCsrfToken) { + if (unraidCsrfToken && method === "POST") { const params = new URLSearchParams() params.append("csrf_token", unraidCsrfToken) if (body) { diff --git a/client/web/synology.go b/client/web/synology.go index 70a024ace..7c3f82c11 100644 --- a/client/web/synology.go +++ b/client/web/synology.go @@ -15,8 +15,6 @@ import ( "tailscale.com/util/groupmember" ) -const synologyPrefix = "/webman/3rdparty/Tailscale/index.cgi/" - // authorizeSynology authenticates the logged-in Synology user and verifies // that they are authorized to use the web client. It returns true if the // request was handled and no further processing is required. diff --git a/client/web/web.go b/client/web/web.go index 71c2b1616..d03642566 100644 --- a/client/web/web.go +++ b/client/web/web.go @@ -58,7 +58,7 @@ type Server struct { devProxy *httputil.ReverseProxy // only filled when devMode is on cgiMode bool - cgiPath string + pathPrefix string apiHandler http.Handler // csrf-protected api handler } @@ -69,8 +69,8 @@ type ServerOpts struct { // CGIMode indicates if the server is running as a CGI script. CGIMode bool - // If running in CGIMode, CGIPath is the URL path prefix to the CGI script. - CGIPath string + // PathPrefix is the URL prefix added to requests by CGI or reverse proxy. + PathPrefix string // LocalClient is the tailscale.LocalClient to use for this web server. // If nil, a new one will be created. @@ -84,10 +84,10 @@ func NewServer(ctx context.Context, opts ServerOpts) (s *Server, cleanup func()) opts.LocalClient = &tailscale.LocalClient{} } s = &Server{ - devMode: opts.DevMode, - lc: opts.LocalClient, - cgiMode: opts.CGIMode, - cgiPath: opts.CGIPath, + devMode: opts.DevMode, + lc: opts.LocalClient, + cgiMode: opts.CGIMode, + pathPrefix: opts.PathPrefix, } cleanup = func() {} if s.devMode { @@ -116,20 +116,9 @@ func init() { func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { handler := s.serve - // if running in cgi mode, strip the cgi path prefix - if s.cgiMode { - prefix := s.cgiPath - if prefix == "" { - switch distro.Get() { - case distro.Synology: - prefix = synologyPrefix - case distro.QNAP: - prefix = qnapPrefix - } - } - if prefix != "" { - handler = enforcePrefix(prefix, handler) - } + // if path prefix is defined, strip it from requests. + if s.pathPrefix != "" { + handler = enforcePrefix(s.pathPrefix, handler) } handler(w, r) @@ -334,7 +323,6 @@ func (s *Server) servePostNodeUpdate(w http.ResponseWriter, r *http.Request) { } else { io.WriteString(w, "{}") } - return } func (s *Server) tailscaleUp(ctx context.Context, st *ipnstate.Status, postData nodeUpdate) (authURL string, retErr error) { @@ -487,6 +475,19 @@ func (s *Server) csrfKey() []byte { // Unlike http.StripPrefix, it does not return a 404 if the prefix is not present. // Instead, it returns a redirect to the prefix path. func enforcePrefix(prefix string, h http.HandlerFunc) http.HandlerFunc { + if prefix == "" { + return h + } + + // ensure that prefix always has both a leading and trailing slash so + // that relative links for JS and CSS assets work correctly. + if !strings.HasPrefix(prefix, "/") { + prefix = "/" + prefix + } + if !strings.HasSuffix(prefix, "/") { + prefix += "/" + } + return func(w http.ResponseWriter, r *http.Request) { if !strings.HasPrefix(r.URL.Path, prefix) { http.Redirect(w, r, prefix, http.StatusFound) diff --git a/cmd/tailscale/cli/web.go b/cmd/tailscale/cli/web.go index 2758ba695..2a3c416e8 100644 --- a/cmd/tailscale/cli/web.go +++ b/cmd/tailscale/cli/web.go @@ -39,6 +39,7 @@ Tailscale, as opposed to a CLI or a native app. webf.StringVar(&webArgs.listen, "listen", "localhost:8088", "listen address; use port 0 for automatic") webf.BoolVar(&webArgs.cgi, "cgi", false, "run as CGI script") webf.BoolVar(&webArgs.dev, "dev", false, "run web client in developer mode [this flag is in development, use is unsupported]") + webf.StringVar(&webArgs.prefix, "prefix", "", "URL prefix added to requests (for cgi or reverse proxies)") return webf })(), Exec: runWeb, @@ -48,6 +49,7 @@ var webArgs struct { listen string cgi bool dev bool + prefix string } func tlsConfigFromEnvironment() *tls.Config { @@ -81,6 +83,7 @@ func runWeb(ctx context.Context, args []string) error { webServer, cleanup := web.NewServer(ctx, web.ServerOpts{ DevMode: webArgs.dev, CGIMode: webArgs.cgi, + PathPrefix: webArgs.prefix, LocalClient: &localClient, }) defer cleanup() diff --git a/release/dist/synology/files/index.cgi b/release/dist/synology/files/index.cgi index 7682824ec..2c1990cfd 100755 --- a/release/dist/synology/files/index.cgi +++ b/release/dist/synology/files/index.cgi @@ -1,2 +1,2 @@ #! /bin/sh -exec /var/packages/Tailscale/target/bin/tailscale web -cgi \ No newline at end of file +exec /var/packages/Tailscale/target/bin/tailscale web -cgi -prefix="/webman/3rdparty/Tailscale/index.cgi/"