From 538448170480c1961eadabda065605b03eae645b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BE=84=E6=BD=AD?= Date: Tue, 9 Sep 2025 16:14:33 +0800 Subject: [PATCH] =?UTF-8?q?refactor(mcp-server):=20improve=20host=20matchi?= =?UTF-8?q?ng=20logic=20using=20HostMatcher=20p=E2=80=A6=20(#2890)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../golang-filter/mcp-session/common/match.go | 74 ++++++++++++++++--- 1 file changed, 64 insertions(+), 10 deletions(-) diff --git a/plugins/golang-filter/mcp-session/common/match.go b/plugins/golang-filter/mcp-session/common/match.go index bd5b250b5..405a139be 100644 --- a/plugins/golang-filter/mcp-session/common/match.go +++ b/plugins/golang-filter/mcp-session/common/match.go @@ -13,6 +13,9 @@ type RuleType string // UpstreamType defines the type of matching rule type UpstreamType string +// HostMatchType defines the type of host matching +type HostMatchType int + const ( ExactMatch RuleType = "exact" PrefixMatch RuleType = "prefix" @@ -23,8 +26,18 @@ const ( RestUpstream UpstreamType = "rest" SSEUpstream UpstreamType = "sse" StreamableUpstream UpstreamType = "streamable" + + HostExact HostMatchType = iota + HostPrefix + HostSuffix ) +// HostMatcher defines the structure for host matching +type HostMatcher struct { + matchType HostMatchType + host string +} + // MatchRule defines the structure for a matching rule type MatchRule struct { MatchRuleDomain string `json:"match_rule_domain"` // Domain pattern, supports wildcards @@ -84,11 +97,38 @@ func ParseMatchList(matchListConfig []interface{}) []MatchRule { return matchList } -// convertWildcardToRegex converts wildcard pattern to regex pattern -func convertWildcardToRegex(pattern string) string { - pattern = regexp.QuoteMeta(pattern) - pattern = "^" + strings.ReplaceAll(pattern, "\\*", ".*") + "$" - return pattern +// stripPortFromHost removes port from host string +// Port removing code is inspired by +// https://github.com/envoyproxy/envoy/blob/v1.17.0/source/common/http/header_utility.cc#L219 +func stripPortFromHost(reqHost string) string { + portStart := strings.LastIndexByte(reqHost, ':') + if portStart != -1 { + // According to RFC3986 v6 address is always enclosed in "[]". + // section 3.2.2. + v6EndIndex := strings.LastIndexByte(reqHost, ']') + if v6EndIndex == -1 || v6EndIndex < portStart { + if portStart+1 <= len(reqHost) { + return reqHost[:portStart] + } + } + } + return reqHost +} + +// parseHostPattern parses a host pattern and returns a HostMatcher +func parseHostPattern(pattern string) HostMatcher { + var hostMatcher HostMatcher + if strings.HasPrefix(pattern, "*") { + hostMatcher.matchType = HostSuffix + hostMatcher.host = pattern[1:] + } else if strings.HasSuffix(pattern, "*") { + hostMatcher.matchType = HostPrefix + hostMatcher.host = pattern[:len(pattern)-1] + } else { + hostMatcher.matchType = HostExact + hostMatcher.host = pattern + } + return hostMatcher } // matchPattern checks if the target matches the pattern based on rule type @@ -117,15 +157,29 @@ func matchPattern(pattern string, target string, ruleType RuleType) bool { } } -// matchDomain checks if the domain matches the pattern +// matchDomain checks if the domain matches the pattern using HostMatcher approach func matchDomain(domain string, pattern string) bool { if pattern == "" || pattern == "*" { return true } - // Convert wildcard pattern to regex pattern - regexPattern := convertWildcardToRegex(pattern) - matched, _ := regexp.MatchString(regexPattern, domain) - return matched + + // Strip port from domain + domain = stripPortFromHost(domain) + + // Parse the pattern into a HostMatcher + hostMatcher := parseHostPattern(pattern) + + // Perform matching based on match type + switch hostMatcher.matchType { + case HostSuffix: + return strings.HasSuffix(domain, hostMatcher.host) + case HostPrefix: + return strings.HasPrefix(domain, hostMatcher.host) + case HostExact: + return domain == hostMatcher.host + default: + return false + } } // matchDomainAndPath checks if both domain and path match the rule