mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-15 16:00:43 +00:00
181 lines
5.1 KiB
Go
181 lines
5.1 KiB
Go
package webhook_sender
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type responseRejection struct {
|
|
statusCode int
|
|
reason string
|
|
}
|
|
|
|
func DiagnosticText(value string, input Request) string {
|
|
return captureResponse([]byte(value), input, preparedTarget{})
|
|
}
|
|
|
|
func (e *responseRejection) Error() string {
|
|
return fmt.Sprintf("webhook provider rejected response (HTTP %d)", e.statusCode)
|
|
}
|
|
|
|
func validateResponse(preset Preset, statusCode int, body []byte) error {
|
|
if preset == "" {
|
|
preset = PresetGeneric
|
|
}
|
|
switch preset {
|
|
case PresetGeneric, PresetDiscord, PresetTeams, PresetSlack:
|
|
default:
|
|
return errors.New("unsupported webhook preset")
|
|
}
|
|
|
|
reason, failed := businessResponseFailure(body)
|
|
if preset == PresetSlack && reason == "" {
|
|
switch value := strings.TrimSpace(string(body)); value {
|
|
case "invalid_payload", "no_text", "invalid_token", "no_service", "no_service_id",
|
|
"no_active_hooks", "channel_not_found", "channel_is_archived",
|
|
"action_prohibited", "posting_to_general_channel_denied",
|
|
"team_disabled", "user_not_found", "too_many_attachments", "rollup_error":
|
|
reason = value
|
|
}
|
|
}
|
|
if statusCode < http.StatusOK || statusCode >= http.StatusMultipleChoices {
|
|
return &responseRejection{statusCode: statusCode, reason: reason}
|
|
}
|
|
if preset == PresetSlack {
|
|
if statusCode != http.StatusOK || strings.TrimSpace(string(body)) != "ok" {
|
|
return &responseRejection{statusCode: statusCode, reason: reason}
|
|
}
|
|
return nil
|
|
}
|
|
if failed {
|
|
return &responseRejection{statusCode: statusCode, reason: reason}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func businessResponseFailure(body []byte) (string, bool) {
|
|
var response map[string]json.RawMessage
|
|
if err := json.Unmarshal(body, &response); err != nil || response == nil {
|
|
return "", false
|
|
}
|
|
message := responseMessage(response)
|
|
if message == "" {
|
|
var errorText string
|
|
if json.Unmarshal(response["error"], &errorText) == nil {
|
|
message = strings.TrimSpace(errorText)
|
|
} else {
|
|
var nested map[string]json.RawMessage
|
|
if json.Unmarshal(response["error"], &nested) == nil {
|
|
message = responseMessage(nested)
|
|
}
|
|
}
|
|
}
|
|
for _, key := range []string{"errcode", "errCode", "error_code", "ErrorCode"} {
|
|
if raw, exists := response[key]; exists {
|
|
code, valid := responseCode(raw)
|
|
if !valid || !isSuccessResponseCode(code, false) {
|
|
return responseFailureReason(key, code, message), true
|
|
}
|
|
}
|
|
}
|
|
for _, key := range []string{"success", "ok"} {
|
|
var success bool
|
|
if raw, exists := response[key]; exists && string(raw) != "null" && json.Unmarshal(raw, &success) == nil && !success {
|
|
return responseFailureReason(key, "false", message), true
|
|
}
|
|
}
|
|
if raw, exists := response["error"]; exists {
|
|
var value any
|
|
if json.Unmarshal(raw, &value) == nil {
|
|
switch typed := value.(type) {
|
|
case string:
|
|
if strings.TrimSpace(typed) != "" {
|
|
return responseFailureReason("error", "", typed), true
|
|
}
|
|
case map[string]any:
|
|
if len(typed) != 0 {
|
|
return responseFailureReason("error", "", message), true
|
|
}
|
|
case bool:
|
|
if typed {
|
|
return responseFailureReason("error", "true", message), true
|
|
}
|
|
case float64:
|
|
if typed != 0 {
|
|
return responseFailureReason("error", strconv.FormatFloat(typed, 'g', -1, 64), message), true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if message != "" {
|
|
for _, key := range []string{"code", "Code", "StatusCode", "statusCode"} {
|
|
if raw, exists := response[key]; exists {
|
|
code, valid := responseCode(raw)
|
|
if !valid || !isSuccessResponseCode(code, true) {
|
|
return responseFailureReason(key, code, message), true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return message, false
|
|
}
|
|
|
|
func responseMessage(response map[string]json.RawMessage) string {
|
|
for _, key := range []string{"errmsg", "msg", "message", "description", "StatusMessage", "Message"} {
|
|
var message string
|
|
if json.Unmarshal(response[key], &message) == nil && strings.TrimSpace(message) != "" {
|
|
return strings.TrimSpace(message)
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func responseCode(raw json.RawMessage) (string, bool) {
|
|
var code json.Number
|
|
if json.Unmarshal(raw, &code) == nil && code != "" {
|
|
return code.String(), true
|
|
}
|
|
var text string
|
|
if json.Unmarshal(raw, &text) == nil && strings.TrimSpace(text) != "" {
|
|
return strings.TrimSpace(text), true
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
func isSuccessResponseCode(code string, allowHTTP bool) bool {
|
|
number, err := strconv.ParseFloat(code, 64)
|
|
if err == nil {
|
|
return number == 0 || allowHTTP && number >= 200 && number < 300
|
|
}
|
|
return allowHTTP && (strings.EqualFold(code, "ok") || strings.EqualFold(code, "success"))
|
|
}
|
|
|
|
func responseFailureReason(field, code, message string) string {
|
|
reason := field
|
|
if code != "" {
|
|
reason += "=" + code
|
|
}
|
|
if message != "" {
|
|
reason += ": " + message
|
|
}
|
|
return reason
|
|
}
|
|
|
|
func safeResponseError(err error, input Request, target preparedTarget) error {
|
|
var rejection *responseRejection
|
|
if !input.CaptureResponse || !errors.As(err, &rejection) || rejection.reason == "" {
|
|
return err
|
|
}
|
|
reason := captureResponse([]byte(rejection.reason), input, target)
|
|
reason = strings.Join(strings.Fields(reason), " ")
|
|
if reason == "" {
|
|
return err
|
|
}
|
|
return fmt.Errorf("%w: %s", rejection, truncateUTF8(reason, 512))
|
|
}
|