From 0b57ae723e09b0bbc3d36ee9bf17853ec8e41898 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Mon, 22 Jun 2026 17:41:44 +0800 Subject: [PATCH] fix: correct webhook Content-Type validation --- .../deployer/providers/webhook/webhook.go | 46 ++++++++++++------- .../notifier/providers/webhook/webhook.go | 46 ++++++++++++------- 2 files changed, 58 insertions(+), 34 deletions(-) diff --git a/pkg/core/deployer/providers/webhook/webhook.go b/pkg/core/deployer/providers/webhook/webhook.go index 2437cb756..0646e7d64 100644 --- a/pkg/core/deployer/providers/webhook/webhook.go +++ b/pkg/core/deployer/providers/webhook/webhook.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "log/slog" + "mime" "net/http" "net/url" "strings" @@ -48,6 +49,26 @@ type Deployer struct { var _ Provider = (*Deployer)(nil) +const ( + contentTypeJson = "application/json" + contentTypeForm = "application/x-www-form-urlencoded" + contentTypeMultipart = "multipart/form-data" +) + +var allowedContentTypes = map[string]bool{ + contentTypeJson: true, + contentTypeForm: true, + contentTypeMultipart: true, +} + +var allowedMethods = map[string]bool{ + http.MethodGet: true, + http.MethodPost: true, + http.MethodPut: true, + http.MethodPatch: true, + http.MethodDelete: true, +} + func NewDeployer(config *DeployerConfig) (*Deployer, error) { if config == nil { return nil, fmt.Errorf("the configuration of the deployer provider is nil") @@ -104,11 +125,7 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*Dep webhookMethod := strings.ToUpper(d.config.Method) if webhookMethod == "" { webhookMethod = http.MethodPost - } else if webhookMethod != http.MethodGet && - webhookMethod != http.MethodPost && - webhookMethod != http.MethodPut && - webhookMethod != http.MethodPatch && - webhookMethod != http.MethodDelete { + } else if !allowedMethods[webhookMethod] { return nil, fmt.Errorf("unsupported webhook request method '%s'", webhookMethod) } @@ -119,16 +136,11 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*Dep } // 处理 Webhook 请求内容类型 - const CONTENT_TYPE_JSON = "application/json" - const CONTENT_TYPE_FORM = "application/x-www-form-urlencoded" - const CONTENT_TYPE_MULTIPART = "multipart/form-data" webhookContentType := webhookHeaders.Get("Content-Type") if webhookContentType == "" { - webhookContentType = CONTENT_TYPE_JSON - webhookHeaders.Set("Content-Type", CONTENT_TYPE_JSON) - } else if strings.HasPrefix(webhookContentType, CONTENT_TYPE_JSON) && - strings.HasPrefix(webhookContentType, CONTENT_TYPE_FORM) && - strings.HasPrefix(webhookContentType, CONTENT_TYPE_MULTIPART) { + webhookContentType = contentTypeJson + webhookHeaders.Set("Content-Type", contentTypeJson) + } else if mediaType, _, err := mime.ParseMediaType(webhookContentType); err != nil || !allowedContentTypes[mediaType] { return nil, fmt.Errorf("unsupported webhook content type '%s'", webhookContentType) } @@ -146,7 +158,7 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*Dep return nil, fmt.Errorf("failed to unmarshal webhook data: %w", err) } - if webhookMethod == http.MethodGet || webhookContentType == CONTENT_TYPE_FORM || webhookContentType == CONTENT_TYPE_MULTIPART { + if webhookMethod == http.MethodGet || webhookContentType == contentTypeForm || webhookContentType == contentTypeMultipart { temp := make(map[string]string) jsonb, err := json.Marshal(webhookData) if err != nil { @@ -186,11 +198,11 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*Dep req.SetQueryParams(webhookData.(map[string]string)) } else { switch webhookContentType { - case CONTENT_TYPE_JSON: + case contentTypeJson: req.SetBody(webhookData) - case CONTENT_TYPE_FORM: + case contentTypeForm: req.SetFormData(webhookData.(map[string]string)) - case CONTENT_TYPE_MULTIPART: + case contentTypeMultipart: req.SetMultipartFormData(webhookData.(map[string]string)) } } diff --git a/pkg/core/notifier/providers/webhook/webhook.go b/pkg/core/notifier/providers/webhook/webhook.go index 0d5b1aa2e..69f42368f 100644 --- a/pkg/core/notifier/providers/webhook/webhook.go +++ b/pkg/core/notifier/providers/webhook/webhook.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "log/slog" + "mime" "net/http" "net/url" "strings" @@ -46,6 +47,26 @@ type Notifier struct { var _ Provider = (*Notifier)(nil) +const ( + contentTypeJson = "application/json" + contentTypeForm = "application/x-www-form-urlencoded" + contentTypeMultipart = "multipart/form-data" +) + +var allowedContentTypes = map[string]bool{ + contentTypeJson: true, + contentTypeForm: true, + contentTypeMultipart: true, +} + +var allowedMethods = map[string]bool{ + http.MethodGet: true, + http.MethodPost: true, + http.MethodPut: true, + http.MethodPatch: true, + http.MethodDelete: true, +} + func NewNotifier(config *NotifierConfig) (*Notifier, error) { if config == nil { return nil, fmt.Errorf("the configuration of the notifier provider is nil") @@ -90,11 +111,7 @@ func (n *Notifier) Notify(ctx context.Context, subject string, message string) ( webhookMethod := strings.ToUpper(n.config.Method) if webhookMethod == "" { webhookMethod = http.MethodPost - } else if webhookMethod != http.MethodGet && - webhookMethod != http.MethodPost && - webhookMethod != http.MethodPut && - webhookMethod != http.MethodPatch && - webhookMethod != http.MethodDelete { + } else if !allowedMethods[webhookMethod] { return nil, fmt.Errorf("unsupported webhook request method '%s'", webhookMethod) } @@ -105,16 +122,11 @@ func (n *Notifier) Notify(ctx context.Context, subject string, message string) ( } // 处理 Webhook 请求内容类型 - const CONTENT_TYPE_JSON = "application/json" - const CONTENT_TYPE_FORM = "application/x-www-form-urlencoded" - const CONTENT_TYPE_MULTIPART = "multipart/form-data" webhookContentType := webhookHeaders.Get("Content-Type") if webhookContentType == "" { - webhookContentType = CONTENT_TYPE_JSON - webhookHeaders.Set("Content-Type", CONTENT_TYPE_JSON) - } else if strings.HasPrefix(webhookContentType, CONTENT_TYPE_JSON) && - strings.HasPrefix(webhookContentType, CONTENT_TYPE_FORM) && - strings.HasPrefix(webhookContentType, CONTENT_TYPE_MULTIPART) { + webhookContentType = contentTypeJson + webhookHeaders.Set("Content-Type", contentTypeJson) + } else if mediaType, _, err := mime.ParseMediaType(webhookContentType); err != nil || !allowedContentTypes[mediaType] { return nil, fmt.Errorf("unsupported webhook content type '%s'", webhookContentType) } @@ -131,7 +143,7 @@ func (n *Notifier) Notify(ctx context.Context, subject string, message string) ( return nil, fmt.Errorf("failed to unmarshal webhook data: %w", err) } - if webhookMethod == http.MethodGet || webhookContentType == CONTENT_TYPE_FORM || webhookContentType == CONTENT_TYPE_MULTIPART { + if webhookMethod == http.MethodGet || webhookContentType == contentTypeForm || webhookContentType == contentTypeMultipart { temp := make(map[string]string) jsonb, err := json.Marshal(webhookData) if err != nil { @@ -161,11 +173,11 @@ func (n *Notifier) Notify(ctx context.Context, subject string, message string) ( req.SetQueryParams(webhookData.(map[string]string)) } else { switch webhookContentType { - case CONTENT_TYPE_JSON: + case contentTypeJson: req.SetBody(webhookData) - case CONTENT_TYPE_FORM: + case contentTypeForm: req.SetFormData(webhookData.(map[string]string)) - case CONTENT_TYPE_MULTIPART: + case contentTypeMultipart: req.SetMultipartFormData(webhookData.(map[string]string)) } }