actions_parse.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. package server
  2. import (
  3. "errors"
  4. "fmt"
  5. "heckel.io/ntfy/util"
  6. "regexp"
  7. "strings"
  8. "unicode/utf8"
  9. )
  10. // Heavily inspired by https://go.dev/src/text/template/parse/lex.go
  11. // And thanks to Rob Pike (for Go, but also) for https://www.youtube.com/watch?v=HxaD_trXwRE
  12. // action=view, label="Look ma, commas and \"quotes\" too", url=https://..
  13. // "Look ma, a button",
  14. // Look ma a button
  15. // label=Look ma a=button
  16. // label="Look ma, a button"
  17. // "Look ma, \"quotes\""
  18. // label="Look ma, \"quotes\""
  19. // label=,
  20. func parseActionsFromSimpleNew(s string) ([]*action, error) {
  21. if !utf8.ValidString(s) {
  22. return nil, errors.New("invalid string")
  23. }
  24. parser := &actionParser{
  25. pos: 0,
  26. input: s,
  27. }
  28. return parser.Parse()
  29. }
  30. type actionParser struct {
  31. input string
  32. pos int
  33. }
  34. const eof = rune(0)
  35. func (p *actionParser) Parse() ([]*action, error) {
  36. println("------------------------")
  37. actions := make([]*action, 0)
  38. for !p.eof() {
  39. a, err := p.parseAction()
  40. if err != nil {
  41. return nil, err
  42. } else if a == nil {
  43. return actions, err
  44. }
  45. actions = append(actions, a)
  46. }
  47. return actions, nil
  48. }
  49. func (p *actionParser) parseAction() (*action, error) {
  50. println("parseAction")
  51. newAction := &action{
  52. Headers: make(map[string]string),
  53. Extras: make(map[string]string),
  54. }
  55. section := 0
  56. for {
  57. key, value, last, err := p.parseSection()
  58. fmt.Printf("--> key=%s, value=%s, last=%t, err=%#v\n", key, value, last, err)
  59. if err != nil {
  60. return nil, err
  61. } else if key == "" && section == 0 {
  62. key = "action"
  63. } else if key == "" && section == 1 {
  64. key = "label"
  65. } else if key == "" && section == 2 && util.InStringList([]string{"view", "http"}, newAction.Action) {
  66. key = "url"
  67. } else if key == "" {
  68. return nil, wrapErrHTTP(errHTTPBadRequestActionsInvalid, "term '%s' unknown", value)
  69. }
  70. if strings.HasPrefix(key, "headers.") {
  71. newAction.Headers[strings.TrimPrefix(key, "headers.")] = value
  72. } else if strings.HasPrefix(key, "extras.") {
  73. newAction.Extras[strings.TrimPrefix(key, "extras.")] = value
  74. } else {
  75. switch strings.ToLower(key) {
  76. case "action":
  77. newAction.Action = value
  78. case "label":
  79. newAction.Label = value
  80. case "clear":
  81. lvalue := strings.ToLower(value)
  82. if !util.InStringList([]string{"true", "yes", "1", "false", "no", "0"}, lvalue) {
  83. return nil, wrapErrHTTP(errHTTPBadRequestActionsInvalid, "'clear=%s' not allowed", value)
  84. }
  85. newAction.Clear = lvalue == "true" || lvalue == "yes" || lvalue == "1"
  86. case "url":
  87. newAction.URL = value
  88. case "method":
  89. newAction.Method = value
  90. case "body":
  91. newAction.Body = value
  92. default:
  93. return nil, wrapErrHTTP(errHTTPBadRequestActionsInvalid, "key '%s' unknown", key)
  94. }
  95. }
  96. p.slurpSpaces()
  97. if last {
  98. return newAction, nil
  99. }
  100. section++
  101. }
  102. }
  103. func (p *actionParser) parseSection() (key string, value string, last bool, err error) {
  104. fmt.Printf("parseSection, pos=%d, len(input)=%d, input[pos:]=%s\n", p.pos, len(p.input), p.input[p.pos:])
  105. p.slurpSpaces()
  106. key = p.parseKey()
  107. r, w := p.peek()
  108. if r == eof || r == ';' || r == ',' {
  109. p.pos += w
  110. last = r == ';' || r == eof
  111. return
  112. } else if r == '"' {
  113. value, last, err = p.parseQuotedValue()
  114. return
  115. }
  116. value, last = p.parseValue()
  117. return
  118. }
  119. func (p *actionParser) parseValue() (value string, last bool) {
  120. start := p.pos
  121. for {
  122. r, w := p.peek()
  123. if r == eof || r == ';' || r == ',' {
  124. last = r == ';' || r == eof
  125. value = p.input[start:p.pos]
  126. p.pos += w
  127. return
  128. }
  129. p.pos += w
  130. }
  131. }
  132. func (p *actionParser) parseQuotedValue() (value string, last bool, err error) {
  133. p.pos++
  134. start := p.pos
  135. var prev rune
  136. for {
  137. r, w := p.peek()
  138. if r == eof {
  139. err = errors.New("unexpected end of input")
  140. return
  141. } else if r == '"' && prev != '\\' {
  142. value = p.input[start:p.pos]
  143. p.pos += w
  144. // Advance until after "," or ";"
  145. p.slurpSpaces()
  146. r, w := p.peek()
  147. last = r == ';' || r == eof
  148. if r != eof && r != ';' && r != ',' {
  149. err = fmt.Errorf("unexpected character '%c' at position %d", r, p.pos)
  150. return
  151. }
  152. p.pos += w
  153. return
  154. }
  155. prev = r
  156. p.pos += w
  157. }
  158. }
  159. var keyRegex = regexp.MustCompile(`^[-.\w]+=`)
  160. func (p *actionParser) parseKey() string {
  161. key := keyRegex.FindString(p.input[p.pos:])
  162. if key != "" {
  163. p.pos += len(key)
  164. return key[:len(key)-1]
  165. }
  166. return key
  167. }
  168. func (p *actionParser) peek() (rune, int) {
  169. if p.pos >= len(p.input) {
  170. return eof, 0
  171. }
  172. return utf8.DecodeRuneInString(p.input[p.pos:])
  173. }
  174. func (p *actionParser) eof() bool {
  175. return p.pos >= len(p.input)
  176. }
  177. func (p *actionParser) slurpSpaces() {
  178. for {
  179. r, w := p.peek()
  180. if r == eof || !isSpace(r) {
  181. return
  182. }
  183. p.pos += w
  184. }
  185. }
  186. func isSpace(r rune) bool {
  187. return r == ' ' || r == '\t' || r == '\r' || r == '\n'
  188. }