Procházet zdrojové kódy

Disallow HEAD/GET requests with body

Philipp Heckel před 3 roky
rodič
revize
b805d49cfd
3 změnil soubory, kde provedl 24 přidání a 11 odebrání
  1. 11 1
      server/errors.go
  2. 1 1
      server/server.go
  3. 12 9
      server/util.go

+ 11 - 1
server/errors.go

@@ -2,6 +2,7 @@ package server
 
 import (
 	"encoding/json"
+	"fmt"
 	"net/http"
 )
 
@@ -22,6 +23,15 @@ func (e errHTTP) JSON() string {
 	return string(b)
 }
 
+func wrapErrHTTP(err *errHTTP, message string, args ...interface{}) *errHTTP {
+	return &errHTTP{
+		Code:     err.Code,
+		HTTPCode: err.HTTPCode,
+		Message:  fmt.Sprintf("%s, %s", err.Message, fmt.Sprintf(message, args...)),
+		Link:     err.Link,
+	}
+}
+
 var (
 	errHTTPBadRequestEmailDisabled                   = &errHTTP{40001, http.StatusBadRequest, "e-mail notifications are not enabled", "https://ntfy.sh/docs/config/#e-mail-notifications"}
 	errHTTPBadRequestDelayNoCache                    = &errHTTP{40002, http.StatusBadRequest, "cannot disable cache for delayed message", ""}
@@ -39,7 +49,7 @@ var (
 	errHTTPBadRequestAttachmentsExpiryBeforeDelivery = &errHTTP{40015, http.StatusBadRequest, "invalid request: attachment expiry before delayed delivery date", "https://ntfy.sh/docs/publish/#scheduled-delivery"}
 	errHTTPBadRequestWebSocketsUpgradeHeaderMissing  = &errHTTP{40016, http.StatusBadRequest, "invalid request: client not using the websocket protocol", "https://ntfy.sh/docs/subscribe/api/#websockets"}
 	errHTTPBadRequestJSONInvalid                     = &errHTTP{40017, http.StatusBadRequest, "invalid request: request body must be message JSON", "https://ntfy.sh/docs/publish/#publish-as-json"}
-	errHTTPBadRequestActionsInvalid                  = &errHTTP{40018, http.StatusBadRequest, "invalid request: actions not valid", "https://ntfy.sh/docs/publish/#action-buttons"}
+	errHTTPBadRequestActionsInvalid                  = &errHTTP{40018, http.StatusBadRequest, "invalid request: actions invalid", "https://ntfy.sh/docs/publish/#action-buttons"}
 	errHTTPNotFound                                  = &errHTTP{40401, http.StatusNotFound, "page not found", ""}
 	errHTTPUnauthorized                              = &errHTTP{40101, http.StatusUnauthorized, "unauthorized", "https://ntfy.sh/docs/publish/#authentication"}
 	errHTTPForbidden                                 = &errHTTP{40301, http.StatusForbidden, "forbidden", "https://ntfy.sh/docs/publish/#authentication"}

+ 1 - 1
server/server.go

@@ -539,7 +539,7 @@ func (s *Server) parsePublishParams(r *http.Request, v *visitor, m *message) (ca
 	if actionsStr != "" {
 		m.Actions, err = parseActions(actionsStr)
 		if err != nil {
-			return false, false, "", false, errHTTPBadRequestActionsInvalid
+			return false, false, "", false, err // wrapped errHTTPBadRequestActionsInvalid
 		}
 	}
 	unifiedpush = readBoolParam(r, false, "x-unifiedpush", "unifiedpush", "up") // see GET too!

+ 12 - 9
server/util.go

@@ -2,7 +2,6 @@ package server
 
 import (
 	"encoding/json"
-	"fmt"
 	"heckel.io/ntfy/util"
 	"net/http"
 	"strings"
@@ -61,22 +60,26 @@ func parseActions(s string) (actions []*action, err error) {
 		return nil, err
 	}
 
-	// Add ID field
+	// Add ID field, ensure correct uppercase/lowercase
 	for i := range actions {
 		actions[i].ID = util.RandomString(actionIDLength)
+		actions[i].Action = strings.ToLower(actions[i].Action)
+		actions[i].Method = strings.ToUpper(actions[i].Method)
 	}
 
 	// Validate
 	if len(actions) > actionsMax {
-		return nil, fmt.Errorf("too many actions, only %d allowed", actionsMax)
+		return nil, wrapErrHTTP(errHTTPBadRequestActionsInvalid, "only %d actions allowed", actionsMax)
 	}
 	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)
+			return nil, wrapErrHTTP(errHTTPBadRequestActionsInvalid, "action '%s' unknown", action.Action)
 		} else if action.Label == "" {
-			return nil, fmt.Errorf("cannot parse actions: label must be set")
+			return nil, wrapErrHTTP(errHTTPBadRequestActionsInvalid, "parameter 'label' is required")
 		} else if util.InStringList([]string{"view", "http"}, action.Action) && action.URL == "" {
-			return nil, fmt.Errorf("parameter 'url' is required for action '%s'", action.Action)
+			return nil, wrapErrHTTP(errHTTPBadRequestActionsInvalid, "parameter 'url' is required for action '%s'", action.Action)
+		} else if action.Action == "http" && util.InStringList([]string{"GET", "HEAD"}, action.Method) && action.Body != "" {
+			return nil, wrapErrHTTP(errHTTPBadRequestActionsInvalid, "parameter 'body' cannot be set if method is %s", action.Method)
 		}
 	}
 
@@ -101,7 +104,7 @@ func parseActionsFromSimple(s string) ([]*action, error) {
 		}
 		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)
+			return nil, wrapErrHTTP(errHTTPBadRequestActionsInvalid, "action requires at least keys 'action', 'label' and one parameter: %s", rawAction)
 		}
 		for i, part := range parts {
 			key, value := util.SplitKV(part, "=")
@@ -131,10 +134,10 @@ func parseActionsFromSimple(s string) ([]*action, error) {
 				case "body":
 					newAction.Body = value
 				default:
-					return nil, fmt.Errorf("cannot parse action: key '%s' not supported, please use JSON format instead", part)
+					return nil, wrapErrHTTP(errHTTPBadRequestActionsInvalid, "key '%s' unknown", key)
 				}
 			} else {
-				return nil, fmt.Errorf("cannot parse action: unknown phrase '%s'", part)
+				return nil, wrapErrHTTP(errHTTPBadRequestActionsInvalid, "unknown term '%s'", part)
 			}
 		}
 		actions = append(actions, newAction)