package web import ( "net/http" "net/url" "regexp" "strconv" "strings" "github.com/aws/aws-sdk-go-v2/aws/arn" "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" "github.com/google/uuid" csrf "github.com/utrack/gin-csrf" "golang.org/x/exp/maps" "code.osinet.fr/fgm/sqs_demo/back/services/redriver" ) type ( // Level is an information level: danger | warning. Level string // QueueOp is an allowed operation or reserved error: delete|redrive|invalid. QueueOp string ) const ( LevelDanger Level = "danger" LevelInfo Level = "info" LevelWarning Level = "warning" OpDelete QueueOp = "delete" OpInvalid QueueOp = "invalid" OpPurge QueueOp = "purge" OpRedrive QueueOp = "redrive" ) var confirms = map[QueueOp]struct { question, description string confirm string Level // danger|warning }{ OpDelete: { confirm: "Clear", description: "These messages cannot be recovered after that step", question: "Do you confirm this deletion request?", Level: LevelDanger, }, OpInvalid: { description: "The operation requested is invalid", Level: LevelInfo, }, OpPurge: { confirm: "Purge", description: "All the messages in the deadletter queue will be lost after that step", question: "Do you confirm this purge request?", Level: LevelDanger, }, OpRedrive: { confirm: "Redrive", description: "These messages will be in their source queue after that step", question: "Do you confirm this redriving request?", Level: LevelWarning, }, } // getOp extracts the operation from the unsafe form data, returning a safe value. func getOp(u *url.URL) QueueOp { q := u.Query() found := 0 var res QueueOp for key := range q { switch op := QueueOp(strings.ToLower(key)); op { case OpDelete, OpPurge, OpRedrive: res = op found++ default: continue } } if found != 1 { return OpInvalid } return res } type IDValidator func(in string) bool // validateUint is an IDValidator for unsigned integer strings. func validateUint(in string) bool { _, err := strconv.Atoi(in) return err == nil } // validateUUID is an IDValidator for UUID strings func validateUUID(in string) bool { _, err := uuid.Parse(in) return err == nil } // validateARN is an IDValidator for ARN strings func validateARN(in string) bool { return arn.IsARN(in) } func parseIDs(v url.Values, isValid IDValidator) []string { const prefix = "id-" var ids []string for k, vs := range v { // Weed out bad keys, bad value lengths, and non-matching checkbox statuses. if pos := strings.Index(k, prefix); pos == -1 || len(vs) != 1 || vs[0] != "on" { continue } b, a, found := strings.Cut(k, prefix) if b == "" && found && isValid(a) { ids = append(ids, a) } } return ids } func parseMessages(v url.Values, ids []string) []redriver.Message { m := make(map[int]redriver.Message, len(ids)) keyRx := regexp.MustCompile("^(id|mid|rh)-(.+)") mOK := make(map[int]bool, len(ids)) for k, vs := range v { ms := keyRx.FindStringSubmatch(k) if len(ms) != 3 || len(vs) != 1 { continue } v := vs[0] id, err := strconv.Atoi(ms[2]) if err != nil { continue } message := m[id] switch ms[1] { case "id": if v == "on" { mOK[id] = true } case "rh": message.ReceiptHandle = v case "mid": message.MessageId = v } m[id] = message } // Only return selected messages. res := make(map[int]redriver.Message, len(mOK)) for id := range mOK { res[id] = m[id] } return maps.Values(res) } func makeConfirmHandler() gin.HandlerFunc { return func(c *gin.Context) { qName := c.Param("name") u := c.Request.URL sess := sessions.Default(c) flashes := sess.Flashes() defer func() { _ = sess.Save() }() op := getOp(u) ids := parseIDs(u.Query(), validateUint) var messages []redriver.Message if len(ids) == 0 && op != OpPurge { op = OpInvalid messages = nil } else { messages = parseMessages(u.Query(), ids) } token := csrf.GetToken(c) data := confirms[op] // op is guaranteed to be valid by getOp(). h := gin.H{ "action": "/queue/" + qName + "/" + string(op), "cancel": "Cancel", "cancelURL": "/queue/" + qName, "confirm": data.confirm, "csrf": token, "description": data.description, "flashes": flashes, "level": data.Level, "messages": messages, "question": data.question, } if qName == "" { h["cancelURL"] = "/" } if op == OpInvalid { h["messages"] = nil } c.HTML(http.StatusOK, "confirm", h) } }