Skip to content

Commit

Permalink
don't prevent the html pages to load a favicon, and provide one by de…
Browse files Browse the repository at this point in the history
…fault

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").
  • Loading branch information
mjl- committed Jul 8, 2024
1 parent 151bd1a commit c629ae2
Show file tree
Hide file tree
Showing 7 changed files with 49 additions and 24 deletions.
Binary file added http/favicon.ico
Binary file not shown.
68 changes: 49 additions & 19 deletions http/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"strings"
"time"

_ "embed"
_ "net/http/pprof"

"golang.org/x/exp/maps"
Expand Down Expand Up @@ -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 {
http.ResponseWriter
http.Flusher
Expand Down Expand Up @@ -361,6 +385,7 @@ type pathHandler struct {
type serve struct {
Kinds []string // Type of handler and protocol (e.g. acme-tls-alpn-01, account-http, admin-https).
TLSConfig *tls.Config
Favicon bool

// SystemHandlers are for MTA-STS, autoconfig, ACME validation. They can't be
// overridden by WebHandlers. WebHandlers are evaluated next, and the internal
Expand Down Expand Up @@ -555,29 +580,34 @@ func portServes(l config.Listener) map[int]*serve {
return mox.Conf.IsClientSettingsDomain(host.Domain)
}

var ensureServe func(https bool, port int, kind string) *serve
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, favicon bool) *serve {
s := portServe[port]
if s == nil {
s = &serve{nil, nil, nil, false, nil}
s = &serve{nil, nil, false, nil, false, nil}
portServe[port] = s
}
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 != "" {
s.TLSConfig = l.TLS.ACMEConfig
} else if https {
s.TLSConfig = l.TLS.Config
if l.TLS.ACME != "" {
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
}

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)
ensureServe(true, port, "acme-tls-alpn-01")
ensureServe(true, port, "acme-tls-alpn-01", false)
}

if l.AccountHTTP.Enabled {
Expand All @@ -586,7 +616,7 @@ func portServes(l config.Listener) map[int]*serve {
if 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))))
srv.ServiceHandle("account", accountHostMatch, path, handler)
redirectToTrailingSlash(srv, accountHostMatch, "account", path)
Expand All @@ -597,7 +627,7 @@ func portServes(l config.Listener) map[int]*serve {
if 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))))
srv.ServiceHandle("account", accountHostMatch, path, handler)
redirectToTrailingSlash(srv, accountHostMatch, "account", path)
Expand All @@ -609,7 +639,7 @@ func portServes(l config.Listener) map[int]*serve {
if 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))))
srv.ServiceHandle("admin", listenerHostMatch, path, handler)
redirectToTrailingSlash(srv, listenerHostMatch, "admin", path)
Expand All @@ -620,7 +650,7 @@ func portServes(l config.Listener) map[int]*serve {
if 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))))
srv.ServiceHandle("admin", listenerHostMatch, path, handler)
redirectToTrailingSlash(srv, listenerHostMatch, "admin", path)
Expand All @@ -637,7 +667,7 @@ func portServes(l config.Listener) map[int]*serve {
if 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)))
srv.ServiceHandle("webapi", accountHostMatch, path, handler)
redirectToTrailingSlash(srv, accountHostMatch, "webapi", path)
Expand All @@ -648,7 +678,7 @@ func portServes(l config.Listener) map[int]*serve {
if 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)))
srv.ServiceHandle("webapi", accountHostMatch, path, handler)
redirectToTrailingSlash(srv, accountHostMatch, "webapi", path)
Expand All @@ -660,7 +690,7 @@ func portServes(l config.Listener) map[int]*serve {
if 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
if l.AccountHTTP.Enabled {
accountPath = "/"
Expand All @@ -678,7 +708,7 @@ func portServes(l config.Listener) map[int]*serve {
if 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
if l.AccountHTTPS.Enabled {
accountPath = "/"
Expand All @@ -693,7 +723,7 @@ func portServes(l config.Listener) map[int]*serve {

if l.MetricsHTTP.Enabled {
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, "/", mox.SafeHeaders(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
Expand All @@ -709,7 +739,7 @@ func portServes(l config.Listener) map[int]*serve {
}
if l.AutoconfigHTTPS.Enabled {
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 {
dom := ipdom.Domain
if dom.IsZero() {
Expand All @@ -736,7 +766,7 @@ func portServes(l config.Listener) map[int]*serve {
}
if l.MTASTSHTTPS.Enabled {
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 {
// todo: may want to check this against the configured domains, could in theory be just a webserver.
dom := ipdom.Domain
Expand All @@ -753,18 +783,18 @@ func portServes(l config.Listener) map[int]*serve {
if _, ok := portServe[port]; ok {
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
srv.SystemHandle("pprof", nil, "/", http.DefaultServeMux)
}
if l.WebserverHTTP.Enabled {
port := config.Port(l.WebserverHTTP.Port, 80)
srv := ensureServe(false, port, "webserver-http")
srv := ensureServe(false, port, "webserver-http", false)
srv.Webserver = true
}
if l.WebserverHTTPS.Enabled {
port := config.Port(l.WebserverHTTPS.Port, 443)
srv := ensureServe(true, port, "webserver-https")
srv := ensureServe(true, port, "webserver-https", false)
srv.Webserver = true
}

Expand Down
1 change: 0 additions & 1 deletion webaccount/account.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
<title>Mox Account</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="noNeedlessFaviconRequestsPlease:" />
<style>
body, html { padding: 1em; font-size: 16px; }
* { font-size: inherit; font-family: ubuntu, lato, sans-serif; margin: 0; padding: 0; box-sizing: border-box; }
Expand Down
1 change: 0 additions & 1 deletion webadmin/admin.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
<title>Mox Admin</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="noNeedlessFaviconRequestsPlease:" />
<style>
body, html { padding: 1em; font-size: 16px; }
* { font-size: inherit; font-family: ubuntu, lato, sans-serif; margin: 0; padding: 0; box-sizing: border-box; }
Expand Down
1 change: 0 additions & 1 deletion webmail/msg.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
<title>Message</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="noNeedlessFaviconRequestsPlease:" />
</head>
<body>
<div id="page"><div style="padding: 1em">Loading...</div></div>
Expand Down
1 change: 0 additions & 1 deletion webmail/text.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="noNeedlessFaviconRequestsPlease:" />
</head>
<body>
<div id="page" style="opacity: .1">Loading...</div>
Expand Down
1 change: 0 additions & 1 deletion webmail/webmail.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
<title>Mox Webmail</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1" />
<link rel="icon" href="noNeedlessFaviconRequestsPlease:" />
<style>
h1, h2 { margin-bottom: 1ex; }
h1 { font-size: 1.1rem; }
Expand Down

0 comments on commit c629ae2

Please sign in to comment.