diff --git a/internal/home/authhttp.go b/internal/home/authhttp.go index ac3a923fd..962615b9b 100644 --- a/internal/home/authhttp.go +++ b/internal/home/authhttp.go @@ -264,11 +264,11 @@ func (web *webAPI) handleLogout(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusFound) } -// RegisterAuthHandlers - register handlers -func RegisterAuthHandlers(web *webAPI) { - globalContext.mux.Handle( +// registerAuthHandlers registers authentication handlers. +func (web *webAPI) registerAuthHandlers() { + web.conf.mux.Handle( "/control/login", - postInstallHandler(ensureHandler(http.MethodPost, web.handleLogin)), + web.postInstallHandler(ensure(http.MethodPost, web.handleLogin)), ) httpRegister(http.MethodGet, "/control/logout", web.handleLogout) } diff --git a/internal/home/authhttp_internal_test.go b/internal/home/authhttp_internal_test.go index ceb13c259..8e512aa9e 100644 --- a/internal/home/authhttp_internal_test.go +++ b/internal/home/authhttp_internal_test.go @@ -321,8 +321,6 @@ func authRequest(path string, c *http.Cookie, user, pass string) (r *http.Reques func TestAuth_ServeHTTP_firstRun(t *testing.T) { storeGlobals(t) - globalContext.firstRun = true - mux := http.NewServeMux() globalContext.mux = mux @@ -335,8 +333,10 @@ func TestAuth_ServeHTTP_firstRun(t *testing.T) { testLogger, nil, nil, + mux, agh.EmptyConfigModifier{}, false, + true, ) require.NoError(t, err) @@ -484,7 +484,8 @@ func TestAuth_ServeHTTP_auth(t *testing.T) { t.Cleanup(func() { auth.close(testutil.ContextWithTimeout(t, testTimeout)) }) - globalContext.mux = http.NewServeMux() + baseMux := http.NewServeMux() + globalContext.mux = baseMux tlsMgr, err := newTLSManager(testutil.ContextWithTimeout(t, testTimeout), &tlsManagerConfig{ logger: testLogger, @@ -501,17 +502,19 @@ func TestAuth_ServeHTTP_auth(t *testing.T) { testLogger, tlsMgr, auth, + baseMux, agh.EmptyConfigModifier{}, false, + false, ) require.NoError(t, err) globalContext.web = web - mux := auth.middleware().Wrap(globalContext.mux) + mux := auth.middleware().Wrap(baseMux) auth.isGLiNet = true - gliNetMw := auth.middleware().Wrap(globalContext.mux) + gliNetMw := auth.middleware().Wrap(baseMux) loginCookie := generateAuthCookie(t, mux, userName, userPassword) @@ -642,24 +645,28 @@ func TestAuth_ServeHTTP_logout(t *testing.T) { t.Cleanup(func() { auth.close(testutil.ContextWithTimeout(t, testTimeout)) }) - globalContext.mux = http.NewServeMux() + baseMux := http.NewServeMux() + globalContext.mux = baseMux ctx := testutil.ContextWithTimeout(t, testTimeout) - web, err := initWeb(ctx, + web, err := initWeb( + ctx, options{}, nil, nil, testLogger, nil, auth, + baseMux, agh.EmptyConfigModifier{}, false, + false, ) require.NoError(t, err) globalContext.web = web - mux := auth.middleware().Wrap(globalContext.mux) + mux := auth.middleware().Wrap(baseMux) loginCookie := generateAuthCookie(t, mux, userName, userPassword) diff --git a/internal/home/control.go b/internal/home/control.go index 75c82e5d0..b4daabf49 100644 --- a/internal/home/control.go +++ b/internal/home/control.go @@ -171,9 +171,13 @@ func (web *webAPI) handleStatus(w http.ResponseWriter, r *http.Request) { } // registerControlHandlers sets up HTTP handlers for various control endpoints. -// web must not be nil. -func registerControlHandlers(web *webAPI) { - globalContext.mux.HandleFunc("/control/version.json", postInstall(web.handleVersionJSON)) +func (web *webAPI) registerControlHandlers() { + mux := web.conf.mux + + mux.Handle( + "/control/version.json", + web.postInstallHandler(http.HandlerFunc(web.handleVersionJSON)), + ) httpRegister(http.MethodPost, "/control/update", web.handleUpdate) httpRegister(http.MethodGet, "/control/status", web.handleStatus) @@ -182,23 +186,32 @@ func registerControlHandlers(web *webAPI) { httpRegister(http.MethodGet, "/control/profile", web.handleGetProfile) httpRegister(http.MethodPut, "/control/profile/update", web.handlePutProfile) - // No auth is necessary for DoH/DoT configurations - globalContext.mux.HandleFunc("/apple/doh.mobileconfig", postInstall(handleMobileConfigDoH)) - globalContext.mux.HandleFunc("/apple/dot.mobileconfig", postInstall(handleMobileConfigDoT)) - RegisterAuthHandlers(web) + // No authentication is required for DoH/DoT configuration endpoints. + mux.Handle( + "/apple/doh.mobileconfig", + web.postInstallHandler(http.HandlerFunc(handleMobileConfigDoH)), + ) + mux.Handle( + "/apple/dot.mobileconfig", + web.postInstallHandler(http.HandlerFunc(handleMobileConfigDoT)), + ) + + web.registerAuthHandlers() } // httpRegister registers an HTTP handler. +// +// TODO(s.chzhen): Do not use [globalContext.mux]. func httpRegister(method, url string, handler http.HandlerFunc) { if method == "" { // "/dns-query" handler doesn't need auth, gzip and isn't restricted by 1 HTTP method - globalContext.mux.HandleFunc(url, postInstall(handler)) + globalContext.mux.Handle(url, postInstallHandler(handler)) return } globalContext.mux.Handle( url, - postInstallHandler(gziphandler.GzipHandler(ensureHandler(method, handler))), + postInstallHandler(gziphandler.GzipHandler(ensure(method, handler))), ) } @@ -207,7 +220,7 @@ func httpRegister(method, url string, handler http.HandlerFunc) { func ensure( method string, handler func(http.ResponseWriter, *http.Request), -) (wrapped func(http.ResponseWriter, *http.Request)) { +) (wrapped http.HandlerFunc) { return func(w http.ResponseWriter, r *http.Request) { m := r.Method if m != method { @@ -265,53 +278,20 @@ func ensureContentType(w http.ResponseWriter, r *http.Request) (ok bool) { return false } -func ensurePOST(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { - return ensure(http.MethodPost, handler) -} - -func ensureGET(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { - return ensure(http.MethodGet, handler) -} - -// Bridge between http.Handler object and Go function -type httpHandler struct { - handler func(http.ResponseWriter, *http.Request) -} - -func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - h.handler(w, r) -} - -func ensureHandler(method string, handler func(http.ResponseWriter, *http.Request)) http.Handler { - h := httpHandler{} - h.handler = ensure(method, handler) - return &h -} - -// preInstall lets the handler run only if firstRun is true, no redirects -func preInstall(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { - return func(w http.ResponseWriter, r *http.Request) { - if !globalContext.firstRun { - // if it's not first run, don't let users access it (for example /install.html when configuration is done) +// preInstallHandler lets the handler run only if firstRun is true; it does not +// perform redirects. +func (web *webAPI) preInstallHandler(handler http.Handler) (wrapped http.Handler) { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !web.conf.firstRun { + // If it's not first run, do not allow access to install-only routes + // (for example, /install.html once configuration is complete). http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden) + return } - handler(w, r) - } -} -// preInstallStruct wraps preInstall into a struct that can be returned as an interface where necessary -type preInstallHandlerStruct struct { - handler http.Handler -} - -func (p *preInstallHandlerStruct) ServeHTTP(w http.ResponseWriter, r *http.Request) { - preInstall(p.handler.ServeHTTP)(w, r) -} - -// preInstallHandler returns http.Handler interface for preInstall wrapper -func preInstallHandler(handler http.Handler) http.Handler { - return &preInstallHandlerStruct{handler} + handler.ServeHTTP(w, r) + }) } // handleHTTPSRedirect redirects the request to HTTPS, if needed, and adds some @@ -401,34 +381,52 @@ func httpsURL(u *url.URL, host string, portHTTPS uint16) (redirectURL *url.URL) } } -// postInstall lets the handler to run only if firstRun is false. Otherwise, it -// redirects to /install.html. It also enforces HTTPS if it is enabled and -// configured and sets appropriate access control headers. -func postInstall(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { - return func(w http.ResponseWriter, r *http.Request) { +// postInstallHandler lets the handler to run only if firstRun is false. +// Otherwise, it redirects to /install.html. It also enforces HTTPS if it is +// enabled and configured and sets appropriate access control headers. +// +// TODO(s.chzhen): Replace with [web.postInstall] after fixing its usage in +// [httpRegister], which is called by [dhcpd.Create] before [web] is +// initialized. +func postInstallHandler(handler http.Handler) (wrapped http.Handler) { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if globalContext.web == nil { + aghhttp.Error(r, w, http.StatusTooEarly, "it is not initialized yet") + + return + } + path := r.URL.Path - if globalContext.firstRun && !strings.HasPrefix(path, "/install.") && + if globalContext.web.conf.firstRun && + !strings.HasPrefix(path, "/install.") && !strings.HasPrefix(path, "/assets/") { http.Redirect(w, r, "install.html", http.StatusFound) return } - proceed := handleHTTPSRedirect(w, r) - if proceed { - handler(w, r) + if handleHTTPSRedirect(w, r) { + handler.ServeHTTP(w, r) } - } + }) } -type postInstallHandlerStruct struct { - handler http.Handler -} +// postInstallHandler lets the handler to run only if firstRun is false. +// Otherwise, it redirects to /install.html. It also enforces HTTPS if it is +// enabled and configured and sets appropriate access control headers. +func (web *webAPI) postInstallHandler(handler http.Handler) (wrapped http.Handler) { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + path := r.URL.Path + if web.conf.firstRun && + !strings.HasPrefix(path, "/install.") && + !strings.HasPrefix(path, "/assets/") { + http.Redirect(w, r, "install.html", http.StatusFound) -func (p *postInstallHandlerStruct) ServeHTTP(w http.ResponseWriter, r *http.Request) { - postInstall(p.handler.ServeHTTP)(w, r) -} + return + } -func postInstallHandler(handler http.Handler) http.Handler { - return &postInstallHandlerStruct{handler} + if handleHTTPSRedirect(w, r) { + handler.ServeHTTP(w, r) + } + }) } diff --git a/internal/home/controlinstall.go b/internal/home/controlinstall.go index 0243f4cd1..56d86f8fb 100644 --- a/internal/home/controlinstall.go +++ b/internal/home/controlinstall.go @@ -445,10 +445,28 @@ func (web *webAPI) handleInstallConfigure(w http.ResponseWriter, r *http.Request return } + web.finalizeInstall(ctx, w, r, req, restartHTTP) +} + +// finalizeInstall completes first-run setup by applying user-provided settings. +// w, r, and req must not be nil. +func (web *webAPI) finalizeInstall( + ctx context.Context, + w http.ResponseWriter, + r *http.Request, + req *applyConfigReq, + restartHTTP bool, +) { + var err error curConfig := &configuration{} copyInstallSettings(curConfig, config) - globalContext.firstRun = false + defer func() { + if err != nil { + copyInstallSettings(config, curConfig) + } + }() + config.DNS.BindHosts = []netip.Addr{req.DNS.IP} config.DNS.Port = req.DNS.Port config.Filtering.Logger = web.baseLogger.With(slogutil.KeyPrefix, "filtering") @@ -462,8 +480,6 @@ func (web *webAPI) handleInstallConfigure(w http.ResponseWriter, r *http.Request } err = web.auth.addUser(ctx, u, req.Password) if err != nil { - globalContext.firstRun = true - copyInstallSettings(config, curConfig) aghhttp.Error(r, w, http.StatusUnprocessableEntity, "%s", err) return @@ -475,8 +491,6 @@ func (web *webAPI) handleInstallConfigure(w http.ResponseWriter, r *http.Request // functions potentially restart the HTTPS server. err = startMods(ctx, web.baseLogger, web.tlsManager, web.confModifier) if err != nil { - globalContext.firstRun = true - copyInstallSettings(config, curConfig) aghhttp.Error(r, w, http.StatusInternalServerError, "%s", err) return @@ -484,8 +498,6 @@ func (web *webAPI) handleInstallConfigure(w http.ResponseWriter, r *http.Request err = config.write(web.tlsManager, web.auth) if err != nil { - globalContext.firstRun = true - copyInstallSettings(config, curConfig) aghhttp.Error(r, w, http.StatusInternalServerError, "Couldn't write config: %s", err) return @@ -494,7 +506,7 @@ func (web *webAPI) handleInstallConfigure(w http.ResponseWriter, r *http.Request web.conf.firstRun = false web.conf.BindAddr = netip.AddrPortFrom(req.Web.IP, req.Web.Port) - registerControlHandlers(web) + web.registerControlHandlers() aghhttp.OK(w) @@ -581,8 +593,20 @@ func startMods( return nil } +// registerInstallHandlers registers install handlers. func (web *webAPI) registerInstallHandlers() { - globalContext.mux.HandleFunc("/control/install/get_addresses", preInstall(ensureGET(web.handleInstallGetAddresses))) - globalContext.mux.HandleFunc("/control/install/check_config", preInstall(ensurePOST(web.handleInstallCheckConfig))) - globalContext.mux.HandleFunc("/control/install/configure", preInstall(ensurePOST(web.handleInstallConfigure))) + mux := web.conf.mux + + mux.Handle( + "/control/install/get_addresses", + web.preInstallHandler(ensure(http.MethodGet, web.handleInstallGetAddresses)), + ) + mux.Handle( + "/control/install/check_config", + web.preInstallHandler(ensure(http.MethodPost, web.handleInstallCheckConfig)), + ) + mux.Handle( + "/control/install/configure", + web.preInstallHandler(ensure(http.MethodPost, web.handleInstallConfigure)), + ) } diff --git a/internal/home/home.go b/internal/home/home.go index b40d09b87..aacd43d0e 100644 --- a/internal/home/home.go +++ b/internal/home/home.go @@ -58,21 +58,9 @@ type homeContext struct { dnsServer *dnsforward.Server // DNS module dhcpServer dhcpd.Interface // DHCP module - // auth stores web user information and handles authentication. - // - // TODO(s.chzhen): Remove once it is no longer called from different - // modules. See [onConfigModified]. - auth *auth - filters *filtering.DNSFilter // DNS filtering module web *webAPI // Web (HTTP, HTTPS) module - // tls contains the current configuration and state of TLS encryption. - // - // TODO(s.chzhen): Remove once it is no longer called from different - // modules. See [onConfigModified]. - tls *tlsManager - // etcHosts contains IP-hostname mappings taken from the OS-specific hosts // configuration files, for example /etc/hosts. etcHosts *aghnet.HostsContainer @@ -90,10 +78,6 @@ type homeContext struct { workDir string // Location of our directory, used to protect against CWD being somewhere else pidFileName string // PID file name. Empty if no PID file was created. controlLock sync.Mutex - - // firstRun, if true, tells AdGuard Home to only start the web interface - // service, and only serve the first-run APIs. - firstRun bool } // getDataDir returns path to the directory where we store databases and filters @@ -160,9 +144,12 @@ func Main(clientBuildFS fs.FS) { // setupContext initializes [globalContext] fields. It also reads and upgrades // config file if necessary. baseLogger must not be nil. -func setupContext(ctx context.Context, baseLogger *slog.Logger, opts options) (err error) { - globalContext.firstRun = detectFirstRun() - +func setupContext( + ctx context.Context, + baseLogger *slog.Logger, + opts options, + isFirstRun bool, +) (err error) { globalContext.mux = http.NewServeMux() if !opts.noEtcHosts { @@ -173,7 +160,7 @@ func setupContext(ctx context.Context, baseLogger *slog.Logger, opts options) (e } } - if globalContext.firstRun { + if isFirstRun { log.Info("This is the first time AdGuard Home is launched") checkNetworkPermissions() @@ -562,8 +549,8 @@ func isUpdateEnabled( } } -// initWeb initializes the web module. upd, baseLogger, tlsMgr, and auth must -// not be nil. +// initWeb initializes the web module. upd, baseLogger, tlsMgr, auth, and mux +// must not be nil. func initWeb( ctx context.Context, opts options, @@ -572,8 +559,10 @@ func initWeb( baseLogger *slog.Logger, tlsMgr *tlsManager, auth *auth, + mux *http.ServeMux, confModifier agh.ConfigModifier, isCustomUpdURL bool, + isFirstRun bool, ) (web *webAPI, err error) { logger := baseLogger.With(slogutil.KeyPrefix, "webapi") @@ -601,6 +590,7 @@ func initWeb( confModifier: confModifier, tlsManager: tlsMgr, auth: auth, + mux: mux, clientFS: clientFS, @@ -612,7 +602,7 @@ func initWeb( defaultWebPort: webPort, - firstRun: globalContext.firstRun, + firstRun: isFirstRun, disableUpdate: disableUpdate, runningAsService: opts.runningAsService, serveHTTP3: config.DNS.ServeHTTP3, @@ -696,7 +686,9 @@ func run( aghtls.Init(ctx, slogLogger.With(slogutil.KeyPrefix, "aghtls")) - err = setupContext(ctx, slogLogger, opts) + isFirstRun := detectFirstRun() + + err = setupContext(ctx, slogLogger, opts, isFirstRun) fatalOnError(err) err = configureOS(config) @@ -728,7 +720,6 @@ func run( confModifier.Apply(ctx) } - globalContext.tls = tlsMgr confModifier.setTLSManager(tlsMgr) err = setupDNSFilteringConf(ctx, slogLogger, config.Filtering, tlsMgr, confModifier) @@ -747,9 +738,9 @@ func run( // TODO(e.burkov): This could be made earlier, probably as the option's // effect. - cmdlineUpdate(ctx, updLogger, opts, upd, tlsMgr) + cmdlineUpdate(ctx, updLogger, opts, upd, tlsMgr, isFirstRun) - if !globalContext.firstRun { + if !isFirstRun { // Save the updated config. err = config.write(nil, nil) fatalOnError(err) @@ -766,7 +757,6 @@ func run( auth, err := initUsers(ctx, slogLogger, opts.glinetMode) fatalOnError(err) - globalContext.auth = auth confModifier.setAuth(auth) web, err := initWeb( @@ -777,8 +767,10 @@ func run( slogLogger, tlsMgr, auth, + globalContext.mux, confModifier, isCustomURL, + isFirstRun, ) fatalOnError(err) @@ -790,7 +782,7 @@ func run( statsDir, querylogDir, err := checkStatsAndQuerylogDirs(&globalContext, config) fatalOnError(err) - if !globalContext.firstRun { + if !isFirstRun { err = initDNS(ctx, slogLogger, tlsMgr, confModifier, statsDir, querylogDir) fatalOnError(err) @@ -1176,14 +1168,15 @@ type jsonError struct { Message string `json:"message"` } -// cmdlineUpdate updates current application and exits. l and tlsMgr must not -// be nil. +// cmdlineUpdate updates current application and exits. l, upd, and tlsMgr must +// not be nil. func cmdlineUpdate( ctx context.Context, l *slog.Logger, opts options, upd *updater.Updater, tlsMgr *tlsManager, + isFirstRun bool, ) { if !opts.performUpdate { return @@ -1212,7 +1205,7 @@ func cmdlineUpdate( os.Exit(osutil.ExitCodeSuccess) } - err = upd.Update(ctx, globalContext.firstRun) + err = upd.Update(ctx, isFirstRun) fatalOnError(err) err = restartService(ctx, l) diff --git a/internal/home/profilehttp_internal_test.go b/internal/home/profilehttp_internal_test.go index 07e6fdf29..cdbfcaaf8 100644 --- a/internal/home/profilehttp_internal_test.go +++ b/internal/home/profilehttp_internal_test.go @@ -71,7 +71,8 @@ func TestWeb_HandleGetProfile(t *testing.T) { t.Cleanup(func() { auth.close(testutil.ContextWithTimeout(t, testTimeout)) }) - globalContext.mux = http.NewServeMux() + baseMux := http.NewServeMux() + globalContext.mux = baseMux tlsMgr, err := newTLSManager(testutil.ContextWithTimeout(t, testTimeout), &tlsManagerConfig{ logger: testLogger, @@ -87,14 +88,16 @@ func TestWeb_HandleGetProfile(t *testing.T) { testLogger, tlsMgr, auth, + baseMux, agh.EmptyConfigModifier{}, false, + false, ) require.NoError(t, err) globalContext.web = web - mux := auth.middleware().Wrap(globalContext.mux) + mux := auth.middleware().Wrap(baseMux) require.True(t, t.Run("userless", func(t *testing.T) { w := httptest.NewRecorder() @@ -138,7 +141,19 @@ func TestWeb_HandlePutProfile(t *testing.T) { } ctx := testutil.ContextWithTimeout(t, testTimeout) - web, err := initWeb(ctx, options{}, nil, nil, testLogger, nil, nil, confModifier, false) + web, err := initWeb( + ctx, + options{}, + nil, + nil, + testLogger, + nil, + nil, + mux, + confModifier, + false, + false, + ) require.NoError(t, err) globalContext.web = web diff --git a/internal/home/tls_internal_test.go b/internal/home/tls_internal_test.go index d424086e6..c7ddcaee8 100644 --- a/internal/home/tls_internal_test.go +++ b/internal/home/tls_internal_test.go @@ -153,7 +153,6 @@ func storeGlobals(tb testing.TB) { prefGLFilePrefix := glFilePrefix storage := globalContext.clients.storage dnsServer := globalContext.dnsServer - firstRun := globalContext.firstRun mux := globalContext.mux web := globalContext.web @@ -162,7 +161,6 @@ func storeGlobals(tb testing.TB) { glFilePrefix = prefGLFilePrefix globalContext.clients.storage = storage globalContext.dnsServer = dnsServer - globalContext.firstRun = firstRun globalContext.mux = mux globalContext.web = web }) @@ -295,6 +293,28 @@ func assertCertSerialNumber(tb testing.TB, conf *tlsConfigSettings, wantSN int64 assert.Equal(tb, wantSN, cert.Leaf.SerialNumber.Int64()) } +// initEmptyWeb returns an initialized *webAPI with zero values and no-op mocks. +func initEmptyWeb(tb testing.TB) (web *webAPI) { + tb.Helper() + + web, err := initWeb( + testutil.ContextWithTimeout(tb, testTimeout), + options{}, + nil, + nil, + testLogger, + nil, + nil, + http.NewServeMux(), + agh.EmptyConfigModifier{}, + false, + false, + ) + require.NoError(tb, err) + + return web +} + func TestTLSManager_Reload(t *testing.T) { storeGlobals(t) @@ -343,9 +363,7 @@ func TestTLSManager_Reload(t *testing.T) { }) require.NoError(t, err) - web, err := initWeb(ctx, options{}, nil, nil, testLogger, nil, nil, agh.EmptyConfigModifier{}, false) - require.NoError(t, err) - + web := initEmptyWeb(t) m.setWebAPI(web) conf := m.config() @@ -415,9 +433,7 @@ func TestValidateTLSSettings(t *testing.T) { }) require.NoError(t, err) - web, err := initWeb(ctx, options{}, nil, nil, testLogger, nil, nil, agh.EmptyConfigModifier{}, false) - require.NoError(t, err) - + web := initEmptyWeb(t) m.setWebAPI(web) tcpLn, err := net.Listen("tcp", ":0") @@ -519,9 +535,7 @@ func TestTLSManager_HandleTLSValidate(t *testing.T) { }) require.NoError(t, err) - web, err := initWeb(ctx, options{}, nil, nil, testLogger, nil, nil, agh.EmptyConfigModifier{}, false) - require.NoError(t, err) - + web := initEmptyWeb(t) m.setWebAPI(web) setts := &tlsConfigSettingsExt{ @@ -612,9 +626,7 @@ func TestTLSManager_HandleTLSConfigure(t *testing.T) { }) require.NoError(t, err) - web, err := initWeb(ctx, options{}, nil, nil, testLogger, nil, nil, agh.EmptyConfigModifier{}, false) - require.NoError(t, err) - + web := initEmptyWeb(t) m.setWebAPI(web) conf := m.config() diff --git a/internal/home/web.go b/internal/home/web.go index 18205eb4a..cbb420d1f 100644 --- a/internal/home/web.go +++ b/internal/home/web.go @@ -63,6 +63,10 @@ type webConfig struct { // be nil. auth *auth + // mux is the default *http.ServeMux, the same as [globalContext.mux]. It + // must not be nil. + mux *http.ServeMux + clientFS fs.FS // BindAddr is the binding address with port for plain HTTP web interface. @@ -162,11 +166,9 @@ func newWebAPI(ctx context.Context, conf *webConfig) (w *webAPI) { clientFS := http.FileServer(http.FS(conf.clientFS)) + mux := conf.mux // if not configured, redirect / to /install.html, otherwise redirect /install.html to / - globalContext.mux.Handle( - "/", - withMiddlewares(clientFS, gziphandler.GzipHandler, postInstallHandler), - ) + mux.Handle("/", withMiddlewares(clientFS, gziphandler.GzipHandler, w.postInstallHandler)) // add handlers for /install paths, we only need them when we're not configured yet if conf.firstRun { @@ -175,10 +177,10 @@ func newWebAPI(ctx context.Context, conf *webConfig) (w *webAPI) { "This is the first launch of AdGuard Home, redirecting everything to /install.html", ) - globalContext.mux.Handle("/install.html", preInstallHandler(clientFS)) + mux.Handle("/install.html", w.preInstallHandler(clientFS)) w.registerInstallHandlers() } else { - registerControlHandlers(w) + w.registerControlHandlers() } w.httpsServer.cond = sync.NewCond(&w.httpsServer.condLock) @@ -241,7 +243,7 @@ func (web *webAPI) start(ctx context.Context) { // Use an h2c handler to support unencrypted HTTP/2, e.g. for proxies. hdlr := h2c.NewHandler( - withMiddlewares(globalContext.mux, limitRequestBody), + withMiddlewares(web.conf.mux, limitRequestBody), &http2.Server{}, ) @@ -336,7 +338,7 @@ func (web *webAPI) tlsServerLoop(ctx context.Context) { // TODO(a.garipov): Remove other logs like this in other code. logMw := httputil.NewLogMiddleware(logger, slog.LevelDebug) - hdlr := logMw.Wrap(withMiddlewares(globalContext.mux, limitRequestBody)) + hdlr := logMw.Wrap(withMiddlewares(web.conf.mux, limitRequestBody)) web.httpsServer.server = &http.Server{ Addr: addr, @@ -381,7 +383,7 @@ func (web *webAPI) mustStartHTTP3(ctx context.Context, address string) { CipherSuites: web.tlsManager.customCipherIDs, MinVersion: tls.VersionTLS12, }, - Handler: web.auth.middleware().Wrap(withMiddlewares(globalContext.mux, limitRequestBody)), + Handler: web.auth.middleware().Wrap(withMiddlewares(web.conf.mux, limitRequestBody)), } web.logger.DebugContext(ctx, "starting http/3 server")