Move UUID validation from multiple places into a middleware func

This commit is contained in:
Kailash Nadh 2019-07-21 20:11:11 +05:30
parent 3fddd78ebf
commit a060d94cb5
2 changed files with 32 additions and 42 deletions

View File

@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
"net/http" "net/http"
"net/url" "net/url"
"regexp"
"strconv" "strconv"
"strings" "strings"
@ -37,6 +38,8 @@ type pagination struct {
Limit int `json:"limit"` Limit int `json:"limit"`
} }
var reUUID = regexp.MustCompile("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$")
// registerHandlers registers HTTP handlers. // registerHandlers registers HTTP handlers.
func registerHandlers(e *echo.Echo) { func registerHandlers(e *echo.Echo) {
e.GET("/", handleIndexPage) e.GET("/", handleIndexPage)
@ -97,12 +100,18 @@ func registerHandlers(e *echo.Echo) {
e.DELETE("/api/templates/:id", handleDeleteTemplate) e.DELETE("/api/templates/:id", handleDeleteTemplate)
// Subscriber facing views. // Subscriber facing views.
e.GET("/subscription/:campUUID/:subUUID", handleSubscriptionPage) e.GET("/subscription/:campUUID/:subUUID", validateUUID(handleSubscriptionPage,
e.POST("/subscription/:campUUID/:subUUID", handleSubscriptionPage) "campUUID", "subUUID"))
e.POST("/subscription/export/:subUUID", handleSelfExportSubscriberData) e.POST("/subscription/:campUUID/:subUUID", validateUUID(handleSubscriptionPage,
e.POST("/subscription/wipe/:subUUID", handleWipeSubscriberData) "campUUID", "subUUID"))
e.GET("/link/:linkUUID/:campUUID/:subUUID", handleLinkRedirect) e.POST("/subscription/export/:subUUID", validateUUID(handleSelfExportSubscriberData,
e.GET("/campaign/:campUUID/:subUUID/px.png", handleRegisterCampaignView) "subUUID"))
e.POST("/subscription/wipe/:subUUID", validateUUID(handleWipeSubscriberData,
"subUUID"))
e.GET("/link/:linkUUID/:campUUID/:subUUID", validateUUID(handleLinkRedirect,
"linkUUID", "campUUID", "subUUID"))
e.GET("/campaign/:campUUID/:subUUID/px.png", validateUUID(handleRegisterCampaignView,
"campUUID", "subUUID"))
// Static views. // Static views.
e.GET("/lists", handleIndexPage) e.GET("/lists", handleIndexPage)
@ -130,6 +139,20 @@ func handleIndexPage(c echo.Context) error {
return c.String(http.StatusOK, string(b)) return c.String(http.StatusOK, string(b))
} }
// validateUUID validates the UUID string format for a given set of params.
func validateUUID(next echo.HandlerFunc, params ...string) echo.HandlerFunc {
return func(c echo.Context) error {
for _, p := range params {
if !reUUID.MatchString(c.Param(p)) {
return c.Render(http.StatusBadRequest, "message",
makeMsgTpl("Invalid request", "",
`One or more UUIDs in the request are invalid.`))
}
}
return next(c)
}
}
// makeAttribsBlob takes a list of keys and values and creates // makeAttribsBlob takes a list of keys and values and creates
// a JSON map out of them. // a JSON map out of them.
func makeAttribsBlob(keys []string, vals []string) ([]byte, bool) { func makeAttribsBlob(keys []string, vals []string) ([]byte, bool) {
@ -154,7 +177,6 @@ func makeAttribsBlob(keys []string, vals []string) ([]byte, bool) {
val = s val = s
} }
} }
attribs[key] = val attribs[key] = val
} }
@ -162,7 +184,6 @@ func makeAttribsBlob(keys []string, vals []string) ([]byte, bool) {
j, _ := json.Marshal(attribs) j, _ := json.Marshal(attribs)
return j, true return j, true
} }
return nil, false return nil, false
} }

View File

@ -7,7 +7,6 @@ import (
"image/png" "image/png"
"io" "io"
"net/http" "net/http"
"regexp"
"strconv" "strconv"
"github.com/knadh/listmonk/messenger" "github.com/knadh/listmonk/messenger"
@ -52,8 +51,7 @@ type msgTpl struct {
} }
var ( var (
regexValidUUID = regexp.MustCompile("^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}$") pixelPNG = drawTransparentImage(3, 14)
pixelPNG = drawTransparentImage(3, 14)
) )
// Render executes and renders a template for echo. // Render executes and renders a template for echo.
@ -83,14 +81,6 @@ func handleSubscriptionPage(c echo.Context) error {
out.AllowExport = app.Constants.Privacy.AllowExport out.AllowExport = app.Constants.Privacy.AllowExport
out.AllowWipe = app.Constants.Privacy.AllowWipe out.AllowWipe = app.Constants.Privacy.AllowWipe
if !regexValidUUID.MatchString(campUUID) ||
!regexValidUUID.MatchString(subUUID) {
return c.Render(http.StatusBadRequest, "message",
makeMsgTpl("Invalid request", "",
`The unsubscription request contains invalid IDs.
Please follow the correct link.`))
}
// Unsubscribe. // Unsubscribe.
if unsub { if unsub {
// Is blacklisting allowed? // Is blacklisting allowed?
@ -119,12 +109,6 @@ func handleLinkRedirect(c echo.Context) error {
campUUID = c.Param("campUUID") campUUID = c.Param("campUUID")
subUUID = c.Param("subUUID") subUUID = c.Param("subUUID")
) )
if !regexValidUUID.MatchString(linkUUID) ||
!regexValidUUID.MatchString(campUUID) ||
!regexValidUUID.MatchString(subUUID) {
return c.Render(http.StatusBadRequest, "message",
makeMsgTpl("Invalid link", "", "The link you clicked is invalid."))
}
var url string var url string
if err := app.Queries.RegisterLinkClick.Get(&url, linkUUID, campUUID, subUUID); err != nil { if err := app.Queries.RegisterLinkClick.Get(&url, linkUUID, campUUID, subUUID); err != nil {
@ -146,13 +130,9 @@ func handleRegisterCampaignView(c echo.Context) error {
campUUID = c.Param("campUUID") campUUID = c.Param("campUUID")
subUUID = c.Param("subUUID") subUUID = c.Param("subUUID")
) )
if regexValidUUID.MatchString(campUUID) && if _, err := app.Queries.RegisterCampaignView.Exec(campUUID, subUUID); err != nil {
regexValidUUID.MatchString(subUUID) { app.Logger.Printf("error registering campaign view: %s", err)
if _, err := app.Queries.RegisterCampaignView.Exec(campUUID, subUUID); err != nil {
app.Logger.Printf("error registering campaign view: %s", err)
}
} }
c.Response().Header().Set("Cache-Control", "no-cache") c.Response().Header().Set("Cache-Control", "no-cache")
return c.Blob(http.StatusOK, "image/png", pixelPNG) return c.Blob(http.StatusOK, "image/png", pixelPNG)
} }
@ -166,12 +146,6 @@ func handleSelfExportSubscriberData(c echo.Context) error {
app = c.Get("app").(*App) app = c.Get("app").(*App)
subUUID = c.Param("subUUID") subUUID = c.Param("subUUID")
) )
if !regexValidUUID.MatchString(subUUID) {
return c.Render(http.StatusInternalServerError, "message",
makeMsgTpl("Invalid request", "",
"The subscriber ID is invalid."))
}
// Is export allowed? // Is export allowed?
if !app.Constants.Privacy.AllowExport { if !app.Constants.Privacy.AllowExport {
return c.Render(http.StatusBadRequest, "message", return c.Render(http.StatusBadRequest, "message",
@ -230,11 +204,6 @@ func handleWipeSubscriberData(c echo.Context) error {
app = c.Get("app").(*App) app = c.Get("app").(*App)
subUUID = c.Param("subUUID") subUUID = c.Param("subUUID")
) )
if !regexValidUUID.MatchString(subUUID) {
return c.Render(http.StatusInternalServerError, "message",
makeMsgTpl("Invalid request", "",
"The subscriber ID is invalid."))
}
// Is wiping allowed? // Is wiping allowed?
if !app.Constants.Privacy.AllowExport { if !app.Constants.Privacy.AllowExport {