diff --git a/internal/controller/nginx/config/servers.go b/internal/controller/nginx/config/servers.go index 88ba4fa8ea..7924b99ff7 100644 --- a/internal/controller/nginx/config/servers.go +++ b/internal/controller/nginx/config/servers.go @@ -414,7 +414,7 @@ func initializeExternalLocations( } if !exactPathExists { externalLocExact := http.Location{ - Path: exactPath(externalLocPath), + Path: exactPath(rule.Path), Type: locType, } extLocations = append(extLocations, externalLocExact) @@ -458,7 +458,6 @@ func updateLocation( mirrorPercentage *float64, ) http.Location { filters := matchRule.Filters - path := pathRule.Path grpc := pathRule.GRPC if filters.InvalidFilter != nil { @@ -466,15 +465,15 @@ func updateLocation( return location } - location = updateLocationMirrorRoute(location, path, grpc) + location = updateLocationMirrorRoute(location, pathRule.Path, grpc) location.Includes = append(location.Includes, createIncludesFromLocationSnippetsFilters(filters.SnippetsFilters)...) if filters.RequestRedirect != nil { - return updateLocationRedirectFilter(location, filters.RequestRedirect, listenerPort, path) + return updateLocationRedirectFilter(location, filters.RequestRedirect, listenerPort, pathRule) } - location = updateLocationRewriteFilter(location, filters.RequestURLRewrite, path) - location = updateLocationMirrorFilters(location, filters.RequestMirrors, path, mirrorPercentage) + location = updateLocationRewriteFilter(location, filters.RequestURLRewrite, pathRule) + location = updateLocationMirrorFilters(location, filters.RequestMirrors, pathRule.Path, mirrorPercentage) location = updateLocationProxySettings(location, matchRule, grpc, keepAliveCheck) return location @@ -495,9 +494,9 @@ func updateLocationRedirectFilter( location http.Location, redirectFilter *dataplane.HTTPRequestRedirectFilter, listenerPort int32, - path string, + pathRule dataplane.PathRule, ) http.Location { - ret, rewrite := createReturnAndRewriteConfigForRedirectFilter(redirectFilter, listenerPort, path) + ret, rewrite := createReturnAndRewriteConfigForRedirectFilter(redirectFilter, listenerPort, pathRule) if rewrite.MainRewrite != "" { location.Rewrites = append(location.Rewrites, rewrite.MainRewrite) } @@ -509,9 +508,9 @@ func updateLocationRedirectFilter( func updateLocationRewriteFilter( location http.Location, rewriteFilter *dataplane.HTTPURLRewriteFilter, - path string, + pathRule dataplane.PathRule, ) http.Location { - rewrites := createRewritesValForRewriteFilter(rewriteFilter, path) + rewrites := createRewritesValForRewriteFilter(rewriteFilter, pathRule) if rewrites != nil { if location.Type == http.InternalLocationType && rewrites.InternalRewrite != "" { location.Rewrites = append(location.Rewrites, rewrites.InternalRewrite) @@ -658,7 +657,7 @@ func createProxySSLVerify(v *dataplane.VerifyTLS) *http.ProxySSLVerify { func createReturnAndRewriteConfigForRedirectFilter( filter *dataplane.HTTPRequestRedirectFilter, listenerPort int32, - path string, + pathRule dataplane.PathRule, ) (*http.Return, *rewriteConfig) { if filter == nil { return nil, nil @@ -702,7 +701,12 @@ func createReturnAndRewriteConfigForRedirectFilter( rewrites := &rewriteConfig{} if filter.Path != nil { - rewrites.MainRewrite = createMainRewriteForFilters(filter.Path, path) + mainRewrite := createMainRewriteForFilters(filter.Path, pathRule) + if mainRewrite == "" { + // Invalid configuration for the rewrite filter + return nil, nil + } + rewrites.MainRewrite = mainRewrite body = fmt.Sprintf("%s://%s$uri$is_args$args", scheme, hostnamePort) } @@ -712,19 +716,26 @@ func createReturnAndRewriteConfigForRedirectFilter( }, rewrites } -func createMainRewriteForFilters(pathModifier *dataplane.HTTPPathModifier, path string) string { +func createMainRewriteForFilters(pathModifier *dataplane.HTTPPathModifier, pathRule dataplane.PathRule) string { var mainRewrite string switch pathModifier.Type { case dataplane.ReplaceFullPath: mainRewrite = fmt.Sprintf("^ %s", pathModifier.Replacement) case dataplane.ReplacePrefixMatch: + // ReplacePrefixMatch is only compatible with a PathPrefix HTTPRouteMatch. + // ReplaceFullPath is compatible with PathTypeExact/PathTypePrefix/PathTypeRegularExpression HTTPRouteMatch. + // see https://gateway-api.sigs.k8s.io/reference/spec/?h=replaceprefixmatch#httppathmodifier + if pathRule.PathType != dataplane.PathTypePrefix { + return "" + } + filterPrefix := pathModifier.Replacement if filterPrefix == "" { filterPrefix = "/" } // capture everything following the configured prefix up to the first ?, if present. - regex := fmt.Sprintf("^%s([^?]*)?", path) + regex := fmt.Sprintf("^%s([^?]*)?", pathRule.Path) // replace the configured prefix with the filter prefix, append the captured segment, // and include the request arguments stored in nginx variable $args. // https://nginx.org/en/docs/http/ngx_http_core_module.html#var_args @@ -733,13 +744,13 @@ func createMainRewriteForFilters(pathModifier *dataplane.HTTPPathModifier, path // if configured prefix does not end in /, but replacement prefix does end in /, // then make sure that we *require* but *don't capture* a trailing slash in the request, // otherwise we'll get duplicate slashes in the full replacement - if strings.HasSuffix(filterPrefix, "/") && !strings.HasSuffix(path, "/") { - regex = fmt.Sprintf("^%s(?:/([^?]*))?", path) + if strings.HasSuffix(filterPrefix, "/") && !strings.HasSuffix(pathRule.Path, "/") { + regex = fmt.Sprintf("^%s(?:/([^?]*))?", pathRule.Path) } // if configured prefix ends in / we won't capture it for a request (since it's not in the regex), // so append it to the replacement prefix if the replacement prefix doesn't already end in / - if strings.HasSuffix(path, "/") && !strings.HasSuffix(filterPrefix, "/") { + if strings.HasSuffix(pathRule.Path, "/") && !strings.HasSuffix(filterPrefix, "/") { replacement = fmt.Sprintf("%s/1ドル?$args?", filterPrefix) } @@ -749,7 +760,10 @@ func createMainRewriteForFilters(pathModifier *dataplane.HTTPPathModifier, path return mainRewrite } -func createRewritesValForRewriteFilter(filter *dataplane.HTTPURLRewriteFilter, path string) *rewriteConfig { +func createRewritesValForRewriteFilter( + filter *dataplane.HTTPURLRewriteFilter, + pathRule dataplane.PathRule, +) *rewriteConfig { if filter == nil { return nil } @@ -758,8 +772,13 @@ func createRewritesValForRewriteFilter(filter *dataplane.HTTPURLRewriteFilter, p if filter.Path != nil { rewrites.InternalRewrite = "^ $request_uri" - // for URLRewriteFilter, we add a break to the rewrite to prevent further processing of the request. - rewrites.MainRewrite = fmt.Sprintf("%s break", createMainRewriteForFilters(filter.Path, path)) + mainRewrite := createMainRewriteForFilters(filter.Path, pathRule) + if mainRewrite == "" { + // Invalid configuration for the rewrite filter + return nil + } + // For URLRewriteFilter, add "break" to prevent further processing of the request. + rewrites.MainRewrite = fmt.Sprintf("%s break", mainRewrite) } return rewrites @@ -982,14 +1001,18 @@ func createPath(rule dataplane.PathRule) string { switch rule.PathType { case dataplane.PathTypeExact: return exactPath(rule.Path) + case dataplane.PathTypePrefix: + return fmt.Sprintf("^~ %s", rule.Path) + case dataplane.PathTypeRegularExpression: + return fmt.Sprintf("~ %s", rule.Path) default: - return rule.Path + return "" // should never happen because path type is validated earlier } } func createDefaultRootLocation() http.Location { return http.Location{ - Path: "/", + Path: "= /", Return: &http.Return{Code: http.StatusNotFound}, } } diff --git a/internal/controller/nginx/config/servers_test.go b/internal/controller/nginx/config/servers_test.go index 6b604d7bec..05d9a53e56 100644 --- a/internal/controller/nginx/config/servers_test.go +++ b/internal/controller/nginx/config/servers_test.go @@ -1398,7 +1398,7 @@ func TestCreateServers(t *testing.T) { return []http.Location{ { - Path: "/", + Path: "^~ /", HTTPMatchKey: ssl + "1_0", Type: http.RedirectLocationType, Includes: externalIncludes, @@ -1425,7 +1425,7 @@ func TestCreateServers(t *testing.T) { Includes: internalIncludes, }, { - Path: "/test/", + Path: "^~ /test/", HTTPMatchKey: ssl + "1_1", Type: http.RedirectLocationType, Includes: externalIncludes, @@ -1438,7 +1438,7 @@ func TestCreateServers(t *testing.T) { Includes: internalIncludes, }, { - Path: "/path-only/", + Path: "^~ /path-only/", ProxyPass: "http://invalid-backend-ref$request_uri", ProxySetHeaders: httpBaseHeaders, Type: http.ExternalLocationType, @@ -1452,7 +1452,7 @@ func TestCreateServers(t *testing.T) { Includes: externalIncludes, }, { - Path: "/backend-tls-policy/", + Path: "^~ /backend-tls-policy/", ProxyPass: "https://test_btp_80$request_uri", ProxySetHeaders: httpBaseHeaders, ProxySSLVerify: &http.ProxySSLVerify{ @@ -1474,7 +1474,7 @@ func TestCreateServers(t *testing.T) { Includes: externalIncludes, }, { - Path: "/redirect-implicit-port/", + Path: "^~ /redirect-implicit-port/", Return: &http.Return{ Code: 302, Body: fmt.Sprintf("$scheme://foo.example.com:%d$request_uri", port), @@ -1492,7 +1492,7 @@ func TestCreateServers(t *testing.T) { Includes: externalIncludes, }, { - Path: "/redirect-explicit-port/", + Path: "^~ /redirect-explicit-port/", Return: &http.Return{ Code: 302, Body: "$scheme://bar.example.com:8080$request_uri", @@ -1510,7 +1510,7 @@ func TestCreateServers(t *testing.T) { Includes: externalIncludes, }, { - Path: "/redirect-with-headers/", + Path: "^~ /redirect-with-headers/", HTTPMatchKey: ssl + "1_6", Type: http.RedirectLocationType, Includes: externalIncludes, @@ -1531,7 +1531,7 @@ func TestCreateServers(t *testing.T) { Includes: internalIncludes, }, { - Path: "/rewrite/", + Path: "^~ /rewrite/", Rewrites: []string{"^ /replacement break"}, ProxyPass: "http://test_foo_80", ProxySetHeaders: rewriteProxySetHeaders, @@ -1547,7 +1547,7 @@ func TestCreateServers(t *testing.T) { Includes: externalIncludes, }, { - Path: "/rewrite-with-headers/", + Path: "^~ /rewrite-with-headers/", HTTPMatchKey: ssl + "1_8", Type: http.RedirectLocationType, Includes: externalIncludes, @@ -1567,7 +1567,7 @@ func TestCreateServers(t *testing.T) { Includes: internalIncludes, }, { - Path: "/mirror/", + Path: "^~ /mirror/", ProxyPass: "http://test_foo_80$request_uri", ProxySetHeaders: httpBaseHeaders, MirrorPaths: []string{"/_ngf-internal-mirror-my-backend-test/route1-0"}, @@ -1671,7 +1671,7 @@ func TestCreateServers(t *testing.T) { Includes: externalIncludes, }, { - Path: "/invalid-filter/", + Path: "^~ /invalid-filter/", Return: &http.Return{ Code: http.StatusInternalServerError, }, @@ -1687,7 +1687,7 @@ func TestCreateServers(t *testing.T) { Includes: externalIncludes, }, { - Path: "/invalid-filter-with-headers/", + Path: "^~ /invalid-filter-with-headers/", HTTPMatchKey: ssl + "1_22", Type: http.RedirectLocationType, Includes: externalIncludes, @@ -1727,7 +1727,7 @@ func TestCreateServers(t *testing.T) { Includes: internalIncludes, }, { - Path: "/proxy-set-headers/", + Path: "^~ /proxy-set-headers/", ProxyPass: "http://test_foo_80$request_uri", ProxySetHeaders: append([]http.Header{ { @@ -1818,7 +1818,7 @@ func TestCreateServers(t *testing.T) { Includes: externalIncludes, }, { - Path: "/redirect-with-path/", + Path: "^~ /redirect-with-path/", Type: http.ExternalLocationType, Return: &http.Return{ Code: 301, @@ -1981,7 +1981,7 @@ func TestCreateServersConflicts(t *testing.T) { }, expLocs: []http.Location{ { - Path: "/coffee/", + Path: "^~ /coffee/", ProxyPass: "http://test_foo_80$request_uri", ProxySetHeaders: httpBaseHeaders, Type: http.ExternalLocationType, @@ -2027,7 +2027,7 @@ func TestCreateServersConflicts(t *testing.T) { Type: http.ExternalLocationType, }, { - Path: "/coffee/", + Path: "^~ /coffee/", ProxyPass: "http://test_bar_80$request_uri", ProxySetHeaders: httpBaseHeaders, Type: http.ExternalLocationType, @@ -2071,7 +2071,7 @@ func TestCreateServersConflicts(t *testing.T) { }, expLocs: []http.Location{ { - Path: "/coffee/", + Path: "^~ /coffee/", ProxyPass: "http://test_bar_80$request_uri", ProxySetHeaders: httpBaseHeaders, Type: http.ExternalLocationType, @@ -2382,7 +2382,7 @@ func TestCreateLocations_Includes(t *testing.T) { }, }, { - Path: "/snippets-prefix-path/", + Path: "^~ /snippets-prefix-path/", Includes: []shared.Include{ { Name: includesFolder + "/prefix-path-location-snippet.conf", @@ -2462,7 +2462,8 @@ func TestCreateLocationsRootPath(t *testing.T) { getPathRules := func(rootPath bool, grpc bool) []dataplane.PathRule { rules := []dataplane.PathRule{ { - Path: "/path-1", + Path: "/path-1", + PathType: dataplane.PathTypeExact, MatchRules: []dataplane.MatchRule{ { Match: dataplane.Match{}, @@ -2471,7 +2472,8 @@ func TestCreateLocationsRootPath(t *testing.T) { }, }, { - Path: "/path-2", + Path: "/path-2", + PathType: dataplane.PathTypeExact, MatchRules: []dataplane.MatchRule{ { Match: dataplane.Match{}, @@ -2483,7 +2485,8 @@ func TestCreateLocationsRootPath(t *testing.T) { if rootPath { rules = append(rules, dataplane.PathRule{ - Path: "/", + Path: "/", + PathType: dataplane.PathTypeExact, MatchRules: []dataplane.MatchRule{ { Match: dataplane.Match{}, @@ -2495,8 +2498,9 @@ func TestCreateLocationsRootPath(t *testing.T) { if grpc { rules = append(rules, dataplane.PathRule{ - Path: "/grpc", - GRPC: true, + Path: "/grpc", + PathType: dataplane.PathTypeExact, + GRPC: true, MatchRules: []dataplane.MatchRule{ { Match: dataplane.Match{}, @@ -2520,19 +2524,19 @@ func TestCreateLocationsRootPath(t *testing.T) { pathRules: getPathRules(false /* rootPath */, false /* grpc */), expLocations: []http.Location{ { - Path: "/path-1", + Path: "= /path-1", ProxyPass: "http://test_foo_80$request_uri", ProxySetHeaders: httpBaseHeaders, Type: http.ExternalLocationType, }, { - Path: "/path-2", + Path: "= /path-2", ProxyPass: "http://test_foo_80$request_uri", ProxySetHeaders: httpBaseHeaders, Type: http.ExternalLocationType, }, { - Path: "/", + Path: "= /", Return: &http.Return{ Code: http.StatusNotFound, }, @@ -2545,26 +2549,26 @@ func TestCreateLocationsRootPath(t *testing.T) { grpc: true, expLocations: []http.Location{ { - Path: "/path-1", + Path: "= /path-1", ProxyPass: "http://test_foo_80$request_uri", ProxySetHeaders: httpBaseHeaders, Type: http.ExternalLocationType, }, { - Path: "/path-2", + Path: "= /path-2", ProxyPass: "http://test_foo_80$request_uri", ProxySetHeaders: httpBaseHeaders, Type: http.ExternalLocationType, }, { - Path: "/grpc", + Path: "= /grpc", ProxyPass: "grpc://test_foo_80", GRPC: true, ProxySetHeaders: grpcBaseHeaders, Type: http.ExternalLocationType, }, { - Path: "/", + Path: "= /", Return: &http.Return{ Code: http.StatusNotFound, }, @@ -2576,19 +2580,19 @@ func TestCreateLocationsRootPath(t *testing.T) { pathRules: getPathRules(true /* rootPath */, false /* grpc */), expLocations: []http.Location{ { - Path: "/path-1", + Path: "= /path-1", ProxyPass: "http://test_foo_80$request_uri", ProxySetHeaders: httpBaseHeaders, Type: http.ExternalLocationType, }, { - Path: "/path-2", + Path: "= /path-2", ProxyPass: "http://test_foo_80$request_uri", ProxySetHeaders: httpBaseHeaders, Type: http.ExternalLocationType, }, { - Path: "/", + Path: "= /", ProxyPass: "http://test_foo_80$request_uri", ProxySetHeaders: httpBaseHeaders, Type: http.ExternalLocationType, @@ -2600,7 +2604,137 @@ func TestCreateLocationsRootPath(t *testing.T) { pathRules: nil, expLocations: []http.Location{ { - Path: "/", + Path: "= /", + Return: &http.Return{ + Code: http.StatusNotFound, + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + locs, httpMatchPair, grpc := createLocations( + &dataplane.VirtualServer{ + PathRules: test.pathRules, + Port: 80, + }, + "1", + &policiesfakes.FakeGenerator{}, + alwaysFalseKeepAliveChecker, + ) + g.Expect(locs).To(Equal(test.expLocations)) + g.Expect(httpMatchPair).To(BeEmpty()) + g.Expect(grpc).To(Equal(test.grpc)) + }) + } +} + +func TestCreateLocationsPath(t *testing.T) { + t.Parallel() + hrNsName := types.NamespacedName{Namespace: "test", Name: "route1"} + + fooGroup := dataplane.BackendGroup{ + Source: hrNsName, + RuleIdx: 0, + Backends: []dataplane.Backend{ + { + UpstreamName: "test_foo_80", + Valid: true, + Weight: 1, + }, + }, + } + + pathRules := []dataplane.PathRule{ + { + Path: "/exact-path", + PathType: dataplane.PathTypeExact, + MatchRules: []dataplane.MatchRule{ + { + Match: dataplane.Match{}, + BackendGroup: fooGroup, + }, + }, + }, + { + Path: "/prefix-path-with-trailing-slash/", + PathType: dataplane.PathTypePrefix, + MatchRules: []dataplane.MatchRule{ + { + Match: dataplane.Match{}, + BackendGroup: fooGroup, + }, + }, + }, + { + Path: "/prefix-path-without-trailing-slash", + PathType: dataplane.PathTypePrefix, + MatchRules: []dataplane.MatchRule{ + { + Match: dataplane.Match{}, + BackendGroup: fooGroup, + }, + }, + }, + { + Path: "^/regular-expression-path/(.*)$", + PathType: dataplane.PathTypeRegularExpression, + MatchRules: []dataplane.MatchRule{ + { + Match: dataplane.Match{}, + BackendGroup: fooGroup, + }, + }, + }, + } + + tests := []struct { + name string + pathRules []dataplane.PathRule + expLocations []http.Location + grpc bool + }{ + { + name: "path rules with pathType.Exact/pathType.Prefix/pathType.RegularExpression", + pathRules: pathRules, + expLocations: []http.Location{ + { + Path: "= /exact-path", + ProxyPass: "http://test_foo_80$request_uri", + ProxySetHeaders: httpBaseHeaders, + Type: http.ExternalLocationType, + }, + { + Path: "^~ /prefix-path-with-trailing-slash/", + ProxyPass: "http://test_foo_80$request_uri", + ProxySetHeaders: httpBaseHeaders, + Type: http.ExternalLocationType, + }, + { + Path: "^~ /prefix-path-without-trailing-slash/", + ProxyPass: "http://test_foo_80$request_uri", + ProxySetHeaders: httpBaseHeaders, + Type: http.ExternalLocationType, + }, + { + Path: "= /prefix-path-without-trailing-slash", + ProxyPass: "http://test_foo_80$request_uri", + ProxySetHeaders: httpBaseHeaders, + Type: http.ExternalLocationType, + }, + { + Path: "~ ^/regular-expression-path/(.*)$", + ProxyPass: "http://test_foo_80$request_uri", + ProxySetHeaders: httpBaseHeaders, + Type: http.ExternalLocationType, + }, + { + Path: "= /", Return: &http.Return{ Code: http.StatusNotFound, }, @@ -2657,7 +2791,7 @@ func TestCreateReturnValForRedirectFilter(t *testing.T) { expectedReturn *http.Return expectedRewrite *rewriteConfig msg string - path string + pathRule dataplane.PathRule listenerPort int32 }{ { @@ -2751,6 +2885,84 @@ func TestCreateReturnValForRedirectFilter(t *testing.T) { expectedRewrite: &rewriteConfig{}, msg: "scheme is http, listenerPort http, no port is set", }, + { + filter: modifiedHTTPRequestRedirectFilter(func( + filter *dataplane.HTTPRequestRedirectFilter, + ) *dataplane.HTTPRequestRedirectFilter { + filter.Scheme = helpers.GetPointer("https") + filter.Port = helpers.GetPointer[int32](2022) + filter.Path = &dataplane.HTTPPathModifier{ + Type: dataplane.ReplaceFullPath, + Replacement: "/full-path", + } + return filter + }), + pathRule: dataplane.PathRule{ + Path: "/original", + PathType: dataplane.PathTypeExact, + }, + listenerPort: listenerPortCustom, + expectedReturn: &http.Return{ + Code: 301, + Body: "https://foo.example.com:2022$uri$is_args$args", + }, + expectedRewrite: &rewriteConfig{ + MainRewrite: "^ /full-path", + }, + msg: "exact path with ReplaceFullPath", + }, + { + filter: modifiedHTTPRequestRedirectFilter(func( + filter *dataplane.HTTPRequestRedirectFilter, + ) *dataplane.HTTPRequestRedirectFilter { + filter.Scheme = helpers.GetPointer("https") + filter.Port = helpers.GetPointer[int32](2022) + filter.Path = &dataplane.HTTPPathModifier{ + Type: dataplane.ReplaceFullPath, + Replacement: "/full-path", + } + return filter + }), + pathRule: dataplane.PathRule{ + Path: "/original", + PathType: dataplane.PathTypePrefix, + }, + listenerPort: listenerPortCustom, + expectedReturn: &http.Return{ + Code: 301, + Body: "https://foo.example.com:2022$uri$is_args$args", + }, + expectedRewrite: &rewriteConfig{ + MainRewrite: "^ /full-path", + }, + msg: "prefix path with ReplaceFullPath", + }, + { + filter: modifiedHTTPRequestRedirectFilter(func( + filter *dataplane.HTTPRequestRedirectFilter, + ) *dataplane.HTTPRequestRedirectFilter { + filter.Scheme = helpers.GetPointer("https") + filter.Port = helpers.GetPointer[int32](2022) + filter.Path = &dataplane.HTTPPathModifier{ + Type: dataplane.ReplaceFullPath, + Replacement: "/full-path", + } + return filter + }), + pathRule: dataplane.PathRule{ + Path: "/original/v[0-9]+", + PathType: dataplane.PathTypeRegularExpression, + }, + listenerPort: listenerPortCustom, + expectedReturn: &http.Return{ + Code: 301, + Body: "https://foo.example.com:2022$uri$is_args$args", + }, + expectedRewrite: &rewriteConfig{ + MainRewrite: "^ /full-path", + }, + msg: "regex path with ReplaceFullPath", + }, { filter: modifiedHTTPRequestRedirectFilter(func( filter *dataplane.HTTPRequestRedirectFilter, @@ -2762,7 +2974,50 @@ func TestCreateReturnValForRedirectFilter(t *testing.T) { } return filter }), - path: "/original", + pathRule: dataplane.PathRule{ + Path: "/original", + PathType: dataplane.PathTypeExact, + }, + listenerPort: listenerPortCustom, + expectedReturn: nil, + expectedRewrite: nil, + msg: "exact path with ReplacePrefixMatch will be ignored", + }, + { + filter: modifiedHTTPRequestRedirectFilter(func( + filter *dataplane.HTTPRequestRedirectFilter, + ) *dataplane.HTTPRequestRedirectFilter { + filter.Port = helpers.GetPointer[int32](80) + filter.Path = &dataplane.HTTPPathModifier{ + Type: dataplane.ReplacePrefixMatch, + Replacement: "", + } + return filter + }), + pathRule: dataplane.PathRule{ + Path: "/original/v[0-9]+", + PathType: dataplane.PathTypeRegularExpression, + }, + listenerPort: listenerPortCustom, + expectedReturn: nil, + expectedRewrite: nil, + msg: "regex path with ReplacePrefixMatch will be ignored", + }, + { + filter: modifiedHTTPRequestRedirectFilter(func( + filter *dataplane.HTTPRequestRedirectFilter, + ) *dataplane.HTTPRequestRedirectFilter { + filter.Port = helpers.GetPointer[int32](80) + filter.Path = &dataplane.HTTPPathModifier{ + Type: dataplane.ReplacePrefixMatch, + Replacement: "", + } + return filter + }), + pathRule: dataplane.PathRule{ + Path: "/original", + PathType: dataplane.PathTypePrefix, + }, listenerPort: listenerPortCustom, expectedReturn: &http.Return{ Code: 301, @@ -2785,7 +3040,10 @@ func TestCreateReturnValForRedirectFilter(t *testing.T) { } return filter }), - path: "/original", + pathRule: dataplane.PathRule{ + Path: "/original", + PathType: dataplane.PathTypePrefix, + }, listenerPort: listenerPortCustom, expectedReturn: &http.Return{ Code: 301, @@ -2807,7 +3065,10 @@ func TestCreateReturnValForRedirectFilter(t *testing.T) { } return filter }), - path: "/original", + pathRule: dataplane.PathRule{ + Path: "/original", + PathType: dataplane.PathTypePrefix, + }, listenerPort: listenerPortCustom, expectedReturn: &http.Return{ Code: 301, @@ -2830,7 +3091,10 @@ func TestCreateReturnValForRedirectFilter(t *testing.T) { } return filter }), - path: "/original", + pathRule: dataplane.PathRule{ + Path: "/original", + PathType: dataplane.PathTypePrefix, + }, listenerPort: listenerPortCustom, expectedReturn: &http.Return{ Code: 302, @@ -2853,7 +3117,10 @@ func TestCreateReturnValForRedirectFilter(t *testing.T) { } return filter }), - path: "/original/", + pathRule: dataplane.PathRule{ + Path: "/original/", + PathType: dataplane.PathTypePrefix, + }, listenerPort: listenerPortCustom, expectedReturn: &http.Return{ Code: 301, @@ -2876,7 +3143,10 @@ func TestCreateReturnValForRedirectFilter(t *testing.T) { } return filter }), - path: "/original/", + pathRule: dataplane.PathRule{ + Path: "/original/", + PathType: dataplane.PathTypePrefix, + }, listenerPort: listenerPortCustom, expectedReturn: &http.Return{ Code: 301, @@ -2894,7 +3164,7 @@ func TestCreateReturnValForRedirectFilter(t *testing.T) { t.Parallel() g := NewWithT(t) - result, rewriteConfig := createReturnAndRewriteConfigForRedirectFilter(test.filter, test.listenerPort, test.path) + result, rewriteConfig := createReturnAndRewriteConfigForRedirectFilter(test.filter, test.listenerPort, test.pathRule) g.Expect(helpers.Diff(test.expectedReturn, result)).To(BeEmpty()) g.Expect(helpers.Diff(test.expectedRewrite, rewriteConfig)).To(BeEmpty()) }) @@ -2907,7 +3177,7 @@ func TestCreateRewritesValForRewriteFilter(t *testing.T) { filter *dataplane.HTTPURLRewriteFilter expected *rewriteConfig msg string - path string + pathRule dataplane.PathRule }{ { filter: nil, @@ -2920,6 +3190,10 @@ func TestCreateRewritesValForRewriteFilter(t *testing.T) { msg: "all fields are empty", }, { + pathRule: dataplane.PathRule{ + Path: "/original", + PathType: dataplane.PathTypeExact, + }, filter: &dataplane.HTTPURLRewriteFilter{ Path: &dataplane.HTTPPathModifier{ Type: dataplane.ReplaceFullPath, @@ -2930,10 +3204,75 @@ func TestCreateRewritesValForRewriteFilter(t *testing.T) { InternalRewrite: "^ $request_uri", MainRewrite: "^ /full-path break", }, - msg: "full path", + msg: "exact path with full replacement", }, { - path: "/original", + pathRule: dataplane.PathRule{ + Path: "/original", + PathType: dataplane.PathTypePrefix, + }, + filter: &dataplane.HTTPURLRewriteFilter{ + Path: &dataplane.HTTPPathModifier{ + Type: dataplane.ReplaceFullPath, + Replacement: "/full-path", + }, + }, + expected: &rewriteConfig{ + InternalRewrite: "^ $request_uri", + MainRewrite: "^ /full-path break", + }, + msg: "prefix path with full replacement", + }, + { + pathRule: dataplane.PathRule{ + Path: "/original/v[0-9]+", + PathType: dataplane.PathTypeRegularExpression, + }, + filter: &dataplane.HTTPURLRewriteFilter{ + Path: &dataplane.HTTPPathModifier{ + Type: dataplane.ReplaceFullPath, + Replacement: "/full-path", + }, + }, + expected: &rewriteConfig{ + InternalRewrite: "^ $request_uri", + MainRewrite: "^ /full-path break", + }, + msg: "regex path with full replacement", + }, + { + pathRule: dataplane.PathRule{ + Path: "/original", + PathType: dataplane.PathTypeExact, + }, + filter: &dataplane.HTTPURLRewriteFilter{ + Path: &dataplane.HTTPPathModifier{ + Type: dataplane.ReplacePrefixMatch, + Replacement: "/prefix-path", + }, + }, + expected: nil, + msg: "exact path with prefix replacement should be ignored", + }, + { + pathRule: dataplane.PathRule{ + Path: "/original/v[0-9]+", + PathType: dataplane.PathTypeRegularExpression, + }, + filter: &dataplane.HTTPURLRewriteFilter{ + Path: &dataplane.HTTPPathModifier{ + Type: dataplane.ReplacePrefixMatch, + Replacement: "/prefix-path", + }, + }, + expected: nil, + msg: "regex path with prefix replacement should be ignored", + }, + { + pathRule: dataplane.PathRule{ + Path: "/original", + PathType: dataplane.PathTypePrefix, + }, filter: &dataplane.HTTPURLRewriteFilter{ Path: &dataplane.HTTPPathModifier{ Type: dataplane.ReplacePrefixMatch, @@ -2947,7 +3286,10 @@ func TestCreateRewritesValForRewriteFilter(t *testing.T) { msg: "prefix path no trailing slashes", }, { - path: "/original", + pathRule: dataplane.PathRule{ + Path: "/original", + PathType: dataplane.PathTypePrefix, + }, filter: &dataplane.HTTPURLRewriteFilter{ Path: &dataplane.HTTPPathModifier{ Type: dataplane.ReplacePrefixMatch, @@ -2961,7 +3303,10 @@ func TestCreateRewritesValForRewriteFilter(t *testing.T) { msg: "prefix path empty string", }, { - path: "/original", + pathRule: dataplane.PathRule{ + Path: "/original", + PathType: dataplane.PathTypePrefix, + }, filter: &dataplane.HTTPURLRewriteFilter{ Path: &dataplane.HTTPPathModifier{ Type: dataplane.ReplacePrefixMatch, @@ -2975,7 +3320,10 @@ func TestCreateRewritesValForRewriteFilter(t *testing.T) { msg: "prefix path /", }, { - path: "/original", + pathRule: dataplane.PathRule{ + Path: "/original", + PathType: dataplane.PathTypePrefix, + }, filter: &dataplane.HTTPURLRewriteFilter{ Path: &dataplane.HTTPPathModifier{ Type: dataplane.ReplacePrefixMatch, @@ -2989,7 +3337,10 @@ func TestCreateRewritesValForRewriteFilter(t *testing.T) { msg: "prefix path replacement with trailing /", }, { - path: "/original/", + pathRule: dataplane.PathRule{ + Path: "/original/", + PathType: dataplane.PathTypePrefix, + }, filter: &dataplane.HTTPURLRewriteFilter{ Path: &dataplane.HTTPPathModifier{ Type: dataplane.ReplacePrefixMatch, @@ -3003,7 +3354,10 @@ func TestCreateRewritesValForRewriteFilter(t *testing.T) { msg: "prefix path original with trailing /", }, { - path: "/original/", + pathRule: dataplane.PathRule{ + Path: "/original/", + PathType: dataplane.PathTypePrefix, + }, filter: &dataplane.HTTPURLRewriteFilter{ Path: &dataplane.HTTPPathModifier{ Type: dataplane.ReplacePrefixMatch, @@ -3023,7 +3377,7 @@ func TestCreateRewritesValForRewriteFilter(t *testing.T) { t.Parallel() g := NewWithT(t) - result := createRewritesValForRewriteFilter(test.filter, test.path) + result := createRewritesValForRewriteFilter(test.filter, test.pathRule) g.Expect(helpers.Diff(test.expected, result)).To(BeEmpty()) }) } diff --git a/internal/controller/nginx/config/validation/common.go b/internal/controller/nginx/config/validation/common.go index ec2eb2d1e2..7bff3e3e65 100644 --- a/internal/controller/nginx/config/validation/common.go +++ b/internal/controller/nginx/config/validation/common.go @@ -2,6 +2,7 @@ package validation import ( "errors" + "fmt" "regexp" "strings" @@ -101,3 +102,63 @@ func validatePath(path string) error { return nil } + +// validatePathInMatch a path used in the location directive. +func validatePathInMatch(path string) error { + if path == "" { + return errors.New("cannot be empty") + } + + if !pathRegexp.MatchString(path) { + msg := k8svalidation.RegexError(pathErrMsg, pathFmt, pathExamples...) + return errors.New(msg) + } + + return nil +} + +// validatePathInRegexMatch a path used in a regex location directive. +// 1. Must be non-empty and start with '/' +// 2. Forbidden characters in NGINX location context: {}, ;, whitespace +// 3. Must compile under Go's regexp (RE2) +// 4. Disallow unescaped '$' (NGINX variables / PCRE backrefs) +// 5. Disallow lookahead/lookbehind (unsupported in RE2) +// 6. Disallow backreferences like 1,円 2円 (RE2 unsupported). +func validatePathInRegexMatch(path string) error { + if path == "" { + return errors.New("cannot be empty") + } + + if !pathRegexp.MatchString(path) { + return errors.New(k8svalidation.RegexError(pathErrMsg, pathFmt, pathExamples...)) + } + + if _, err := regexp.Compile(path); err != nil { + return fmt.Errorf("invalid RE2 regex for path '%s': %w", path, err) + } + + for i := range len(path) { + if path[i] == '$' && (i == 0 || path[i-1] != '\\') { + return fmt.Errorf("invalid unescaped `$` at position %d in path '%s'", i, path) + } + } + + lookarounds := []string{"(?=", "(?!", "(?<=", "(? 0 { + var positions []string + for _, m := range matches { + positions = append(positions, fmt.Sprintf("[%d-%d]", m[0], m[1])) + } + return fmt.Errorf("backreference(s) %v found in path '%s' which are not supported in RE2", positions, path) + } + + return nil +} diff --git a/internal/controller/nginx/config/validation/common_test.go b/internal/controller/nginx/config/validation/common_test.go index 042a82f6a3..a61c550336 100644 --- a/internal/controller/nginx/config/validation/common_test.go +++ b/internal/controller/nginx/config/validation/common_test.go @@ -95,3 +95,61 @@ func TestValidatePathForFilters(t *testing.T) { "/path$", ) } + +func TestValidatePathInMatch(t *testing.T) { + t.Parallel() + validator := validatePathInMatch + + testValidValuesForSimpleValidator( + t, + validator, + "/", + "/path", + "/path/subpath-123", + "/_ngf-internal-route0-rule0", + ) + testInvalidValuesForSimpleValidator( + t, + validator, + "/ ", + "/path{", + "/path}", + "/path;", + "path", + "", + ) +} + +func TestValidatePathInRegexMatch(t *testing.T) { + t.Parallel() + validator := validatePathInRegexMatch + + testValidValuesForSimpleValidator( + t, + validator, + `/api/v[0-9]+`, + `/users/(?P[0-9]+)`, + `/foo_service/\w+`, + `/foo/bar`, + `/foo/\\$bar`, + ) + + testInvalidValuesForSimpleValidator( + t, + validator, + ``, + `(foo`, + `/path with space`, + `/foo;bar`, + `/foo{2}`, + `/foo$bar`, + `/foo(?=bar)`, + `/foo(?!bar)`, + `/foo(?<=bar)`, + `/foo(?[0-9]+)`, + `/foo/(?P[0-9]+)`, + ) +} diff --git a/internal/controller/nginx/config/validation/http_filters.go b/internal/controller/nginx/config/validation/http_filters.go index 5a92a01be9..062f149bad 100644 --- a/internal/controller/nginx/config/validation/http_filters.go +++ b/internal/controller/nginx/config/validation/http_filters.go @@ -55,6 +55,16 @@ func (HTTPPathValidator) ValidatePath(path string) error { return validatePath(path) } +// ValidatePathInMatch a path used in the location directive. +func (HTTPPathValidator) ValidatePathInMatch(path string) error { + return validatePathInMatch(path) +} + +// ValidatePathInRegexMatch a path used in a regex location directive. +func (HTTPPathValidator) ValidatePathInRegexMatch(path string) error { + return validatePathInRegexMatch(path) +} + func (HTTPHeaderValidator) ValidateFilterHeaderName(name string) error { return validateHeaderName(name) } diff --git a/internal/controller/nginx/config/validation/http_njs_match.go b/internal/controller/nginx/config/validation/http_njs_match.go index 12674081c2..7d3a6b2452 100644 --- a/internal/controller/nginx/config/validation/http_njs_match.go +++ b/internal/controller/nginx/config/validation/http_njs_match.go @@ -15,20 +15,6 @@ import ( // so changes to the implementation change the validation rules here. type HTTPNJSMatchValidator struct{} -// ValidatePathInMatch a path used in the location directive. -func (HTTPNJSMatchValidator) ValidatePathInMatch(path string) error { - if path == "" { - return errors.New("cannot be empty") - } - - if !pathRegexp.MatchString(path) { - msg := k8svalidation.RegexError(pathErrMsg, pathFmt, pathExamples...) - return errors.New(msg) - } - - return nil -} - func (HTTPNJSMatchValidator) ValidateHeaderNameInMatch(name string) error { if err := k8svalidation.IsHTTPHeaderName(name); err != nil { return errors.New(err[0]) diff --git a/internal/controller/nginx/config/validation/http_njs_match_test.go b/internal/controller/nginx/config/validation/http_njs_match_test.go index 70293060f8..9877a50d7c 100644 --- a/internal/controller/nginx/config/validation/http_njs_match_test.go +++ b/internal/controller/nginx/config/validation/http_njs_match_test.go @@ -4,30 +4,6 @@ import ( "testing" ) -func TestValidatePathInMatch(t *testing.T) { - t.Parallel() - validator := HTTPNJSMatchValidator{} - - testValidValuesForSimpleValidator( - t, - validator.ValidatePathInMatch, - "/", - "/path", - "/path/subpath-123", - "/_ngf-internal-route0-rule0", - ) - testInvalidValuesForSimpleValidator( - t, - validator.ValidatePathInMatch, - "/ ", - "/path{", - "/path}", - "/path;", - "path", - "", - ) -} - func TestValidateHeaderNameInMatch(t *testing.T) { t.Parallel() validator := HTTPNJSMatchValidator{} diff --git a/internal/controller/state/dataplane/convert.go b/internal/controller/state/dataplane/convert.go index 97a9b27c10..a74a9c3925 100644 --- a/internal/controller/state/dataplane/convert.go +++ b/internal/controller/state/dataplane/convert.go @@ -131,6 +131,8 @@ func convertPathType(pathType v1.PathMatchType) PathType { return PathTypePrefix case v1.PathMatchExact: return PathTypeExact + case v1.PathMatchRegularExpression: + return PathTypeRegularExpression default: panic(fmt.Sprintf("unsupported path type: %s", pathType)) } diff --git a/internal/controller/state/dataplane/convert_test.go b/internal/controller/state/dataplane/convert_test.go index 405836b3e7..62773bb33f 100644 --- a/internal/controller/state/dataplane/convert_test.go +++ b/internal/controller/state/dataplane/convert_test.go @@ -548,7 +548,11 @@ func TestConvertPathType(t *testing.T) { pathType: v1.PathMatchExact, }, { + expected: PathTypeRegularExpression, pathType: v1.PathMatchRegularExpression, + }, + { + pathType: v1.PathMatchType("InvalidType"), panic: true, }, } diff --git a/internal/controller/state/dataplane/types.go b/internal/controller/state/dataplane/types.go index 08e7e0867b..e593dfd6e0 100644 --- a/internal/controller/state/dataplane/types.go +++ b/internal/controller/state/dataplane/types.go @@ -15,10 +15,12 @@ import ( type PathType string const ( - // PathTypePrefix indicates that the path is a prefix. - PathTypePrefix PathType = "prefix" // PathTypeExact indicates that the path is exact. PathTypeExact PathType = "exact" + // PathTypePrefix indicates that the path is a prefix. + PathTypePrefix PathType = "prefix" + // PathTypeRegularExpression indicates that the path is a regular expression. + PathTypeRegularExpression PathType = "regularExpression" ) // Configuration is an intermediate representation of dataplane configuration. diff --git a/internal/controller/state/graph/httproute.go b/internal/controller/state/graph/httproute.go index 48415d0573..983bd8be1b 100644 --- a/internal/controller/state/graph/httproute.go +++ b/internal/controller/state/graph/httproute.go @@ -397,20 +397,26 @@ func validatePathMatch( return field.ErrorList{field.Invalid(fieldPath.Child("value"), *path.Value, msg)} } - if *path.Type != v1.PathMatchPathPrefix && *path.Type != v1.PathMatchExact { + switch *path.Type { + case v1.PathMatchExact, v1.PathMatchPathPrefix: + if err := validator.ValidatePathInMatch(*path.Value); err != nil { + valErr := field.Invalid(fieldPath.Child("value"), *path.Value, err.Error()) + allErrs = append(allErrs, valErr) + } + case v1.PathMatchRegularExpression: + if err := validator.ValidatePathInRegexMatch(*path.Value); err != nil { + valErr := field.Invalid(fieldPath.Child("value"), *path.Value, err.Error()) + allErrs = append(allErrs, valErr) + } + default: valErr := field.NotSupported( fieldPath.Child("type"), *path.Type, - []string{string(v1.PathMatchExact), string(v1.PathMatchPathPrefix)}, + []string{string(v1.PathMatchExact), string(v1.PathMatchPathPrefix), string(v1.PathMatchRegularExpression)}, ) allErrs = append(allErrs, valErr) } - if err := validator.ValidatePathInMatch(*path.Value); err != nil { - valErr := field.Invalid(fieldPath.Child("value"), *path.Value, err.Error()) - allErrs = append(allErrs, valErr) - } - return allErrs } diff --git a/internal/controller/state/graph/httproute_test.go b/internal/controller/state/graph/httproute_test.go index 3b90b0970f..b75058748d 100644 --- a/internal/controller/state/graph/httproute_test.go +++ b/internal/controller/state/graph/httproute_test.go @@ -1161,11 +1161,26 @@ func TestValidateMatch(t *testing.T) { match: gatewayv1.HTTPRouteMatch{ Path: &gatewayv1.HTTPPathMatch{ Type: helpers.GetPointer(gatewayv1.PathMatchRegularExpression), - Value: helpers.GetPointer("/"), + Value: helpers.GetPointer("/foo/(.*)$"), + }, + }, + expectErrCount: 0, + name: "valid regex match", + }, + { + validator: func() *validationfakes.FakeHTTPFieldsValidator { + validator := createAllValidValidator() + validator.ValidatePathInRegexMatchReturns(errors.New("invalid path value")) + return validator + }(), + match: gatewayv1.HTTPRouteMatch{ + Path: &gatewayv1.HTTPPathMatch{ + Type: helpers.GetPointer(gatewayv1.PathMatchRegularExpression), + Value: helpers.GetPointer("(foo"), }, }, expectErrCount: 1, - name: "wrong path type", + name: "bad path regex", }, { validator: createAllValidValidator(), @@ -1337,8 +1352,8 @@ func TestValidateMatch(t *testing.T) { validator: createAllValidValidator(), match: gatewayv1.HTTPRouteMatch{ Path: &gatewayv1.HTTPPathMatch{ - Type: helpers.GetPointer(gatewayv1.PathMatchRegularExpression), // invalid - Value: helpers.GetPointer("/"), + Type: helpers.GetPointer(gatewayv1.PathMatchRegularExpression), + Value: helpers.GetPointer("/foo/(.*)$"), }, Headers: []gatewayv1.HTTPHeaderMatch{ { @@ -1355,7 +1370,7 @@ func TestValidateMatch(t *testing.T) { }, }, }, - expectErrCount: 3, + expectErrCount: 2, name: "multiple errors", }, { diff --git a/internal/controller/state/validation/validationfakes/fake_httpfields_validator.go b/internal/controller/state/validation/validationfakes/fake_httpfields_validator.go index 06b5bf1915..cd5ff2d8f7 100644 --- a/internal/controller/state/validation/validationfakes/fake_httpfields_validator.go +++ b/internal/controller/state/validation/validationfakes/fake_httpfields_validator.go @@ -108,6 +108,17 @@ type FakeHTTPFieldsValidator struct { validatePathInMatchReturnsOnCall map[int]struct { result1 error } + ValidatePathInRegexMatchStub func(string) error + validatePathInRegexMatchMutex sync.RWMutex + validatePathInRegexMatchArgsForCall []struct { + arg1 string + } + validatePathInRegexMatchReturns struct { + result1 error + } + validatePathInRegexMatchReturnsOnCall map[int]struct { + result1 error + } ValidateQueryParamNameInMatchStub func(string) error validateQueryParamNameInMatchMutex sync.RWMutex validateQueryParamNameInMatchArgsForCall []struct { @@ -715,6 +726,67 @@ func (fake *FakeHTTPFieldsValidator) ValidatePathInMatchReturnsOnCall(i int, res }{result1} } +func (fake *FakeHTTPFieldsValidator) ValidatePathInRegexMatch(arg1 string) error { + fake.validatePathInRegexMatchMutex.Lock() + ret, specificReturn := fake.validatePathInRegexMatchReturnsOnCall[len(fake.validatePathInRegexMatchArgsForCall)] + fake.validatePathInRegexMatchArgsForCall = append(fake.validatePathInRegexMatchArgsForCall, struct { + arg1 string + }{arg1}) + stub := fake.ValidatePathInRegexMatchStub + fakeReturns := fake.validatePathInRegexMatchReturns + fake.recordInvocation("ValidatePathInRegexMatch", []interface{}{arg1}) + fake.validatePathInRegexMatchMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeHTTPFieldsValidator) ValidatePathInRegexMatchCallCount() int { + fake.validatePathInRegexMatchMutex.RLock() + defer fake.validatePathInRegexMatchMutex.RUnlock() + return len(fake.validatePathInRegexMatchArgsForCall) +} + +func (fake *FakeHTTPFieldsValidator) ValidatePathInRegexMatchCalls(stub func(string) error) { + fake.validatePathInRegexMatchMutex.Lock() + defer fake.validatePathInRegexMatchMutex.Unlock() + fake.ValidatePathInRegexMatchStub = stub +} + +func (fake *FakeHTTPFieldsValidator) ValidatePathInRegexMatchArgsForCall(i int) string { + fake.validatePathInRegexMatchMutex.RLock() + defer fake.validatePathInRegexMatchMutex.RUnlock() + argsForCall := fake.validatePathInRegexMatchArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeHTTPFieldsValidator) ValidatePathInRegexMatchReturns(result1 error) { + fake.validatePathInRegexMatchMutex.Lock() + defer fake.validatePathInRegexMatchMutex.Unlock() + fake.ValidatePathInRegexMatchStub = nil + fake.validatePathInRegexMatchReturns = struct { + result1 error + }{result1} +} + +func (fake *FakeHTTPFieldsValidator) ValidatePathInRegexMatchReturnsOnCall(i int, result1 error) { + fake.validatePathInRegexMatchMutex.Lock() + defer fake.validatePathInRegexMatchMutex.Unlock() + fake.ValidatePathInRegexMatchStub = nil + if fake.validatePathInRegexMatchReturnsOnCall == nil { + fake.validatePathInRegexMatchReturnsOnCall = make(map[int]struct { + result1 error + }) + } + fake.validatePathInRegexMatchReturnsOnCall[i] = struct { + result1 error + }{result1} +} + func (fake *FakeHTTPFieldsValidator) ValidateQueryParamNameInMatch(arg1 string) error { fake.validateQueryParamNameInMatchMutex.Lock() ret, specificReturn := fake.validateQueryParamNameInMatchReturnsOnCall[len(fake.validateQueryParamNameInMatchArgsForCall)] diff --git a/internal/controller/state/validation/validator.go b/internal/controller/state/validation/validator.go index d01a907e7f..10dc1fe8c3 100644 --- a/internal/controller/state/validation/validator.go +++ b/internal/controller/state/validation/validator.go @@ -24,6 +24,7 @@ type Validators struct { type HTTPFieldsValidator interface { SkipValidation() bool ValidatePathInMatch(path string) error + ValidatePathInRegexMatch(path string) error ValidateHeaderNameInMatch(name string) error ValidateHeaderValueInMatch(value string) error ValidateQueryParamNameInMatch(name string) error @@ -68,6 +69,7 @@ type SkipValidator struct{} func (SkipValidator) SkipValidation() bool { return true } func (SkipValidator) ValidatePathInMatch(string) error { return nil } +func (SkipValidator) ValidatePathInRegexMatch(string) error { return nil } func (SkipValidator) ValidateHeaderNameInMatch(string) error { return nil } func (SkipValidator) ValidateHeaderValueInMatch(string) error { return nil } func (SkipValidator) ValidateQueryParamNameInMatch(string) error { return nil }

AltStyle によって変換されたページ (->オリジナル) /