mirror of
https://github.com/mjl-/mox.git
synced 2024-12-26 08:23:48 +03:00
don't prevent the html pages to load a favicon, and provide one by default
for issue #186 by morki, thanks for reporting and providing sample favicons. generated by the mentioned generator at favicon.io, with the ubuntu font and a fuchsia-like color. the favicon is served for listeners/domains that have the admin/account/webmail/webapi endpoints enabled, i.e. user-facing. the mta-sts, autoconfig, etc urls don't serve the favicon. admins can create webhandler routes to serve another favicon. these webhandler routes are evaluted before the favicon route (a "service handler").
This commit is contained in:
parent
151bd1a9c0
commit
c629ae26af
7 changed files with 49 additions and 24 deletions
BIN
http/favicon.ico
Normal file
BIN
http/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 823 B |
68
http/web.go
68
http/web.go
|
@ -19,6 +19,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
_ "embed"
|
||||||
_ "net/http/pprof"
|
_ "net/http/pprof"
|
||||||
|
|
||||||
"golang.org/x/exp/maps"
|
"golang.org/x/exp/maps"
|
||||||
|
@ -74,6 +75,29 @@ var (
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// We serve a favicon when webaccount/webmail/webadmin/webapi for account-related
|
||||||
|
// domains. They are configured as "service handler", which have a lower priority
|
||||||
|
// than web handler. Admins can configure a custom /favicon.ico route to override
|
||||||
|
// the builtin favicon. In the future, we may want to make it easier to customize
|
||||||
|
// the favicon, possibly per client settings domain.
|
||||||
|
//
|
||||||
|
//go:embed favicon.ico
|
||||||
|
var faviconIco string
|
||||||
|
var faviconModTime = time.Now()
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
p, err := os.Executable()
|
||||||
|
if err == nil {
|
||||||
|
if st, err := os.Stat(p); err == nil {
|
||||||
|
faviconModTime = st.ModTime()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func faviconHandle(w http.ResponseWriter, r *http.Request) {
|
||||||
|
http.ServeContent(w, r, "favicon.ico", faviconModTime, strings.NewReader(faviconIco))
|
||||||
|
}
|
||||||
|
|
||||||
type responseWriterFlusher interface {
|
type responseWriterFlusher interface {
|
||||||
http.ResponseWriter
|
http.ResponseWriter
|
||||||
http.Flusher
|
http.Flusher
|
||||||
|
@ -361,6 +385,7 @@ type pathHandler struct {
|
||||||
type serve struct {
|
type serve struct {
|
||||||
Kinds []string // Type of handler and protocol (e.g. acme-tls-alpn-01, account-http, admin-https).
|
Kinds []string // Type of handler and protocol (e.g. acme-tls-alpn-01, account-http, admin-https).
|
||||||
TLSConfig *tls.Config
|
TLSConfig *tls.Config
|
||||||
|
Favicon bool
|
||||||
|
|
||||||
// SystemHandlers are for MTA-STS, autoconfig, ACME validation. They can't be
|
// SystemHandlers are for MTA-STS, autoconfig, ACME validation. They can't be
|
||||||
// overridden by WebHandlers. WebHandlers are evaluated next, and the internal
|
// overridden by WebHandlers. WebHandlers are evaluated next, and the internal
|
||||||
|
@ -555,21 +580,26 @@ func portServes(l config.Listener) map[int]*serve {
|
||||||
return mox.Conf.IsClientSettingsDomain(host.Domain)
|
return mox.Conf.IsClientSettingsDomain(host.Domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
var ensureServe func(https bool, port int, kind string) *serve
|
var ensureServe func(https bool, port int, kind string, favicon bool) *serve
|
||||||
ensureServe = func(https bool, port int, kind string) *serve {
|
ensureServe = func(https bool, port int, kind string, favicon bool) *serve {
|
||||||
s := portServe[port]
|
s := portServe[port]
|
||||||
if s == nil {
|
if s == nil {
|
||||||
s = &serve{nil, nil, nil, false, nil}
|
s = &serve{nil, nil, false, nil, false, nil}
|
||||||
portServe[port] = s
|
portServe[port] = s
|
||||||
}
|
}
|
||||||
s.Kinds = append(s.Kinds, kind)
|
s.Kinds = append(s.Kinds, kind)
|
||||||
|
if favicon && !s.Favicon {
|
||||||
|
s.ServiceHandle("favicon", accountHostMatch, "/favicon.ico", mox.SafeHeaders(http.HandlerFunc(faviconHandle)))
|
||||||
|
s.Favicon = true
|
||||||
|
}
|
||||||
|
|
||||||
if https && l.TLS.ACME != "" {
|
if https && l.TLS.ACME != "" {
|
||||||
s.TLSConfig = l.TLS.ACMEConfig
|
s.TLSConfig = l.TLS.ACMEConfig
|
||||||
} else if https {
|
} else if https {
|
||||||
s.TLSConfig = l.TLS.Config
|
s.TLSConfig = l.TLS.Config
|
||||||
if l.TLS.ACME != "" {
|
if l.TLS.ACME != "" {
|
||||||
tlsport := config.Port(mox.Conf.Static.ACME[l.TLS.ACME].Port, 443)
|
tlsport := config.Port(mox.Conf.Static.ACME[l.TLS.ACME].Port, 443)
|
||||||
ensureServe(true, tlsport, "acme-tls-alpn-01")
|
ensureServe(true, tlsport, "acme-tls-alpn-01", false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
|
@ -577,7 +607,7 @@ func portServes(l config.Listener) map[int]*serve {
|
||||||
|
|
||||||
if l.TLS != nil && l.TLS.ACME != "" && (l.SMTP.Enabled && !l.SMTP.NoSTARTTLS || l.Submissions.Enabled || l.IMAPS.Enabled) {
|
if l.TLS != nil && l.TLS.ACME != "" && (l.SMTP.Enabled && !l.SMTP.NoSTARTTLS || l.Submissions.Enabled || l.IMAPS.Enabled) {
|
||||||
port := config.Port(mox.Conf.Static.ACME[l.TLS.ACME].Port, 443)
|
port := config.Port(mox.Conf.Static.ACME[l.TLS.ACME].Port, 443)
|
||||||
ensureServe(true, port, "acme-tls-alpn-01")
|
ensureServe(true, port, "acme-tls-alpn-01", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
if l.AccountHTTP.Enabled {
|
if l.AccountHTTP.Enabled {
|
||||||
|
@ -586,7 +616,7 @@ func portServes(l config.Listener) map[int]*serve {
|
||||||
if l.AccountHTTP.Path != "" {
|
if l.AccountHTTP.Path != "" {
|
||||||
path = l.AccountHTTP.Path
|
path = l.AccountHTTP.Path
|
||||||
}
|
}
|
||||||
srv := ensureServe(false, port, "account-http at "+path)
|
srv := ensureServe(false, port, "account-http at "+path, true)
|
||||||
handler := mox.SafeHeaders(http.StripPrefix(path[:len(path)-1], http.HandlerFunc(webaccount.Handler(path, l.AccountHTTP.Forwarded))))
|
handler := mox.SafeHeaders(http.StripPrefix(path[:len(path)-1], http.HandlerFunc(webaccount.Handler(path, l.AccountHTTP.Forwarded))))
|
||||||
srv.ServiceHandle("account", accountHostMatch, path, handler)
|
srv.ServiceHandle("account", accountHostMatch, path, handler)
|
||||||
redirectToTrailingSlash(srv, accountHostMatch, "account", path)
|
redirectToTrailingSlash(srv, accountHostMatch, "account", path)
|
||||||
|
@ -597,7 +627,7 @@ func portServes(l config.Listener) map[int]*serve {
|
||||||
if l.AccountHTTPS.Path != "" {
|
if l.AccountHTTPS.Path != "" {
|
||||||
path = l.AccountHTTPS.Path
|
path = l.AccountHTTPS.Path
|
||||||
}
|
}
|
||||||
srv := ensureServe(true, port, "account-https at "+path)
|
srv := ensureServe(true, port, "account-https at "+path, true)
|
||||||
handler := mox.SafeHeaders(http.StripPrefix(path[:len(path)-1], http.HandlerFunc(webaccount.Handler(path, l.AccountHTTPS.Forwarded))))
|
handler := mox.SafeHeaders(http.StripPrefix(path[:len(path)-1], http.HandlerFunc(webaccount.Handler(path, l.AccountHTTPS.Forwarded))))
|
||||||
srv.ServiceHandle("account", accountHostMatch, path, handler)
|
srv.ServiceHandle("account", accountHostMatch, path, handler)
|
||||||
redirectToTrailingSlash(srv, accountHostMatch, "account", path)
|
redirectToTrailingSlash(srv, accountHostMatch, "account", path)
|
||||||
|
@ -609,7 +639,7 @@ func portServes(l config.Listener) map[int]*serve {
|
||||||
if l.AdminHTTP.Path != "" {
|
if l.AdminHTTP.Path != "" {
|
||||||
path = l.AdminHTTP.Path
|
path = l.AdminHTTP.Path
|
||||||
}
|
}
|
||||||
srv := ensureServe(false, port, "admin-http at "+path)
|
srv := ensureServe(false, port, "admin-http at "+path, true)
|
||||||
handler := mox.SafeHeaders(http.StripPrefix(path[:len(path)-1], http.HandlerFunc(webadmin.Handler(path, l.AdminHTTP.Forwarded))))
|
handler := mox.SafeHeaders(http.StripPrefix(path[:len(path)-1], http.HandlerFunc(webadmin.Handler(path, l.AdminHTTP.Forwarded))))
|
||||||
srv.ServiceHandle("admin", listenerHostMatch, path, handler)
|
srv.ServiceHandle("admin", listenerHostMatch, path, handler)
|
||||||
redirectToTrailingSlash(srv, listenerHostMatch, "admin", path)
|
redirectToTrailingSlash(srv, listenerHostMatch, "admin", path)
|
||||||
|
@ -620,7 +650,7 @@ func portServes(l config.Listener) map[int]*serve {
|
||||||
if l.AdminHTTPS.Path != "" {
|
if l.AdminHTTPS.Path != "" {
|
||||||
path = l.AdminHTTPS.Path
|
path = l.AdminHTTPS.Path
|
||||||
}
|
}
|
||||||
srv := ensureServe(true, port, "admin-https at "+path)
|
srv := ensureServe(true, port, "admin-https at "+path, true)
|
||||||
handler := mox.SafeHeaders(http.StripPrefix(path[:len(path)-1], http.HandlerFunc(webadmin.Handler(path, l.AdminHTTPS.Forwarded))))
|
handler := mox.SafeHeaders(http.StripPrefix(path[:len(path)-1], http.HandlerFunc(webadmin.Handler(path, l.AdminHTTPS.Forwarded))))
|
||||||
srv.ServiceHandle("admin", listenerHostMatch, path, handler)
|
srv.ServiceHandle("admin", listenerHostMatch, path, handler)
|
||||||
redirectToTrailingSlash(srv, listenerHostMatch, "admin", path)
|
redirectToTrailingSlash(srv, listenerHostMatch, "admin", path)
|
||||||
|
@ -637,7 +667,7 @@ func portServes(l config.Listener) map[int]*serve {
|
||||||
if l.WebAPIHTTP.Path != "" {
|
if l.WebAPIHTTP.Path != "" {
|
||||||
path = l.WebAPIHTTP.Path
|
path = l.WebAPIHTTP.Path
|
||||||
}
|
}
|
||||||
srv := ensureServe(false, port, "webapi-http at "+path)
|
srv := ensureServe(false, port, "webapi-http at "+path, true)
|
||||||
handler := mox.SafeHeaders(http.StripPrefix(path[:len(path)-1], webapisrv.NewServer(maxMsgSize, path, l.WebAPIHTTP.Forwarded)))
|
handler := mox.SafeHeaders(http.StripPrefix(path[:len(path)-1], webapisrv.NewServer(maxMsgSize, path, l.WebAPIHTTP.Forwarded)))
|
||||||
srv.ServiceHandle("webapi", accountHostMatch, path, handler)
|
srv.ServiceHandle("webapi", accountHostMatch, path, handler)
|
||||||
redirectToTrailingSlash(srv, accountHostMatch, "webapi", path)
|
redirectToTrailingSlash(srv, accountHostMatch, "webapi", path)
|
||||||
|
@ -648,7 +678,7 @@ func portServes(l config.Listener) map[int]*serve {
|
||||||
if l.WebAPIHTTPS.Path != "" {
|
if l.WebAPIHTTPS.Path != "" {
|
||||||
path = l.WebAPIHTTPS.Path
|
path = l.WebAPIHTTPS.Path
|
||||||
}
|
}
|
||||||
srv := ensureServe(true, port, "webapi-https at "+path)
|
srv := ensureServe(true, port, "webapi-https at "+path, true)
|
||||||
handler := mox.SafeHeaders(http.StripPrefix(path[:len(path)-1], webapisrv.NewServer(maxMsgSize, path, l.WebAPIHTTPS.Forwarded)))
|
handler := mox.SafeHeaders(http.StripPrefix(path[:len(path)-1], webapisrv.NewServer(maxMsgSize, path, l.WebAPIHTTPS.Forwarded)))
|
||||||
srv.ServiceHandle("webapi", accountHostMatch, path, handler)
|
srv.ServiceHandle("webapi", accountHostMatch, path, handler)
|
||||||
redirectToTrailingSlash(srv, accountHostMatch, "webapi", path)
|
redirectToTrailingSlash(srv, accountHostMatch, "webapi", path)
|
||||||
|
@ -660,7 +690,7 @@ func portServes(l config.Listener) map[int]*serve {
|
||||||
if l.WebmailHTTP.Path != "" {
|
if l.WebmailHTTP.Path != "" {
|
||||||
path = l.WebmailHTTP.Path
|
path = l.WebmailHTTP.Path
|
||||||
}
|
}
|
||||||
srv := ensureServe(false, port, "webmail-http at "+path)
|
srv := ensureServe(false, port, "webmail-http at "+path, true)
|
||||||
var accountPath string
|
var accountPath string
|
||||||
if l.AccountHTTP.Enabled {
|
if l.AccountHTTP.Enabled {
|
||||||
accountPath = "/"
|
accountPath = "/"
|
||||||
|
@ -678,7 +708,7 @@ func portServes(l config.Listener) map[int]*serve {
|
||||||
if l.WebmailHTTPS.Path != "" {
|
if l.WebmailHTTPS.Path != "" {
|
||||||
path = l.WebmailHTTPS.Path
|
path = l.WebmailHTTPS.Path
|
||||||
}
|
}
|
||||||
srv := ensureServe(true, port, "webmail-https at "+path)
|
srv := ensureServe(true, port, "webmail-https at "+path, true)
|
||||||
var accountPath string
|
var accountPath string
|
||||||
if l.AccountHTTPS.Enabled {
|
if l.AccountHTTPS.Enabled {
|
||||||
accountPath = "/"
|
accountPath = "/"
|
||||||
|
@ -693,7 +723,7 @@ func portServes(l config.Listener) map[int]*serve {
|
||||||
|
|
||||||
if l.MetricsHTTP.Enabled {
|
if l.MetricsHTTP.Enabled {
|
||||||
port := config.Port(l.MetricsHTTP.Port, 8010)
|
port := config.Port(l.MetricsHTTP.Port, 8010)
|
||||||
srv := ensureServe(false, port, "metrics-http")
|
srv := ensureServe(false, port, "metrics-http", false)
|
||||||
srv.SystemHandle("metrics", nil, "/metrics", mox.SafeHeaders(promhttp.Handler()))
|
srv.SystemHandle("metrics", nil, "/metrics", mox.SafeHeaders(promhttp.Handler()))
|
||||||
srv.SystemHandle("metrics", nil, "/", mox.SafeHeaders(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv.SystemHandle("metrics", nil, "/", mox.SafeHeaders(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path != "/" {
|
if r.URL.Path != "/" {
|
||||||
|
@ -709,7 +739,7 @@ func portServes(l config.Listener) map[int]*serve {
|
||||||
}
|
}
|
||||||
if l.AutoconfigHTTPS.Enabled {
|
if l.AutoconfigHTTPS.Enabled {
|
||||||
port := config.Port(l.AutoconfigHTTPS.Port, 443)
|
port := config.Port(l.AutoconfigHTTPS.Port, 443)
|
||||||
srv := ensureServe(!l.AutoconfigHTTPS.NonTLS, port, "autoconfig-https")
|
srv := ensureServe(!l.AutoconfigHTTPS.NonTLS, port, "autoconfig-https", false)
|
||||||
autoconfigMatch := func(ipdom dns.IPDomain) bool {
|
autoconfigMatch := func(ipdom dns.IPDomain) bool {
|
||||||
dom := ipdom.Domain
|
dom := ipdom.Domain
|
||||||
if dom.IsZero() {
|
if dom.IsZero() {
|
||||||
|
@ -736,7 +766,7 @@ func portServes(l config.Listener) map[int]*serve {
|
||||||
}
|
}
|
||||||
if l.MTASTSHTTPS.Enabled {
|
if l.MTASTSHTTPS.Enabled {
|
||||||
port := config.Port(l.MTASTSHTTPS.Port, 443)
|
port := config.Port(l.MTASTSHTTPS.Port, 443)
|
||||||
srv := ensureServe(!l.MTASTSHTTPS.NonTLS, port, "mtasts-https")
|
srv := ensureServe(!l.MTASTSHTTPS.NonTLS, port, "mtasts-https", false)
|
||||||
mtastsMatch := func(ipdom dns.IPDomain) bool {
|
mtastsMatch := func(ipdom dns.IPDomain) bool {
|
||||||
// todo: may want to check this against the configured domains, could in theory be just a webserver.
|
// todo: may want to check this against the configured domains, could in theory be just a webserver.
|
||||||
dom := ipdom.Domain
|
dom := ipdom.Domain
|
||||||
|
@ -753,18 +783,18 @@ func portServes(l config.Listener) map[int]*serve {
|
||||||
if _, ok := portServe[port]; ok {
|
if _, ok := portServe[port]; ok {
|
||||||
pkglog.Fatal("cannot serve pprof on same endpoint as other http services")
|
pkglog.Fatal("cannot serve pprof on same endpoint as other http services")
|
||||||
}
|
}
|
||||||
srv := &serve{[]string{"pprof-http"}, nil, nil, false, nil}
|
srv := &serve{[]string{"pprof-http"}, nil, false, nil, false, nil}
|
||||||
portServe[port] = srv
|
portServe[port] = srv
|
||||||
srv.SystemHandle("pprof", nil, "/", http.DefaultServeMux)
|
srv.SystemHandle("pprof", nil, "/", http.DefaultServeMux)
|
||||||
}
|
}
|
||||||
if l.WebserverHTTP.Enabled {
|
if l.WebserverHTTP.Enabled {
|
||||||
port := config.Port(l.WebserverHTTP.Port, 80)
|
port := config.Port(l.WebserverHTTP.Port, 80)
|
||||||
srv := ensureServe(false, port, "webserver-http")
|
srv := ensureServe(false, port, "webserver-http", false)
|
||||||
srv.Webserver = true
|
srv.Webserver = true
|
||||||
}
|
}
|
||||||
if l.WebserverHTTPS.Enabled {
|
if l.WebserverHTTPS.Enabled {
|
||||||
port := config.Port(l.WebserverHTTPS.Port, 443)
|
port := config.Port(l.WebserverHTTPS.Port, 443)
|
||||||
srv := ensureServe(true, port, "webserver-https")
|
srv := ensureServe(true, port, "webserver-https", false)
|
||||||
srv.Webserver = true
|
srv.Webserver = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
<title>Mox Account</title>
|
<title>Mox Account</title>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<link rel="icon" href="noNeedlessFaviconRequestsPlease:" />
|
|
||||||
<style>
|
<style>
|
||||||
body, html { padding: 1em; font-size: 16px; }
|
body, html { padding: 1em; font-size: 16px; }
|
||||||
* { font-size: inherit; font-family: ubuntu, lato, sans-serif; margin: 0; padding: 0; box-sizing: border-box; }
|
* { font-size: inherit; font-family: ubuntu, lato, sans-serif; margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
<title>Mox Admin</title>
|
<title>Mox Admin</title>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<link rel="icon" href="noNeedlessFaviconRequestsPlease:" />
|
|
||||||
<style>
|
<style>
|
||||||
body, html { padding: 1em; font-size: 16px; }
|
body, html { padding: 1em; font-size: 16px; }
|
||||||
* { font-size: inherit; font-family: ubuntu, lato, sans-serif; margin: 0; padding: 0; box-sizing: border-box; }
|
* { font-size: inherit; font-family: ubuntu, lato, sans-serif; margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
<title>Message</title>
|
<title>Message</title>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<link rel="icon" href="noNeedlessFaviconRequestsPlease:" />
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="page"><div style="padding: 1em">Loading...</div></div>
|
<div id="page"><div style="padding: 1em">Loading...</div></div>
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<link rel="icon" href="noNeedlessFaviconRequestsPlease:" />
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="page" style="opacity: .1">Loading...</div>
|
<div id="page" style="opacity: .1">Loading...</div>
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
<title>Mox Webmail</title>
|
<title>Mox Webmail</title>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1" />
|
||||||
<link rel="icon" href="noNeedlessFaviconRequestsPlease:" />
|
|
||||||
<style>
|
<style>
|
||||||
h1, h2 { margin-bottom: 1ex; }
|
h1, h2 { margin-bottom: 1ex; }
|
||||||
h1 { font-size: 1.1rem; }
|
h1 { font-size: 1.1rem; }
|
||||||
|
|
Loading…
Reference in a new issue