@@ -28,7 +28,6 @@ import (
2828 "github.com/axllent/mailpit/server/apiv1"
2929 "github.com/axllent/mailpit/server/handlers"
3030 "github.com/axllent/mailpit/server/websockets"
31- "github.com/gorilla/mux"
3231 "github.com/lithammer/shortuuid/v4"
3332)
3433
@@ -64,37 +63,43 @@ func Listen() {
6463 r := apiRoutes ()
6564
6665 // kubernetes probes
67- r .HandleFunc (config .Webroot + "livez" , handlers .HealthzHandler )
68- r .HandleFunc (config .Webroot + "readyz" , handlers .ReadyzHandler (isReady ))
66+ r .HandleFunc ("GET " + config .Webroot + "livez" , handlers .HealthzHandler )
67+ r .HandleFunc ("GET " + config .Webroot + "readyz" , handlers .ReadyzHandler (isReady ))
6968
7069 // proxy handler for screenshots
71- r .HandleFunc (config .Webroot + "proxy" , middleWareFunc (handlers .ProxyHandler )). Methods ( "GET" )
70+ r .HandleFunc ("GET " + config .Webroot + "proxy" , middleWareFunc (handlers .ProxyHandler ))
7271
7372 // virtual filesystem for /dist/ & some individual files
74- r .PathPrefix ( config .Webroot + "dist/" ). Handler ( middleWareFunc (embedController ))
75- r .PathPrefix ( config .Webroot + "api/" ). Handler ( middleWareFunc (embedController ))
76- r .Path ( config .Webroot + "favicon.ico" ). Handler ( middleWareFunc (embedController ))
77- r .Path ( config .Webroot + "favicon.svg" ). Handler ( middleWareFunc (embedController ))
78- r .Path ( config .Webroot + "mailpit.svg" ). Handler ( middleWareFunc (embedController ))
79- r .Path ( config .Webroot + "notification.png" ). Handler ( middleWareFunc (embedController ))
73+ r .Handle ( "GET " + config .Webroot + "dist/" , middleWareFunc (embedController ))
74+ r .Handle ( "GET " + config .Webroot + "api/" , middleWareFunc (embedController ))
75+ r .Handle ( "GET " + config .Webroot + "favicon.ico" , middleWareFunc (embedController ))
76+ r .Handle ( "GET " + config .Webroot + "favicon.svg" , middleWareFunc (embedController ))
77+ r .Handle ( "GET " + config .Webroot + "mailpit.svg" , middleWareFunc (embedController ))
78+ r .Handle ( "GET " + config .Webroot + "notification.png" , middleWareFunc (embedController ))
8079
8180 // redirect to webroot if no trailing slash
8281 if config .Webroot != "/" {
8382 redirect := strings .TrimRight (config .Webroot , "/" )
84- r .HandleFunc (redirect , middleWareFunc (addSlashToWebroot )). Methods ( "GET" )
83+ r .HandleFunc ("GET " + redirect , middleWareFunc (addSlashToWebroot ))
8584 }
8685
8786 // UI shortcut
88- r .HandleFunc (config .Webroot + "view/latest" , middleWareFunc (handlers .RedirectToLatestMessage )).Methods ("GET" )
89-
90- // frontend testing
91- r .HandleFunc (config .Webroot + "view/{id}.html" , middleWareFunc (apiv1 .GetMessageHTML )).Methods ("GET" )
92- r .HandleFunc (config .Webroot + "view/{id}.txt" , middleWareFunc (apiv1 .GetMessageText )).Methods ("GET" )
93-
94- // web UI via virtual index.html
95- r .PathPrefix (config .Webroot + "view/" ).Handler (middleWareFunc (index )).Methods ("GET" )
96- r .Path (config .Webroot + "search" ).Handler (middleWareFunc (index )).Methods ("GET" )
97- r .Path (config .Webroot ).Handler (middleWareFunc (index )).Methods ("GET" )
87+ r .HandleFunc ("GET " + config .Webroot + "view/latest" , middleWareFunc (handlers .RedirectToLatestMessage ))
88+
89+ // frontend testing + web UI via virtual index.html
90+ // Go's ServeMux wildcards must span a full path segment so {id}.html is invalid;
91+ // viewHandler dispatches on the path suffix instead.
92+ r .HandleFunc ("GET " + config .Webroot + "view/" , middleWareFunc (viewHandler ))
93+
94+ r .Handle ("GET " + config .Webroot + "search" , middleWareFunc (index ))
95+ // Exact-match the webroot; stdlib "/" is always a subtree so we guard inside.
96+ r .HandleFunc ("GET " + config .Webroot , func (w http.ResponseWriter , r * http.Request ) {
97+ if r .URL .Path != config .Webroot {
98+ http .NotFound (w , r )
99+ return
100+ }
101+ middleWareFunc (index )(w , r )
102+ })
98103
99104 if auth .UICredentials != nil {
100105 logger .Log ().Info ("[http] enabling basic authentication" )
@@ -165,51 +170,51 @@ func Listen() {
165170 }
166171}
167172
168- func apiRoutes () * mux. Router {
169- r := mux . NewRouter ()
173+ func apiRoutes () * http. ServeMux {
174+ r := http . NewServeMux ()
170175
171176 // API V1
172- r .HandleFunc (config .Webroot + "api/v1/messages" , middleWareFunc (apiv1 .GetMessages )). Methods ( "GET" )
173- r .HandleFunc (config .Webroot + "api/v1/messages" , middleWareFunc (apiv1 .SetReadStatus )). Methods ( "PUT" )
174- r .HandleFunc (config .Webroot + "api/v1/messages" , middleWareFunc (apiv1 .DeleteMessages )). Methods ( "DELETE" )
175- r .HandleFunc (config .Webroot + "api/v1/search" , middleWareFunc (apiv1 .Search )). Methods ( "GET" )
176- r .HandleFunc (config .Webroot + "api/v1/search" , middleWareFunc (apiv1 .DeleteSearch )). Methods ( "DELETE" )
177- r .HandleFunc (config .Webroot + "api/v1/send" , sendAPIAuthMiddleware (apiv1 .SendMessageHandler )). Methods ( "POST" )
178- r .HandleFunc (config .Webroot + "api/v1/tags" , middleWareFunc (apiv1 .GetAllTags )). Methods ( "GET" )
179- r .HandleFunc (config .Webroot + "api/v1/tags" , middleWareFunc (apiv1 .SetMessageTags )). Methods ( "PUT" )
180- r .HandleFunc (config .Webroot + "api/v1/tags/{tag}" , middleWareFunc (apiv1 .RenameTag )). Methods ( "PUT" )
181- r .HandleFunc (config .Webroot + "api/v1/tags/{tag}" , middleWareFunc (apiv1 .DeleteTag )). Methods ( "DELETE" )
182- r .HandleFunc (config .Webroot + "api/v1/message/{id}/part/{partID}" , middleWareFunc (apiv1 .DownloadAttachment )). Methods ( "GET" )
183- r .HandleFunc (config .Webroot + "api/v1/message/{id}/part/{partID}/thumb" , middleWareFunc (apiv1 .Thumbnail )). Methods ( "GET" )
184- r .HandleFunc (config .Webroot + "api/v1/message/{id}/headers" , middleWareFunc (apiv1 .GetHeaders )). Methods ( "GET" )
185- r .HandleFunc (config .Webroot + "api/v1/message/{id}/raw" , middleWareFunc (apiv1 .DownloadRaw )). Methods ( "GET" )
186- r .HandleFunc (config .Webroot + "api/v1/message/{id}/release" , middleWareFunc (apiv1 .ReleaseMessage )). Methods ( "POST" )
187- r .HandleFunc (config .Webroot + "api/v1/message/{id}/html-check" , middleWareFunc (apiv1 .HTMLCheck )). Methods ( "GET" )
188- r .HandleFunc (config .Webroot + "api/v1/message/{id}/link-check" , middleWareFunc (apiv1 .LinkCheck )). Methods ( "GET" )
177+ r .HandleFunc ("GET " + config .Webroot + "api/v1/messages" , middleWareFunc (apiv1 .GetMessages ))
178+ r .HandleFunc ("PUT " + config .Webroot + "api/v1/messages" , middleWareFunc (apiv1 .SetReadStatus ))
179+ r .HandleFunc ("DELETE " + config .Webroot + "api/v1/messages" , middleWareFunc (apiv1 .DeleteMessages ))
180+ r .HandleFunc ("GET " + config .Webroot + "api/v1/search" , middleWareFunc (apiv1 .Search ))
181+ r .HandleFunc ("DELETE " + config .Webroot + "api/v1/search" , middleWareFunc (apiv1 .DeleteSearch ))
182+ r .HandleFunc ("POST " + config .Webroot + "api/v1/send" , sendAPIAuthMiddleware (apiv1 .SendMessageHandler ))
183+ r .HandleFunc ("GET " + config .Webroot + "api/v1/tags" , middleWareFunc (apiv1 .GetAllTags ))
184+ r .HandleFunc ("PUT " + config .Webroot + "api/v1/tags" , middleWareFunc (apiv1 .SetMessageTags ))
185+ r .HandleFunc ("PUT " + config .Webroot + "api/v1/tags/{tag}" , middleWareFunc (apiv1 .RenameTag ))
186+ r .HandleFunc ("DELETE " + config .Webroot + "api/v1/tags/{tag}" , middleWareFunc (apiv1 .DeleteTag ))
187+ r .HandleFunc ("GET " + config .Webroot + "api/v1/message/{id}/part/{partID}" , middleWareFunc (apiv1 .DownloadAttachment ))
188+ r .HandleFunc ("GET " + config .Webroot + "api/v1/message/{id}/part/{partID}/thumb" , middleWareFunc (apiv1 .Thumbnail ))
189+ r .HandleFunc ("GET " + config .Webroot + "api/v1/message/{id}/headers" , middleWareFunc (apiv1 .GetHeaders ))
190+ r .HandleFunc ("GET " + config .Webroot + "api/v1/message/{id}/raw" , middleWareFunc (apiv1 .DownloadRaw ))
191+ r .HandleFunc ("POST " + config .Webroot + "api/v1/message/{id}/release" , middleWareFunc (apiv1 .ReleaseMessage ))
192+ r .HandleFunc ("GET " + config .Webroot + "api/v1/message/{id}/html-check" , middleWareFunc (apiv1 .HTMLCheck ))
193+ r .HandleFunc ("GET " + config .Webroot + "api/v1/message/{id}/link-check" , middleWareFunc (apiv1 .LinkCheck ))
189194 if config .EnableSpamAssassin != "" {
190- r .HandleFunc (config .Webroot + "api/v1/message/{id}/sa-check" , middleWareFunc (apiv1 .SpamAssassinCheck )). Methods ( "GET" )
195+ r .HandleFunc ("GET " + config .Webroot + "api/v1/message/{id}/sa-check" , middleWareFunc (apiv1 .SpamAssassinCheck ))
191196 }
192- r .HandleFunc (config .Webroot + "api/v1/message/{id}" , middleWareFunc (apiv1 .GetMessage )). Methods ( "GET" )
193- r .HandleFunc (config .Webroot + "api/v1/info" , middleWareFunc (apiv1 .AppInfo )). Methods ( "GET" )
194- r .HandleFunc (config .Webroot + "api/v1/webui" , middleWareFunc (apiv1 .WebUIConfig )). Methods ( "GET" )
195- r .HandleFunc (config .Webroot + "api/v1/swagger.json" , middleWareFunc (swaggerBasePath )). Methods ( "GET" )
197+ r .HandleFunc ("GET " + config .Webroot + "api/v1/message/{id}" , middleWareFunc (apiv1 .GetMessage ))
198+ r .HandleFunc ("GET " + config .Webroot + "api/v1/info" , middleWareFunc (apiv1 .AppInfo ))
199+ r .HandleFunc ("GET " + config .Webroot + "api/v1/webui" , middleWareFunc (apiv1 .WebUIConfig ))
200+ r .HandleFunc ("GET " + config .Webroot + "api/v1/swagger.json" , middleWareFunc (swaggerBasePath ))
196201
197202 // Chaos
198- r .HandleFunc (config .Webroot + "api/v1/chaos" , middleWareFunc (apiv1 .GetChaos )). Methods ( "GET" )
199- r .HandleFunc (config .Webroot + "api/v1/chaos" , middleWareFunc (apiv1 .SetChaos )). Methods ( "PUT" )
203+ r .HandleFunc ("GET " + config .Webroot + "api/v1/chaos" , middleWareFunc (apiv1 .GetChaos ))
204+ r .HandleFunc ("PUT " + config .Webroot + "api/v1/chaos" , middleWareFunc (apiv1 .SetChaos ))
200205
201206 // Prometheus metrics (if enabled and using existing server)
202207 if prometheus .GetMode () == "integrated" {
203- r .HandleFunc (config .Webroot + "metrics" , middleWareFunc (func (w http.ResponseWriter , r * http.Request ) {
208+ r .HandleFunc ("GET " + config .Webroot + "metrics" , middleWareFunc (func (w http.ResponseWriter , r * http.Request ) {
204209 prometheus .GetHandler ().ServeHTTP (w , r )
205- })). Methods ( "GET" )
210+ }))
206211 }
207212
208213 // web UI websocket
209- r .HandleFunc (config .Webroot + "api/events" , middleWareFunc (apiWebsocket )). Methods ( "GET" )
214+ r .HandleFunc ("GET " + config .Webroot + "api/events" , middleWareFunc (apiWebsocket ))
210215
211216 // return blank 200 response for OPTIONS requests for CORS
212- r .PathPrefix ( config .Webroot + "api/v1/" ). Handler ( middleWareFunc (apiv1 .GetOptions )). Methods ( "OPTIONS" )
217+ r .Handle ( "OPTIONS " + config .Webroot + "api/v1/" , middleWareFunc (apiv1 .GetOptions ))
213218
214219 return r
215220}
@@ -345,6 +350,21 @@ func addSlashToWebroot(w http.ResponseWriter, r *http.Request) {
345350 http .Redirect (w , r , config .Webroot , http .StatusFound )
346351}
347352
353+ // viewHandler routes /view/ requests based on path suffix.
354+ // Go's ServeMux requires wildcards to span a full path segment,
355+ // so patterns like /view/{id}.html are invalid; we dispatch manually here.
356+ func viewHandler (w http.ResponseWriter , r * http.Request ) {
357+ path := strings .TrimPrefix (r .URL .Path , config .Webroot + "view/" )
358+ switch {
359+ case strings .HasSuffix (path , ".html" ):
360+ apiv1 .GetMessageHTML (w , r )
361+ case strings .HasSuffix (path , ".txt" ):
362+ apiv1 .GetMessageText (w , r )
363+ default :
364+ index (w , r )
365+ }
366+ }
367+
348368// Websocket to broadcast changes.
349369// Authentication and CORS are handled by middleWareFunc before this is reached.
350370func apiWebsocket (w http.ResponseWriter , r * http.Request ) {
0 commit comments