Disallow HEAD/GET requests with body
This commit is contained in:
		
							parent
							
								
									12f85cceb1
								
							
						
					
					
						commit
						b805d49cfd
					
				
					 3 changed files with 24 additions and 11 deletions
				
			
		|  | @ -2,6 +2,7 @@ package server | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
|  | 	"fmt" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -22,6 +23,15 @@ func (e errHTTP) JSON() string { | ||||||
| 	return string(b) | 	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 ( | var ( | ||||||
| 	errHTTPBadRequestEmailDisabled                   = &errHTTP{40001, http.StatusBadRequest, "e-mail notifications are not enabled", "https://ntfy.sh/docs/config/#e-mail-notifications"} | 	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", ""} | 	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"} | 	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"} | 	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"} | 	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", ""} | 	errHTTPNotFound                                  = &errHTTP{40401, http.StatusNotFound, "page not found", ""} | ||||||
| 	errHTTPUnauthorized                              = &errHTTP{40101, http.StatusUnauthorized, "unauthorized", "https://ntfy.sh/docs/publish/#authentication"} | 	errHTTPUnauthorized                              = &errHTTP{40101, http.StatusUnauthorized, "unauthorized", "https://ntfy.sh/docs/publish/#authentication"} | ||||||
| 	errHTTPForbidden                                 = &errHTTP{40301, http.StatusForbidden, "forbidden", "https://ntfy.sh/docs/publish/#authentication"} | 	errHTTPForbidden                                 = &errHTTP{40301, http.StatusForbidden, "forbidden", "https://ntfy.sh/docs/publish/#authentication"} | ||||||
|  |  | ||||||
|  | @ -539,7 +539,7 @@ func (s *Server) parsePublishParams(r *http.Request, v *visitor, m *message) (ca | ||||||
| 	if actionsStr != "" { | 	if actionsStr != "" { | ||||||
| 		m.Actions, err = parseActions(actionsStr) | 		m.Actions, err = parseActions(actionsStr) | ||||||
| 		if err != nil { | 		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! | 	unifiedpush = readBoolParam(r, false, "x-unifiedpush", "unifiedpush", "up") // see GET too! | ||||||
|  |  | ||||||
|  | @ -2,7 +2,6 @@ package server | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"fmt" |  | ||||||
| 	"heckel.io/ntfy/util" | 	"heckel.io/ntfy/util" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"strings" | 	"strings" | ||||||
|  | @ -61,22 +60,26 @@ func parseActions(s string) (actions []*action, err error) { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Add ID field | 	// Add ID field, ensure correct uppercase/lowercase | ||||||
| 	for i := range actions { | 	for i := range actions { | ||||||
| 		actions[i].ID = util.RandomString(actionIDLength) | 		actions[i].ID = util.RandomString(actionIDLength) | ||||||
|  | 		actions[i].Action = strings.ToLower(actions[i].Action) | ||||||
|  | 		actions[i].Method = strings.ToUpper(actions[i].Method) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Validate | 	// Validate | ||||||
| 	if len(actions) > actionsMax { | 	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 { | 	for _, action := range actions { | ||||||
| 		if !util.InStringList([]string{"view", "broadcast", "http"}, action.Action) { | 		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 == "" { | 		} 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 == "" { | 		} 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, ",") | 		parts := util.SplitNoEmpty(rawAction, ",") | ||||||
| 		if len(parts) < 3 { | 		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 { | 		for i, part := range parts { | ||||||
| 			key, value := util.SplitKV(part, "=") | 			key, value := util.SplitKV(part, "=") | ||||||
|  | @ -131,10 +134,10 @@ func parseActionsFromSimple(s string) ([]*action, error) { | ||||||
| 				case "body": | 				case "body": | ||||||
| 					newAction.Body = value | 					newAction.Body = value | ||||||
| 				default: | 				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 { | 			} else { | ||||||
| 				return nil, fmt.Errorf("cannot parse action: unknown phrase '%s'", part) | 				return nil, wrapErrHTTP(errHTTPBadRequestActionsInvalid, "unknown term '%s'", part) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		actions = append(actions, newAction) | 		actions = append(actions, newAction) | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue