123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 |
- 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)
- }
- }
|