2022-01-16 04:17:46 +00:00
|
|
|
package server
|
|
|
|
|
|
|
|
import (
|
2022-04-19 13:14:32 +00:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"heckel.io/ntfy/util"
|
2022-01-16 04:17:46 +00:00
|
|
|
"net/http"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
2022-04-20 20:31:25 +00:00
|
|
|
const (
|
|
|
|
actionIDLength = 10
|
|
|
|
actionsMax = 3
|
|
|
|
)
|
|
|
|
|
2022-01-16 04:17:46 +00:00
|
|
|
func readBoolParam(r *http.Request, defaultValue bool, names ...string) bool {
|
|
|
|
value := strings.ToLower(readParam(r, names...))
|
|
|
|
if value == "" {
|
|
|
|
return defaultValue
|
|
|
|
}
|
|
|
|
return value == "1" || value == "yes" || value == "true"
|
|
|
|
}
|
|
|
|
|
|
|
|
func readParam(r *http.Request, names ...string) string {
|
2022-02-26 04:25:04 +00:00
|
|
|
value := readHeaderParam(r, names...)
|
|
|
|
if value != "" {
|
|
|
|
return value
|
|
|
|
}
|
|
|
|
return readQueryParam(r, names...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func readHeaderParam(r *http.Request, names ...string) string {
|
2022-01-16 04:17:46 +00:00
|
|
|
for _, name := range names {
|
|
|
|
value := r.Header.Get(name)
|
|
|
|
if value != "" {
|
|
|
|
return strings.TrimSpace(value)
|
|
|
|
}
|
|
|
|
}
|
2022-02-26 04:25:04 +00:00
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
func readQueryParam(r *http.Request, names ...string) string {
|
2022-01-16 04:17:46 +00:00
|
|
|
for _, name := range names {
|
|
|
|
value := r.URL.Query().Get(strings.ToLower(name))
|
|
|
|
if value != "" {
|
|
|
|
return strings.TrimSpace(value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
2022-04-19 13:14:32 +00:00
|
|
|
|
|
|
|
func parseActions(s string) (actions []*action, err error) {
|
2022-04-20 03:26:46 +00:00
|
|
|
// Parse JSON or simple format
|
2022-04-19 13:14:32 +00:00
|
|
|
s = strings.TrimSpace(s)
|
|
|
|
if strings.HasPrefix(s, "[") {
|
|
|
|
actions, err = parseActionsFromJSON(s)
|
|
|
|
} else {
|
|
|
|
actions, err = parseActionsFromSimple(s)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-04-20 03:26:46 +00:00
|
|
|
|
|
|
|
// Add ID field
|
2022-04-19 13:14:32 +00:00
|
|
|
for i := range actions {
|
|
|
|
actions[i].ID = util.RandomString(actionIDLength)
|
2022-04-20 03:26:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Validate
|
2022-04-20 20:31:25 +00:00
|
|
|
if len(actions) > actionsMax {
|
|
|
|
return nil, fmt.Errorf("too many actions, only %d allowed", actionsMax)
|
|
|
|
}
|
2022-04-20 03:26:46 +00:00
|
|
|
for _, action := range actions {
|
|
|
|
if !util.InStringList([]string{"view", "broadcast", "http"}, action.Action) {
|
|
|
|
return nil, fmt.Errorf("cannot parse actions: action '%s' unknown", action.Action)
|
|
|
|
} else if action.Label == "" {
|
2022-04-19 13:14:32 +00:00
|
|
|
return nil, fmt.Errorf("cannot parse actions: label must be set")
|
2022-04-20 20:31:25 +00:00
|
|
|
} else if util.InStringList([]string{"view", "http"}, action.Action) && action.URL == "" {
|
2022-04-20 03:26:46 +00:00
|
|
|
return nil, fmt.Errorf("parameter 'url' is required for action '%s'", action.Action)
|
2022-04-19 13:14:32 +00:00
|
|
|
}
|
|
|
|
}
|
2022-04-20 03:26:46 +00:00
|
|
|
|
2022-04-19 13:14:32 +00:00
|
|
|
return actions, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseActionsFromJSON(s string) ([]*action, error) {
|
|
|
|
actions := make([]*action, 0)
|
|
|
|
if err := json.Unmarshal([]byte(s), &actions); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return actions, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseActionsFromSimple(s string) ([]*action, error) {
|
|
|
|
actions := make([]*action, 0)
|
|
|
|
rawActions := util.SplitNoEmpty(s, ";")
|
|
|
|
for _, rawAction := range rawActions {
|
2022-04-21 13:58:28 +00:00
|
|
|
newAction := &action{
|
|
|
|
Headers: make(map[string]string),
|
|
|
|
Extras: make(map[string]string),
|
|
|
|
}
|
2022-04-19 13:14:32 +00:00
|
|
|
parts := util.SplitNoEmpty(rawAction, ",")
|
|
|
|
if len(parts) < 3 {
|
|
|
|
return nil, fmt.Errorf("cannot parse action: action requires at least keys 'action', 'label' and one parameter: %s", rawAction)
|
|
|
|
}
|
|
|
|
for i, part := range parts {
|
|
|
|
key, value := util.SplitKV(part, "=")
|
|
|
|
if key == "" && i == 0 {
|
|
|
|
newAction.Action = value
|
|
|
|
} else if key == "" && i == 1 {
|
|
|
|
newAction.Label = value
|
2022-04-20 20:31:25 +00:00
|
|
|
} else if key == "" && util.InStringList([]string{"view", "http"}, newAction.Action) && i == 2 {
|
|
|
|
newAction.URL = value
|
2022-04-21 13:58:28 +00:00
|
|
|
} else if strings.HasPrefix(key, "headers.") {
|
|
|
|
newAction.Headers[strings.TrimPrefix(key, "headers.")] = value
|
|
|
|
} else if strings.HasPrefix(key, "extras.") {
|
|
|
|
newAction.Extras[strings.TrimPrefix(key, "extras.")] = value
|
2022-04-19 13:14:32 +00:00
|
|
|
} else if key != "" {
|
|
|
|
switch strings.ToLower(key) {
|
|
|
|
case "action":
|
|
|
|
newAction.Action = value
|
|
|
|
case "label":
|
|
|
|
newAction.Label = value
|
|
|
|
case "url":
|
|
|
|
newAction.URL = value
|
|
|
|
case "method":
|
|
|
|
newAction.Method = value
|
|
|
|
case "body":
|
|
|
|
newAction.Body = value
|
|
|
|
default:
|
2022-04-21 13:58:28 +00:00
|
|
|
return nil, fmt.Errorf("cannot parse action: key '%s' not supported, please use JSON format instead", part)
|
2022-04-19 13:14:32 +00:00
|
|
|
}
|
|
|
|
} else {
|
2022-04-21 13:58:28 +00:00
|
|
|
return nil, fmt.Errorf("cannot parse action: unknown phrase '%s'", part)
|
2022-04-19 13:14:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
actions = append(actions, newAction)
|
|
|
|
}
|
|
|
|
return actions, nil
|
|
|
|
}
|