Copilot commented on code in PR #3090:
URL: https://github.com/apache/dubbo-go/pull/3090#discussion_r2564901322


##########
protocol/triple/triple_protocol/cors.go:
##########
@@ -0,0 +1,288 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package triple_protocol
+
+import (
+       "net/http"
+       "net/url"
+       "sort"
+       "strconv"
+       "strings"
+)
+
+import (
+       "github.com/dubbogo/gost/log/logger"
+)
+
+import (
+       "dubbo.apache.org/dubbo-go/v3/global"
+)
+
+// corsConfig is an internal CORS configuration struct that doesn't depend on 
global package.
+type corsConfig struct {
+       allowOrigins     []string
+       allowMethods     []string
+       allowHeaders     []string
+       exposeHeaders    []string
+       allowCredentials bool
+       maxAge           int
+       hasWildcard      bool
+}
+
+const defaultPreflightMaxAge = 86400
+
+// convertCorsConfig converts a *global.CorsConfig to an internal *corsConfig.
+func convertCorsConfig(cfg *global.CorsConfig) *corsConfig {
+       if cfg == nil {
+               return nil
+       }
+       return &corsConfig{
+               allowOrigins:     append([]string(nil), cfg.AllowOrigins...),
+               allowMethods:     append([]string(nil), cfg.AllowMethods...),
+               allowHeaders:     append([]string(nil), cfg.AllowHeaders...),
+               exposeHeaders:    append([]string(nil), cfg.ExposeHeaders...),
+               allowCredentials: cfg.AllowCredentials,
+               maxAge:           cfg.MaxAge,
+       }
+}
+
+func buildCorsConfig(cfg *corsConfig, handlers []protocolHandler) *corsConfig {
+       if cfg == nil {
+               return nil
+       }
+       if len(cfg.allowOrigins) == 0 {
+               // Empty origins means CORS is disabled.
+               return nil
+       }
+
+       built := &corsConfig{
+               allowOrigins:     append([]string(nil), cfg.allowOrigins...),
+               allowHeaders:     append([]string(nil), cfg.allowHeaders...),
+               exposeHeaders:    append([]string(nil), cfg.exposeHeaders...),
+               allowCredentials: cfg.allowCredentials,
+               maxAge:           cfg.maxAge,
+               hasWildcard:      hasWildcardOrigin(cfg.allowOrigins),
+       }
+
+       built.allowMethods = normalizeCorsMethods(cfg.allowMethods, handlers)
+
+       if built.maxAge <= 0 {
+               built.maxAge = defaultPreflightMaxAge
+       }
+
+       return built
+}
+
+// normalizeCorsMethods normalizes and deduplicates CORS methods, adding 
defaults if needed.
+func normalizeCorsMethods(configuredMethods []string, handlers 
[]protocolHandler) []string {
+       methodSet := make(map[string]struct{})
+
+       // Add configured methods (normalized to uppercase)
+       for _, m := range configuredMethods {
+               if m == "" {
+                       continue
+               }
+               methodSet[strings.ToUpper(m)] = struct{}{}
+       }
+
+       // If no methods configured, try to infer from handlers
+       if len(methodSet) == 0 {
+               for _, hdl := range handlers {
+                       for m := range hdl.Methods() {
+                               methodSet[strings.ToUpper(m)] = struct{}{}
+                       }
+               }
+       }
+
+       // If still no methods, use defaults
+       if len(methodSet) == 0 {
+               for _, m := range []string{http.MethodGet, http.MethodPost, 
http.MethodPut, http.MethodDelete} {
+                       methodSet[m] = struct{}{}
+               }
+       }
+
+       // OPTIONS is always included for preflight requests
+       methodSet[http.MethodOptions] = struct{}{}
+
+       // Convert set to sorted slice
+       methods := make([]string, 0, len(methodSet))
+       for m := range methodSet {
+               methods = append(methods, m)
+       }
+       sort.Strings(methods)
+       return methods
+}
+
+// matchOrigin checks if the request origin matches any allowed pattern.
+// Supports:
+//   - exact match
+//   - "*" wildcard
+//   - subdomain wildcard: "*.example.com" or "https://*.example.com";
+func matchOrigin(origin string, allowedOrigins []string) bool {
+       if origin == "" || len(allowedOrigins) == 0 {
+               return false
+       }
+
+       originURL, err := url.Parse(origin)
+       if err != nil {
+               return false
+       }
+       originHost := originURL.Hostname()
+       originScheme := originURL.Scheme
+
+       for _, allowed := range allowedOrigins {
+               switch allowed {
+               case "":
+                       continue
+               case "*":
+                       return true
+               case origin:
+                       return true
+               }
+
+               allowedURL, err := url.Parse(allowed)
+               if err == nil && allowedURL.Hostname() != "" {
+                       if allowedURL.Scheme != "" && allowedURL.Scheme != 
originScheme {
+                               continue
+                       }
+                       allowedHost := allowedURL.Hostname()
+                       if strings.HasPrefix(allowedHost, "*.") {
+                               if subdomainOf(originHost, 
strings.TrimPrefix(allowedHost, "*.")) {
+                                       return true
+                               }
+                               continue
+                       }
+                       if allowedHost == originHost {
+                               return true
+                       }
+                       continue
+               }
+
+               if strings.HasPrefix(allowed, "*.") {
+                       if subdomainOf(originHost, strings.TrimPrefix(allowed, 
"*.")) {
+                               return true
+                       }
+               } else if allowed == originHost {
+                       return true
+               }
+       }
+
+       return false
+}
+
+func subdomainOf(originHost, base string) bool {
+       if base == "" || originHost == "" {
+               return false
+       }
+       return originHost != base && strings.HasSuffix(originHost, "."+base)

Review Comment:
   The `subdomainOf` function logic is incorrect. It should return true when 
`originHost` equals `base` OR is a subdomain, but currently it explicitly 
excludes the exact match case (`originHost != base`). For example, if `base` is 
\"example.com\" and the allowed origin is \"*.example.com\", a request from 
\"example.com\" itself should be allowed by the wildcard pattern, but this 
logic would reject it. Consider whether the exact match exclusion is 
intentional or if the logic should be `strings.HasSuffix(originHost, 
\".\"+base) || originHost == base`.
   ```suggestion
        return originHost == base || strings.HasSuffix(originHost, "."+base)
   ```



##########
protocol/triple/triple_protocol/cors.go:
##########
@@ -0,0 +1,288 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package triple_protocol
+
+import (
+       "net/http"
+       "net/url"
+       "sort"
+       "strconv"
+       "strings"
+)
+
+import (
+       "github.com/dubbogo/gost/log/logger"
+)
+
+import (
+       "dubbo.apache.org/dubbo-go/v3/global"
+)
+
+// corsConfig is an internal CORS configuration struct that doesn't depend on 
global package.
+type corsConfig struct {
+       allowOrigins     []string
+       allowMethods     []string
+       allowHeaders     []string
+       exposeHeaders    []string
+       allowCredentials bool
+       maxAge           int
+       hasWildcard      bool
+}
+
+const defaultPreflightMaxAge = 86400
+
+// convertCorsConfig converts a *global.CorsConfig to an internal *corsConfig.
+func convertCorsConfig(cfg *global.CorsConfig) *corsConfig {
+       if cfg == nil {
+               return nil
+       }
+       return &corsConfig{
+               allowOrigins:     append([]string(nil), cfg.AllowOrigins...),
+               allowMethods:     append([]string(nil), cfg.AllowMethods...),
+               allowHeaders:     append([]string(nil), cfg.AllowHeaders...),
+               exposeHeaders:    append([]string(nil), cfg.ExposeHeaders...),
+               allowCredentials: cfg.AllowCredentials,
+               maxAge:           cfg.MaxAge,
+       }
+}
+
+func buildCorsConfig(cfg *corsConfig, handlers []protocolHandler) *corsConfig {
+       if cfg == nil {
+               return nil
+       }
+       if len(cfg.allowOrigins) == 0 {
+               // Empty origins means CORS is disabled.
+               return nil
+       }
+
+       built := &corsConfig{
+               allowOrigins:     append([]string(nil), cfg.allowOrigins...),
+               allowHeaders:     append([]string(nil), cfg.allowHeaders...),
+               exposeHeaders:    append([]string(nil), cfg.exposeHeaders...),
+               allowCredentials: cfg.allowCredentials,
+               maxAge:           cfg.maxAge,
+               hasWildcard:      hasWildcardOrigin(cfg.allowOrigins),
+       }
+
+       built.allowMethods = normalizeCorsMethods(cfg.allowMethods, handlers)
+
+       if built.maxAge <= 0 {
+               built.maxAge = defaultPreflightMaxAge
+       }
+
+       return built
+}
+
+// normalizeCorsMethods normalizes and deduplicates CORS methods, adding 
defaults if needed.
+func normalizeCorsMethods(configuredMethods []string, handlers 
[]protocolHandler) []string {
+       methodSet := make(map[string]struct{})
+
+       // Add configured methods (normalized to uppercase)
+       for _, m := range configuredMethods {
+               if m == "" {
+                       continue
+               }
+               methodSet[strings.ToUpper(m)] = struct{}{}
+       }
+
+       // If no methods configured, try to infer from handlers
+       if len(methodSet) == 0 {
+               for _, hdl := range handlers {
+                       for m := range hdl.Methods() {
+                               methodSet[strings.ToUpper(m)] = struct{}{}
+                       }
+               }
+       }
+
+       // If still no methods, use defaults
+       if len(methodSet) == 0 {
+               for _, m := range []string{http.MethodGet, http.MethodPost, 
http.MethodPut, http.MethodDelete} {
+                       methodSet[m] = struct{}{}
+               }
+       }
+
+       // OPTIONS is always included for preflight requests
+       methodSet[http.MethodOptions] = struct{}{}
+
+       // Convert set to sorted slice
+       methods := make([]string, 0, len(methodSet))
+       for m := range methodSet {
+               methods = append(methods, m)
+       }
+       sort.Strings(methods)
+       return methods
+}
+
+// matchOrigin checks if the request origin matches any allowed pattern.
+// Supports:
+//   - exact match
+//   - "*" wildcard
+//   - subdomain wildcard: "*.example.com" or "https://*.example.com";
+func matchOrigin(origin string, allowedOrigins []string) bool {
+       if origin == "" || len(allowedOrigins) == 0 {
+               return false
+       }
+
+       originURL, err := url.Parse(origin)
+       if err != nil {
+               return false
+       }
+       originHost := originURL.Hostname()
+       originScheme := originURL.Scheme
+
+       for _, allowed := range allowedOrigins {
+               switch allowed {
+               case "":
+                       continue
+               case "*":
+                       return true
+               case origin:
+                       return true
+               }
+
+               allowedURL, err := url.Parse(allowed)
+               if err == nil && allowedURL.Hostname() != "" {
+                       if allowedURL.Scheme != "" && allowedURL.Scheme != 
originScheme {
+                               continue
+                       }
+                       allowedHost := allowedURL.Hostname()
+                       if strings.HasPrefix(allowedHost, "*.") {
+                               if subdomainOf(originHost, 
strings.TrimPrefix(allowedHost, "*.")) {
+                                       return true
+                               }
+                               continue
+                       }
+                       if allowedHost == originHost {
+                               return true
+                       }
+                       continue
+               }
+
+               if strings.HasPrefix(allowed, "*.") {
+                       if subdomainOf(originHost, strings.TrimPrefix(allowed, 
"*.")) {
+                               return true
+                       }
+               } else if allowed == originHost {
+                       return true
+               }
+       }
+
+       return false
+}
+
+func subdomainOf(originHost, base string) bool {
+       if base == "" || originHost == "" {
+               return false
+       }
+       return originHost != base && strings.HasSuffix(originHost, "."+base)
+}
+
+func handlePreflight(w http.ResponseWriter, r *http.Request, cors *corsConfig) 
bool {

Review Comment:
   The `handlePreflight` function lacks test coverage. This function handles 
CORS preflight requests which are critical for security. Add tests covering: 
valid preflight requests, forbidden origins, disallowed methods, missing Origin 
header, and proper response header setting.



##########
protocol/triple/triple_protocol/handler.go:
##########
@@ -35,8 +35,9 @@ type Handler struct {
        // key is group/version
        implementations  map[string]StreamingHandlerFunc
        protocolHandlers []protocolHandler
-       allowMethod      string // Allow header
-       acceptPost       string // Accept-Post header
+       allowMethod      string      // Allow header
+       acceptPost       string      // Accept-Post header
+       cors             *corsConfig // CORS configuration

Review Comment:
   [nitpick] Inconsistent formatting: the alignment of field types was changed. 
The original code had aligned field types (both `string` fields lined up), but 
now `*corsConfig` breaks that alignment. Consider maintaining the existing 
alignment pattern for consistency.
   ```suggestion
        allowMethod      string       // Allow header
        acceptPost       string       // Accept-Post header
        cors             *corsConfig  // CORS configuration
   ```



##########
protocol/triple/triple_protocol/handler.go:
##########
@@ -462,3 +475,19 @@ func (c *handlerConfig) newProtocolHandlers(streamType 
StreamType) []protocolHan
 func getIdentifier(group, version string) string {
        return group + "/" + version
 }
+
+// handleCORS processes CORS requests. Returns true if the request was handled 
and processing should stop.
+func (h *Handler) handleCORS(w http.ResponseWriter, r *http.Request) bool {
+       if r.Method == http.MethodOptions && 
r.Header.Get("Access-Control-Request-Method") != "" {
+               return handlePreflight(w, r, h.cors)
+       }
+
+       if origin := r.Header.Get("Origin"); origin != "" {
+               if !allowOrigin(origin, h.cors) {
+                       w.WriteHeader(http.StatusForbidden)
+                       return true
+               }
+               addCORSHeaders(w, r, h.cors)
+       }

Review Comment:
   [nitpick] For actual (non-preflight) CORS requests with forbidden origins, 
the handler returns `StatusForbidden` but doesn't write an error message body. 
While the status code is correct, returning an empty response body may make 
debugging difficult for API consumers. Consider writing a descriptive error 
message in the response body to improve the developer experience.



##########
protocol/triple/triple_protocol/option.go:
##########
@@ -22,6 +22,10 @@ import (
        "time"
 )
 
+import (

Review Comment:
   Duplicate import statement. The package already has an import section at 
lines 18-23. This second import block should be merged with the existing one to 
maintain consistent import organization.
   ```suggestion
   
   ```



##########
protocol/triple/triple_protocol/cors.go:
##########
@@ -0,0 +1,288 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package triple_protocol
+
+import (
+       "net/http"
+       "net/url"
+       "sort"
+       "strconv"
+       "strings"
+)
+
+import (
+       "github.com/dubbogo/gost/log/logger"
+)
+
+import (
+       "dubbo.apache.org/dubbo-go/v3/global"
+)
+
+// corsConfig is an internal CORS configuration struct that doesn't depend on 
global package.
+type corsConfig struct {
+       allowOrigins     []string
+       allowMethods     []string
+       allowHeaders     []string
+       exposeHeaders    []string
+       allowCredentials bool
+       maxAge           int
+       hasWildcard      bool
+}
+
+const defaultPreflightMaxAge = 86400
+
+// convertCorsConfig converts a *global.CorsConfig to an internal *corsConfig.
+func convertCorsConfig(cfg *global.CorsConfig) *corsConfig {
+       if cfg == nil {
+               return nil
+       }
+       return &corsConfig{
+               allowOrigins:     append([]string(nil), cfg.AllowOrigins...),
+               allowMethods:     append([]string(nil), cfg.AllowMethods...),
+               allowHeaders:     append([]string(nil), cfg.AllowHeaders...),
+               exposeHeaders:    append([]string(nil), cfg.ExposeHeaders...),
+               allowCredentials: cfg.AllowCredentials,
+               maxAge:           cfg.MaxAge,
+       }
+}
+
+func buildCorsConfig(cfg *corsConfig, handlers []protocolHandler) *corsConfig {
+       if cfg == nil {
+               return nil
+       }
+       if len(cfg.allowOrigins) == 0 {
+               // Empty origins means CORS is disabled.
+               return nil
+       }
+
+       built := &corsConfig{
+               allowOrigins:     append([]string(nil), cfg.allowOrigins...),
+               allowHeaders:     append([]string(nil), cfg.allowHeaders...),
+               exposeHeaders:    append([]string(nil), cfg.exposeHeaders...),
+               allowCredentials: cfg.allowCredentials,
+               maxAge:           cfg.maxAge,
+               hasWildcard:      hasWildcardOrigin(cfg.allowOrigins),
+       }
+
+       built.allowMethods = normalizeCorsMethods(cfg.allowMethods, handlers)
+
+       if built.maxAge <= 0 {
+               built.maxAge = defaultPreflightMaxAge
+       }
+
+       return built
+}
+
+// normalizeCorsMethods normalizes and deduplicates CORS methods, adding 
defaults if needed.
+func normalizeCorsMethods(configuredMethods []string, handlers 
[]protocolHandler) []string {
+       methodSet := make(map[string]struct{})
+
+       // Add configured methods (normalized to uppercase)
+       for _, m := range configuredMethods {
+               if m == "" {
+                       continue
+               }
+               methodSet[strings.ToUpper(m)] = struct{}{}
+       }
+
+       // If no methods configured, try to infer from handlers
+       if len(methodSet) == 0 {
+               for _, hdl := range handlers {
+                       for m := range hdl.Methods() {
+                               methodSet[strings.ToUpper(m)] = struct{}{}
+                       }
+               }
+       }
+
+       // If still no methods, use defaults
+       if len(methodSet) == 0 {
+               for _, m := range []string{http.MethodGet, http.MethodPost, 
http.MethodPut, http.MethodDelete} {
+                       methodSet[m] = struct{}{}
+               }
+       }
+
+       // OPTIONS is always included for preflight requests
+       methodSet[http.MethodOptions] = struct{}{}
+
+       // Convert set to sorted slice
+       methods := make([]string, 0, len(methodSet))
+       for m := range methodSet {
+               methods = append(methods, m)
+       }
+       sort.Strings(methods)
+       return methods
+}
+
+// matchOrigin checks if the request origin matches any allowed pattern.
+// Supports:
+//   - exact match
+//   - "*" wildcard
+//   - subdomain wildcard: "*.example.com" or "https://*.example.com";
+func matchOrigin(origin string, allowedOrigins []string) bool {
+       if origin == "" || len(allowedOrigins) == 0 {
+               return false
+       }
+
+       originURL, err := url.Parse(origin)
+       if err != nil {
+               return false
+       }
+       originHost := originURL.Hostname()
+       originScheme := originURL.Scheme
+
+       for _, allowed := range allowedOrigins {
+               switch allowed {
+               case "":
+                       continue
+               case "*":
+                       return true
+               case origin:
+                       return true
+               }
+
+               allowedURL, err := url.Parse(allowed)
+               if err == nil && allowedURL.Hostname() != "" {
+                       if allowedURL.Scheme != "" && allowedURL.Scheme != 
originScheme {
+                               continue
+                       }
+                       allowedHost := allowedURL.Hostname()
+                       if strings.HasPrefix(allowedHost, "*.") {
+                               if subdomainOf(originHost, 
strings.TrimPrefix(allowedHost, "*.")) {
+                                       return true
+                               }
+                               continue
+                       }

Review Comment:
   The subdomain matching logic is duplicated at lines 164-169 and 176-179. 
This creates a maintenance burden if the logic needs to be updated. Consider 
extracting this into a helper function or refactoring to handle both cases in 
one place.



##########
protocol/triple/triple_protocol/cors.go:
##########
@@ -0,0 +1,288 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package triple_protocol
+
+import (
+       "net/http"
+       "net/url"
+       "sort"
+       "strconv"
+       "strings"
+)
+
+import (
+       "github.com/dubbogo/gost/log/logger"
+)
+
+import (
+       "dubbo.apache.org/dubbo-go/v3/global"
+)
+
+// corsConfig is an internal CORS configuration struct that doesn't depend on 
global package.
+type corsConfig struct {
+       allowOrigins     []string
+       allowMethods     []string
+       allowHeaders     []string
+       exposeHeaders    []string
+       allowCredentials bool
+       maxAge           int
+       hasWildcard      bool
+}
+
+const defaultPreflightMaxAge = 86400
+
+// convertCorsConfig converts a *global.CorsConfig to an internal *corsConfig.
+func convertCorsConfig(cfg *global.CorsConfig) *corsConfig {
+       if cfg == nil {
+               return nil
+       }
+       return &corsConfig{
+               allowOrigins:     append([]string(nil), cfg.AllowOrigins...),
+               allowMethods:     append([]string(nil), cfg.AllowMethods...),
+               allowHeaders:     append([]string(nil), cfg.AllowHeaders...),
+               exposeHeaders:    append([]string(nil), cfg.ExposeHeaders...),
+               allowCredentials: cfg.AllowCredentials,
+               maxAge:           cfg.MaxAge,
+       }
+}
+
+func buildCorsConfig(cfg *corsConfig, handlers []protocolHandler) *corsConfig {
+       if cfg == nil {
+               return nil
+       }
+       if len(cfg.allowOrigins) == 0 {
+               // Empty origins means CORS is disabled.
+               return nil
+       }

Review Comment:
   The `buildCorsConfig` function lacks test coverage. This is a critical 
function that determines whether CORS is enabled and normalizes configuration. 
The logic at lines 68-71 (disabling CORS when origins are empty) and the method 
normalization at line 82 should be tested. Add unit tests covering: empty 
origins, nil config, config with wildcard origins, and method inference from 
handlers.



##########
protocol/triple/triple_protocol/handler.go:
##########
@@ -462,3 +475,19 @@ func (c *handlerConfig) newProtocolHandlers(streamType 
StreamType) []protocolHan
 func getIdentifier(group, version string) string {
        return group + "/" + version
 }
+
+// handleCORS processes CORS requests. Returns true if the request was handled 
and processing should stop.
+func (h *Handler) handleCORS(w http.ResponseWriter, r *http.Request) bool {

Review Comment:
   The `handleCORS` method lacks test coverage. This is the entry point for 
CORS handling in the request pipeline. Add integration tests covering: OPTIONS 
preflight requests, actual requests with Origin header, requests without Origin 
header, and forbidden origins.



##########
protocol/triple/triple_protocol/cors.go:
##########
@@ -0,0 +1,288 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package triple_protocol
+
+import (
+       "net/http"
+       "net/url"
+       "sort"
+       "strconv"
+       "strings"
+)
+
+import (
+       "github.com/dubbogo/gost/log/logger"
+)
+
+import (
+       "dubbo.apache.org/dubbo-go/v3/global"
+)
+
+// corsConfig is an internal CORS configuration struct that doesn't depend on 
global package.
+type corsConfig struct {
+       allowOrigins     []string
+       allowMethods     []string
+       allowHeaders     []string
+       exposeHeaders    []string
+       allowCredentials bool
+       maxAge           int
+       hasWildcard      bool
+}
+
+const defaultPreflightMaxAge = 86400
+
+// convertCorsConfig converts a *global.CorsConfig to an internal *corsConfig.
+func convertCorsConfig(cfg *global.CorsConfig) *corsConfig {
+       if cfg == nil {
+               return nil
+       }
+       return &corsConfig{
+               allowOrigins:     append([]string(nil), cfg.AllowOrigins...),
+               allowMethods:     append([]string(nil), cfg.AllowMethods...),
+               allowHeaders:     append([]string(nil), cfg.AllowHeaders...),
+               exposeHeaders:    append([]string(nil), cfg.ExposeHeaders...),
+               allowCredentials: cfg.AllowCredentials,
+               maxAge:           cfg.MaxAge,
+       }
+}
+
+func buildCorsConfig(cfg *corsConfig, handlers []protocolHandler) *corsConfig {
+       if cfg == nil {
+               return nil
+       }
+       if len(cfg.allowOrigins) == 0 {
+               // Empty origins means CORS is disabled.
+               return nil
+       }
+
+       built := &corsConfig{
+               allowOrigins:     append([]string(nil), cfg.allowOrigins...),
+               allowHeaders:     append([]string(nil), cfg.allowHeaders...),
+               exposeHeaders:    append([]string(nil), cfg.exposeHeaders...),
+               allowCredentials: cfg.allowCredentials,
+               maxAge:           cfg.maxAge,
+               hasWildcard:      hasWildcardOrigin(cfg.allowOrigins),
+       }
+
+       built.allowMethods = normalizeCorsMethods(cfg.allowMethods, handlers)
+
+       if built.maxAge <= 0 {
+               built.maxAge = defaultPreflightMaxAge
+       }
+
+       return built
+}
+
+// normalizeCorsMethods normalizes and deduplicates CORS methods, adding 
defaults if needed.
+func normalizeCorsMethods(configuredMethods []string, handlers 
[]protocolHandler) []string {
+       methodSet := make(map[string]struct{})
+
+       // Add configured methods (normalized to uppercase)
+       for _, m := range configuredMethods {
+               if m == "" {
+                       continue
+               }
+               methodSet[strings.ToUpper(m)] = struct{}{}
+       }
+
+       // If no methods configured, try to infer from handlers
+       if len(methodSet) == 0 {
+               for _, hdl := range handlers {
+                       for m := range hdl.Methods() {
+                               methodSet[strings.ToUpper(m)] = struct{}{}
+                       }
+               }
+       }
+
+       // If still no methods, use defaults
+       if len(methodSet) == 0 {
+               for _, m := range []string{http.MethodGet, http.MethodPost, 
http.MethodPut, http.MethodDelete} {
+                       methodSet[m] = struct{}{}
+               }
+       }
+
+       // OPTIONS is always included for preflight requests
+       methodSet[http.MethodOptions] = struct{}{}
+
+       // Convert set to sorted slice
+       methods := make([]string, 0, len(methodSet))
+       for m := range methodSet {
+               methods = append(methods, m)
+       }
+       sort.Strings(methods)
+       return methods
+}
+
+// matchOrigin checks if the request origin matches any allowed pattern.
+// Supports:
+//   - exact match
+//   - "*" wildcard
+//   - subdomain wildcard: "*.example.com" or "https://*.example.com";
+func matchOrigin(origin string, allowedOrigins []string) bool {

Review Comment:
   The `matchOrigin` function lacks test coverage. This function implements 
critical security logic for origin validation including exact matching, 
wildcard matching, and subdomain patterns. Add comprehensive unit tests 
covering: exact matches, wildcard \"*\", subdomain wildcards 
(\"*.example.com\"), scheme-specific wildcards (\"https://*.example.com\";), 
invalid origins, and edge cases like empty strings.



##########
protocol/triple/options_cors.go:
##########
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package triple
+
+import (
+       "errors"
+)
+
+import (
+       "dubbo.apache.org/dubbo-go/v3/global"
+)
+
+// CORSOption configures a single aspect of CORS.
+type CORSOption func(*global.CorsConfig)
+
+var (
+       errNilCORSConfig             = errors.New("cors config is nil")
+       errWildcardOriginWithCookies = errors.New("allowCredentials cannot be 
true when allow-origins contains \"*\"")
+       errNegativeMaxAge            = errors.New("max-age must be 
non-negative")
+)
+
+// CORSAllowOrigins sets allowed origins for CORS requests.
+func CORSAllowOrigins(origins ...string) CORSOption {
+       return func(c *global.CorsConfig) {
+               c.AllowOrigins = append([]string(nil), origins...)
+       }
+}
+
+// CORSAllowMethods sets allowed HTTP methods for CORS requests.
+func CORSAllowMethods(methods ...string) CORSOption {
+       return func(c *global.CorsConfig) {
+               c.AllowMethods = append([]string(nil), methods...)
+       }
+}
+
+// CORSAllowHeaders sets allowed request headers for CORS requests.
+func CORSAllowHeaders(headers ...string) CORSOption {
+       return func(c *global.CorsConfig) {
+               c.AllowHeaders = append([]string(nil), headers...)
+       }
+}
+
+// CORSExposeHeaders sets headers exposed to the browser.
+func CORSExposeHeaders(headers ...string) CORSOption {
+       return func(c *global.CorsConfig) {
+               c.ExposeHeaders = append([]string(nil), headers...)
+       }
+}
+
+// CORSAllowCredentials toggles whether credentials are allowed.
+func CORSAllowCredentials(allow bool) CORSOption {
+       return func(c *global.CorsConfig) {
+               c.AllowCredentials = allow
+       }
+}
+
+// CORSMaxAge sets the max age for preflight cache.
+func CORSMaxAge(maxAge int) CORSOption {
+       return func(c *global.CorsConfig) {
+               c.MaxAge = maxAge
+       }
+}
+
+// validateCorsConfig validates CORS configuration for unsafe combinations.
+func validateCorsConfig(cors *global.CorsConfig) error {

Review Comment:
   The `validateCorsConfig` function lacks test coverage. This function 
implements important validation logic to prevent insecure configurations. Add 
unit tests covering: nil config, wildcard origin with credentials enabled, 
negative max age, and valid configurations.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to