types.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. package server
  2. import (
  3. "errors"
  4. "heckel.io/ntfy/util"
  5. "net/http"
  6. "strconv"
  7. "time"
  8. )
  9. // List of possible events
  10. const (
  11. openEvent = "open"
  12. keepaliveEvent = "keepalive"
  13. messageEvent = "message"
  14. pollRequestEvent = "poll_request"
  15. )
  16. const (
  17. messageIDLength = 12
  18. )
  19. // message represents a message published to a topic
  20. type message struct {
  21. ID string `json:"id"` // Random message ID
  22. Time int64 `json:"time"` // Unix time in seconds
  23. Updated int64 `json:"updated,omitempty"` // Set if updated, unix time in seconds
  24. Deleted int64 `json:"deleted,omitempty"` // Set if deleted, unix time in seconds
  25. Event string `json:"event"` // One of the above
  26. Topic string `json:"topic"`
  27. Priority int `json:"priority,omitempty"`
  28. Tags []string `json:"tags,omitempty"`
  29. Click string `json:"click,omitempty"`
  30. Attachment *attachment `json:"attachment,omitempty"`
  31. Title string `json:"title,omitempty"`
  32. Message string `json:"message,omitempty"`
  33. Encoding string `json:"encoding,omitempty"` // Empty for raw UTF-8, or "base64" for encoded bytes
  34. }
  35. type attachment struct {
  36. Name string `json:"name"`
  37. Type string `json:"type,omitempty"`
  38. Size int64 `json:"size,omitempty"`
  39. Expires int64 `json:"expires,omitempty"`
  40. URL string `json:"url"`
  41. Owner string `json:"-"` // IP address of uploader, used for rate limiting
  42. }
  43. // publishMessage is used as input when publishing as JSON
  44. type publishMessage struct {
  45. Topic string `json:"topic"`
  46. Title string `json:"title"`
  47. Message string `json:"message"`
  48. Priority int `json:"priority"`
  49. Tags []string `json:"tags"`
  50. Click string `json:"click"`
  51. Attach string `json:"attach"`
  52. Filename string `json:"filename"`
  53. }
  54. // messageEncoder is a function that knows how to encode a message
  55. type messageEncoder func(msg *message) (string, error)
  56. // newMessage creates a new message with the current timestamp
  57. func newMessage(event, topic, msg string) *message {
  58. return &message{
  59. ID: util.RandomString(messageIDLength),
  60. Time: time.Now().Unix(),
  61. Event: event,
  62. Topic: topic,
  63. Priority: 0,
  64. Tags: nil,
  65. Title: "",
  66. Message: msg,
  67. }
  68. }
  69. // newOpenMessage is a convenience method to create an open message
  70. func newOpenMessage(topic string) *message {
  71. return newMessage(openEvent, topic, "")
  72. }
  73. // newKeepaliveMessage is a convenience method to create a keepalive message
  74. func newKeepaliveMessage(topic string) *message {
  75. return newMessage(keepaliveEvent, topic, "")
  76. }
  77. // newDefaultMessage is a convenience method to create a notification message
  78. func newDefaultMessage(topic, msg string) *message {
  79. return newMessage(messageEvent, topic, msg)
  80. }
  81. func validMessageID(s string) bool {
  82. return util.ValidRandomString(s, messageIDLength)
  83. }
  84. func validUnixTimestamp(s string) bool {
  85. _, err := toUnixTimestamp(s)
  86. return err == nil
  87. }
  88. func toUnixTimestamp(s string) (int64, error) {
  89. u, err := strconv.ParseInt(s, 10, 64)
  90. if err != nil {
  91. return 0, err
  92. }
  93. if u < 1000000000 || u > 3000000000 { // I know. It's practical. So relax ...
  94. return 0, errors.New("invalid unix date")
  95. }
  96. return u, nil
  97. }
  98. type sinceMarker struct {
  99. time time.Time
  100. id string
  101. }
  102. func newSince(id string, timestamp int64) sinceMarker {
  103. return sinceMarker{time.Unix(timestamp, 0), id}
  104. }
  105. func newSinceTime(timestamp int64) sinceMarker {
  106. return sinceMarker{time.Unix(timestamp, 0), ""}
  107. }
  108. func newSinceID(id string) sinceMarker {
  109. return sinceMarker{time.Unix(0, 0), id}
  110. }
  111. func (t sinceMarker) IsAll() bool {
  112. return t == sinceAllMessages
  113. }
  114. func (t sinceMarker) IsNone() bool {
  115. return t == sinceNoMessages
  116. }
  117. func (t sinceMarker) IsID() bool {
  118. return t.id != ""
  119. }
  120. func (t sinceMarker) IsTime() bool {
  121. return t.time.Unix() > 0
  122. }
  123. func (t sinceMarker) Time() time.Time {
  124. return t.time
  125. }
  126. func (t sinceMarker) ID() string {
  127. return t.id
  128. }
  129. var (
  130. sinceAllMessages = sinceMarker{time.Unix(0, 0), ""}
  131. sinceNoMessages = sinceMarker{time.Unix(1, 0), ""}
  132. )
  133. type queryFilter struct {
  134. Message string
  135. Title string
  136. Tags []string
  137. Priority []int
  138. }
  139. func parseQueryFilters(r *http.Request) (*queryFilter, error) {
  140. messageFilter := readParam(r, "x-message", "message", "m")
  141. titleFilter := readParam(r, "x-title", "title", "t")
  142. tagsFilter := util.SplitNoEmpty(readParam(r, "x-tags", "tags", "tag", "ta"), ",")
  143. priorityFilter := make([]int, 0)
  144. for _, p := range util.SplitNoEmpty(readParam(r, "x-priority", "priority", "prio", "p"), ",") {
  145. priority, err := util.ParsePriority(p)
  146. if err != nil {
  147. return nil, err
  148. }
  149. priorityFilter = append(priorityFilter, priority)
  150. }
  151. return &queryFilter{
  152. Message: messageFilter,
  153. Title: titleFilter,
  154. Tags: tagsFilter,
  155. Priority: priorityFilter,
  156. }, nil
  157. }
  158. func (q *queryFilter) Pass(msg *message) bool {
  159. if msg.Event != messageEvent {
  160. return true // filters only apply to messages
  161. }
  162. if q.Message != "" && msg.Message != q.Message {
  163. return false
  164. }
  165. if q.Title != "" && msg.Title != q.Title {
  166. return false
  167. }
  168. messagePriority := msg.Priority
  169. if messagePriority == 0 {
  170. messagePriority = 3 // For query filters, default priority (3) is the same as "not set" (0)
  171. }
  172. if len(q.Priority) > 0 && !util.InIntList(q.Priority, messagePriority) {
  173. return false
  174. }
  175. if len(q.Tags) > 0 && !util.InStringListAll(msg.Tags, q.Tags) {
  176. return false
  177. }
  178. return true
  179. }