1
0
mirror of https://github.com/axllent/mailpit.git synced 2025-01-06 00:25:39 +02:00
mailpit/server/server.go
2022-08-04 17:18:07 +12:00

169 lines
4.3 KiB
Go

package server
import (
"compress/gzip"
"embed"
"fmt"
"io"
"io/fs"
"log"
"net/http"
"os"
"strconv"
"strings"
"github.com/axllent/mailpit/config"
"github.com/axllent/mailpit/logger"
"github.com/axllent/mailpit/server/websockets"
"github.com/gorilla/mux"
)
//go:embed ui
var embeddedFS embed.FS
// Listen will start the httpd
func Listen() {
serverRoot, err := fs.Sub(embeddedFS, "ui")
if err != nil {
logger.Log().Errorf("[http] %s", err)
os.Exit(1)
}
websockets.MessageHub = websockets.NewHub()
go websockets.MessageHub.Run()
r := mux.NewRouter()
r.HandleFunc("/api/mailboxes", middleWareFunc(apiListMailboxes))
r.HandleFunc("/api/{mailbox}/messages", middleWareFunc(apiListMailbox))
r.HandleFunc("/api/{mailbox}/search", middleWareFunc(apiSearchMailbox))
r.HandleFunc("/api/{mailbox}/delete", middleWareFunc(apiDeleteAll))
r.HandleFunc("/api/{mailbox}/events", apiWebsocket)
r.HandleFunc("/api/{mailbox}/{id}/source", middleWareFunc(apiDownloadSource))
r.HandleFunc("/api/{mailbox}/{id}/part/{partID}", middleWareFunc(apiDownloadAttachment))
r.HandleFunc("/api/{mailbox}/{id}/delete", middleWareFunc(apiDeleteOne))
r.HandleFunc("/api/{mailbox}/{id}/unread", middleWareFunc(apiUnreadOne))
r.HandleFunc("/api/{mailbox}/{id}", middleWareFunc(apiOpenMessage))
r.PathPrefix("/").Handler(middlewareHandler(http.FileServer(http.FS(serverRoot))))
http.Handle("/", r)
if config.SSLCert != "" && config.SSLKey != "" {
logger.Log().Infof("[http] starting secure server on https://%s", config.HTTPListen)
log.Fatal(http.ListenAndServeTLS(config.HTTPListen, config.SSLCert, config.SSLKey, nil))
} else {
logger.Log().Infof("[http] starting server on http://%s", config.HTTPListen)
log.Fatal(http.ListenAndServe(config.HTTPListen, nil))
}
}
// BasicAuthResponse returns an basic auth response to the browser
func basicAuthResponse(w http.ResponseWriter) {
w.Header().Set("WWW-Authenticate", `Basic realm="Login"`)
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte("Unauthorised.\n"))
}
type gzipResponseWriter struct {
io.Writer
http.ResponseWriter
}
func (w gzipResponseWriter) Write(b []byte) (int, error) {
return w.Writer.Write(b)
}
// MiddleWareFunc http middleware adds optional basic authentication
// and gzip compression.
func middleWareFunc(fn http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if config.AuthFile != "" {
user, pass, ok := r.BasicAuth()
if !ok {
basicAuthResponse(w)
return
}
if !config.Auth.Match(user, pass) {
basicAuthResponse(w)
return
}
}
if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
fn(w, r)
return
}
w.Header().Set("Content-Encoding", "gzip")
gz := gzip.NewWriter(w)
defer gz.Close()
gzr := gzipResponseWriter{Writer: gz, ResponseWriter: w}
fn(gzr, r)
}
}
// MiddlewareHandler http middleware adds optional basic authentication
// and gzip compression
func middlewareHandler(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if config.AuthFile != "" {
user, pass, ok := r.BasicAuth()
if !ok {
basicAuthResponse(w)
return
}
if !config.Auth.Match(user, pass) {
basicAuthResponse(w)
return
}
}
if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
h.ServeHTTP(w, r)
return
}
w.Header().Set("Content-Encoding", "gzip")
gz := gzip.NewWriter(w)
defer gz.Close()
h.ServeHTTP(gzipResponseWriter{Writer: gz, ResponseWriter: w}, r)
})
}
// FourOFour returns a basic 404 message
func fourOFour(w http.ResponseWriter) {
w.WriteHeader(http.StatusNotFound)
w.Header().Set("Content-Type", "text/plain")
fmt.Fprint(w, "404 page not found")
}
// HTTPError returns a basic error message (400 response)
func httpError(w http.ResponseWriter, msg string) {
w.WriteHeader(http.StatusBadRequest)
w.Header().Set("Content-Type", "text/plain")
fmt.Fprint(w, msg)
}
// Get the start and limit based on query params. Defaults to 0, 50
func getStartLimit(req *http.Request) (start int, limit int) {
start = 0
limit = 50
s := req.URL.Query().Get("start")
if n, e := strconv.ParseInt(s, 10, 64); e == nil && n > 0 {
start = int(n)
}
l := req.URL.Query().Get("limit")
if n, e := strconv.ParseInt(l, 10, 64); e == nil && n > 0 {
if n > 500 {
n = 500
}
limit = int(n)
}
return start, limit
}