From 4ba64a576bb2ed05c2c05d491ac85df448970e1a Mon Sep 17 00:00:00 2001 From: Seefs Date: Sat, 24 Jan 2026 23:22:35 +0800 Subject: [PATCH 1/2] feat: header passthrough --- relay/channel/api_request.go | 59 ++++++++++++++++++++++++++++++------ 1 file changed, 49 insertions(+), 10 deletions(-) diff --git a/relay/channel/api_request.go b/relay/channel/api_request.go index 1ff1e2392c5b..2399fa198434 100644 --- a/relay/channel/api_request.go +++ b/relay/channel/api_request.go @@ -38,9 +38,45 @@ func SetupApiRequestHeader(info *common.RelayInfo, c *gin.Context, req *http.Hea } } -// processHeaderOverride 处理请求头覆盖,支持变量替换 -// 支持的变量:{api_key} -func processHeaderOverride(info *common.RelayInfo) (map[string]string, error) { +const clientHeaderPlaceholderPrefix = "{client_header:" + +func applyHeaderOverridePlaceholders(template string, c *gin.Context, apiKey string) (string, bool, error) { + trimmed := strings.TrimSpace(template) + if strings.HasPrefix(trimmed, clientHeaderPlaceholderPrefix) { + afterPrefix := trimmed[len(clientHeaderPlaceholderPrefix):] + end := strings.Index(afterPrefix, "}") + if end < 0 || end != len(afterPrefix)-1 { + return "", false, fmt.Errorf("client_header placeholder must be the full value: %q", template) + } + + name := strings.TrimSpace(afterPrefix[:end]) + if name == "" { + return "", false, fmt.Errorf("client_header placeholder name is empty: %q", template) + } + if c == nil || c.Request == nil { + return "", false, fmt.Errorf("missing request context for client_header placeholder") + } + clientHeaderValue := c.Request.Header.Get(name) + if strings.TrimSpace(clientHeaderValue) == "" { + return "", false, nil + } + template = clientHeaderValue + } + + if strings.Contains(template, "{api_key}") { + template = strings.ReplaceAll(template, "{api_key}", apiKey) + } + if strings.TrimSpace(template) == "" { + return "", false, nil + } + return template, true, nil +} + +// processHeaderOverride applies channel header overrides, with placeholder substitution. +// Supported placeholders: +// - {api_key}: resolved to the channel API key +// - {client_header:}: resolved to the incoming request header value +func processHeaderOverride(info *common.RelayInfo, c *gin.Context) (map[string]string, error) { headerOverride := make(map[string]string) for k, v := range info.HeadersOverride { str, ok := v.(string) @@ -48,12 +84,15 @@ func processHeaderOverride(info *common.RelayInfo) (map[string]string, error) { return nil, types.NewError(nil, types.ErrorCodeChannelHeaderOverrideInvalid) } - // 替换支持的变量 - if strings.Contains(str, "{api_key}") { - str = strings.ReplaceAll(str, "{api_key}", info.ApiKey) + value, include, err := applyHeaderOverridePlaceholders(str, c, info.ApiKey) + if err != nil { + return nil, types.NewError(err, types.ErrorCodeChannelHeaderOverrideInvalid) + } + if !include { + continue } - headerOverride[k] = str + headerOverride[k] = value } return headerOverride, nil } @@ -71,7 +110,7 @@ func DoApiRequest(a Adaptor, c *gin.Context, info *common.RelayInfo, requestBody return nil, fmt.Errorf("new request failed: %w", err) } headers := req.Header - headerOverride, err := processHeaderOverride(info) + headerOverride, err := processHeaderOverride(info, c) if err != nil { return nil, err } @@ -104,7 +143,7 @@ func DoFormRequest(a Adaptor, c *gin.Context, info *common.RelayInfo, requestBod // set form data req.Header.Set("Content-Type", c.Request.Header.Get("Content-Type")) headers := req.Header - headerOverride, err := processHeaderOverride(info) + headerOverride, err := processHeaderOverride(info, c) if err != nil { return nil, err } @@ -128,7 +167,7 @@ func DoWssRequest(a Adaptor, c *gin.Context, info *common.RelayInfo, requestBody return nil, fmt.Errorf("get request url failed: %w", err) } targetHeader := http.Header{} - headerOverride, err := processHeaderOverride(info) + headerOverride, err := processHeaderOverride(info, c) if err != nil { return nil, err } From 342b4fe80220c714409575793bfc0cb6ed91ca6b Mon Sep 17 00:00:00 2001 From: Seefs Date: Sat, 24 Jan 2026 23:42:45 +0800 Subject: [PATCH 2/2] fix: Do not interpolate {api_key} inside client-supplied content. --- relay/channel/api_request.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/relay/channel/api_request.go b/relay/channel/api_request.go index 2399fa198434..3659a5c388f2 100644 --- a/relay/channel/api_request.go +++ b/relay/channel/api_request.go @@ -60,7 +60,8 @@ func applyHeaderOverridePlaceholders(template string, c *gin.Context, apiKey str if strings.TrimSpace(clientHeaderValue) == "" { return "", false, nil } - template = clientHeaderValue + // Do not interpolate {api_key} inside client-supplied content. + return clientHeaderValue, true, nil } if strings.Contains(template, "{api_key}") {