mirror of
https://github.com/mjl-/mox.git
synced 2025-07-12 14:24:37 +03:00
make http(s) path for serving the account and admin pages configurable
so you can use the host (domain) name of the mail server for serving other resources too. the default is is still that account is served on /, and so takes all incoming requests before giving webhandlers a chance. mox localserve now serves the account pages on /account/
This commit is contained in:
78
http/web.go
78
http/web.go
@ -170,15 +170,15 @@ func (w *loggingWriter) Done() {
|
||||
}
|
||||
|
||||
// Set some http headers that should prevent potential abuse. Better safe than sorry.
|
||||
func safeHeaders(fn http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
func safeHeaders(fn http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
h := w.Header()
|
||||
h.Set("X-Frame-Options", "deny")
|
||||
h.Set("X-Content-Type-Options", "nosniff")
|
||||
h.Set("Content-Security-Policy", "default-src 'self' 'unsafe-inline' data:")
|
||||
h.Set("Referrer-Policy", "same-origin")
|
||||
fn(w, r)
|
||||
}
|
||||
fn.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// Built-in handlers, e.g. mta-sts and autoconfig.
|
||||
@ -186,7 +186,7 @@ type pathHandler struct {
|
||||
Name string // For logging/metrics.
|
||||
HostMatch func(dom dns.Domain) bool // If not nil, called to see if domain of requests matches. Only called if requested host is a valid domain.
|
||||
Path string // Path to register, like on http.ServeMux.
|
||||
Handle http.HandlerFunc
|
||||
Handler http.Handler
|
||||
}
|
||||
type serve struct {
|
||||
Kinds []string // Type of handler and protocol (e.g. acme-tls-alpn-01, account-http, admin-https).
|
||||
@ -195,9 +195,10 @@ type serve struct {
|
||||
Webserver bool // Whether serving WebHandler. PathHandlers are always evaluated before WebHandlers.
|
||||
}
|
||||
|
||||
// HandleFunc registers a named handler for a path and optional host. If path ends with a slash, it
|
||||
// is used as prefix match, otherwise a full path match is required. If hostOpt is set, only requests to those host are handled by this handler.
|
||||
func (s *serve) HandleFunc(name string, hostMatch func(dns.Domain) bool, path string, fn http.HandlerFunc) {
|
||||
// Handle registers a named handler for a path and optional host. If path ends with
|
||||
// a slash, it is used as prefix match, otherwise a full path match is required. If
|
||||
// hostOpt is set, only requests to those host are handled by this handler.
|
||||
func (s *serve) Handle(name string, hostMatch func(dns.Domain) bool, path string, fn http.Handler) {
|
||||
s.PathHandlers = append(s.PathHandlers, pathHandler{name, hostMatch, path, fn})
|
||||
}
|
||||
|
||||
@ -278,7 +279,7 @@ func (s *serve) ServeHTTP(xw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
if r.URL.Path == h.Path || strings.HasSuffix(h.Path, "/") && strings.HasPrefix(r.URL.Path, h.Path) {
|
||||
nw.Handler = h.Name
|
||||
h.Handle(nw, r)
|
||||
h.Handler.ServeHTTP(nw, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -325,35 +326,49 @@ func Listen() {
|
||||
if l.AccountHTTP.Enabled {
|
||||
port := config.Port(l.AccountHTTP.Port, 80)
|
||||
srv := ensureServe(false, port, "account-http")
|
||||
srv.HandleFunc("account", nil, "/", safeHeaders(accountHandle))
|
||||
path := "/"
|
||||
if l.AccountHTTP.Path != "" {
|
||||
path = l.AccountHTTP.Path
|
||||
}
|
||||
handler := safeHeaders(http.StripPrefix(path[:len(path)-1], http.HandlerFunc(accountHandle)))
|
||||
srv.Handle("account", nil, path, handler)
|
||||
}
|
||||
if l.AccountHTTPS.Enabled {
|
||||
port := config.Port(l.AccountHTTPS.Port, 443)
|
||||
srv := ensureServe(true, port, "account-https")
|
||||
srv.HandleFunc("account", nil, "/", safeHeaders(accountHandle))
|
||||
path := "/"
|
||||
if l.AccountHTTPS.Path != "" {
|
||||
path = l.AccountHTTPS.Path
|
||||
}
|
||||
handler := safeHeaders(http.StripPrefix(path[:len(path)-1], http.HandlerFunc(accountHandle)))
|
||||
srv.Handle("account", nil, path, handler)
|
||||
}
|
||||
|
||||
if l.AdminHTTP.Enabled {
|
||||
port := config.Port(l.AdminHTTP.Port, 80)
|
||||
srv := ensureServe(false, port, "admin-http")
|
||||
if !l.AccountHTTP.Enabled {
|
||||
srv.HandleFunc("admin", nil, "/", safeHeaders(adminIndex))
|
||||
path := "/admin/"
|
||||
if l.AdminHTTP.Path != "" {
|
||||
path = l.AdminHTTP.Path
|
||||
}
|
||||
srv.HandleFunc("admin", nil, "/admin/", safeHeaders(adminHandle))
|
||||
handler := safeHeaders(http.StripPrefix(path[:len(path)-1], http.HandlerFunc(adminHandle)))
|
||||
srv.Handle("admin", nil, path, handler)
|
||||
}
|
||||
if l.AdminHTTPS.Enabled {
|
||||
port := config.Port(l.AdminHTTPS.Port, 443)
|
||||
srv := ensureServe(true, port, "admin-https")
|
||||
if !l.AccountHTTPS.Enabled {
|
||||
srv.HandleFunc("admin", nil, "/", safeHeaders(adminIndex))
|
||||
path := "/admin/"
|
||||
if l.AdminHTTPS.Path != "" {
|
||||
path = l.AdminHTTPS.Path
|
||||
}
|
||||
srv.HandleFunc("admin", nil, "/admin/", safeHeaders(adminHandle))
|
||||
handler := safeHeaders(http.StripPrefix(path[:len(path)-1], http.HandlerFunc(adminHandle)))
|
||||
srv.Handle("admin", nil, path, handler)
|
||||
}
|
||||
if l.MetricsHTTP.Enabled {
|
||||
port := config.Port(l.MetricsHTTP.Port, 8010)
|
||||
srv := ensureServe(false, port, "metrics-http")
|
||||
srv.HandleFunc("metrics", nil, "/metrics", safeHeaders(promhttp.Handler().ServeHTTP))
|
||||
srv.HandleFunc("metrics", nil, "/", safeHeaders(func(w http.ResponseWriter, r *http.Request) {
|
||||
srv.Handle("metrics", nil, "/metrics", safeHeaders(promhttp.Handler()))
|
||||
srv.Handle("metrics", nil, "/", safeHeaders(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
@ -363,7 +378,7 @@ func Listen() {
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
fmt.Fprint(w, `<html><body>see <a href="/metrics">/metrics</a></body></html>`)
|
||||
}))
|
||||
})))
|
||||
}
|
||||
if l.AutoconfigHTTPS.Enabled {
|
||||
port := config.Port(l.AutoconfigHTTPS.Port, 443)
|
||||
@ -372,8 +387,8 @@ func Listen() {
|
||||
// todo: may want to check this against the configured domains, could in theory be just a webserver.
|
||||
return strings.HasPrefix(dom.ASCII, "autoconfig.")
|
||||
}
|
||||
srv.HandleFunc("autoconfig", autoconfigMatch, "/mail/config-v1.1.xml", safeHeaders(autoconfHandle))
|
||||
srv.HandleFunc("autodiscover", autoconfigMatch, "/autodiscover/autodiscover.xml", safeHeaders(autodiscoverHandle))
|
||||
srv.Handle("autoconfig", autoconfigMatch, "/mail/config-v1.1.xml", safeHeaders(http.HandlerFunc(autoconfHandle)))
|
||||
srv.Handle("autodiscover", autoconfigMatch, "/autodiscover/autodiscover.xml", safeHeaders(http.HandlerFunc(autodiscoverHandle)))
|
||||
}
|
||||
if l.MTASTSHTTPS.Enabled {
|
||||
port := config.Port(l.MTASTSHTTPS.Port, 443)
|
||||
@ -382,7 +397,7 @@ func Listen() {
|
||||
// todo: may want to check this against the configured domains, could in theory be just a webserver.
|
||||
return strings.HasPrefix(dom.ASCII, "mta-sts.")
|
||||
}
|
||||
srv.HandleFunc("mtasts", mtastsMatch, "/.well-known/mta-sts.txt", safeHeaders(mtastsPolicyHandle))
|
||||
srv.Handle("mtasts", mtastsMatch, "/.well-known/mta-sts.txt", safeHeaders(http.HandlerFunc(mtastsPolicyHandle)))
|
||||
}
|
||||
if l.PprofHTTP.Enabled {
|
||||
// Importing net/http/pprof registers handlers on the default serve mux.
|
||||
@ -392,7 +407,7 @@ func Listen() {
|
||||
}
|
||||
srv := &serve{[]string{"pprof-http"}, nil, nil, false}
|
||||
portServe[port] = srv
|
||||
srv.HandleFunc("pprof", nil, "/", http.DefaultServeMux.ServeHTTP)
|
||||
srv.Handle("pprof", nil, "/", http.DefaultServeMux)
|
||||
}
|
||||
if l.WebserverHTTP.Enabled {
|
||||
port := config.Port(l.WebserverHTTP.Port, 80)
|
||||
@ -412,7 +427,7 @@ func Listen() {
|
||||
// validation handler.
|
||||
if srv, ok := portServe[80]; ok && srv.TLSConfig == nil {
|
||||
srv.Kinds = append(srv.Kinds, "acme-http-01")
|
||||
srv.HandleFunc("acme-http-01", nil, "/.well-known/acme-challenge/", m.Manager.HTTPHandler(nil).ServeHTTP)
|
||||
srv.Handle("acme-http-01", nil, "/.well-known/acme-challenge/", m.Manager.HTTPHandler(nil))
|
||||
}
|
||||
|
||||
hosts := map[dns.Domain]struct{}{
|
||||
@ -452,19 +467,6 @@ func Listen() {
|
||||
}
|
||||
}
|
||||
|
||||
// Only used when the account page is not active on the same listener.
|
||||
func adminIndex(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
if r.Method != "GET" {
|
||||
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, "/admin/", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// functions to be launched in goroutine that will serve on a listener.
|
||||
var servers []func()
|
||||
|
||||
|
Reference in New Issue
Block a user