mirror of
https://github.com/axllent/mailpit.git
synced 2025-01-06 00:25:39 +02:00
169 lines
4.3 KiB
Go
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
|
|
}
|