1717package io .grpc .xds .internal .headermutations ;
1818
1919import com .google .common .collect .ImmutableList ;
20- import com .google .common .collect .ImmutableSet ;
21- import io .envoyproxy .envoy .config .core .v3 .HeaderValueOption ;
20+ import io .grpc .xds .internal .grpcservice .HeaderValueValidationUtils ;
2221import io .grpc .xds .internal .headermutations .HeaderMutations .RequestHeaderMutations ;
2322import io .grpc .xds .internal .headermutations .HeaderMutations .ResponseHeaderMutations ;
2423import java .util .Collection ;
25- import java .util .Locale ;
2624import java .util .Optional ;
2725import java .util .function .Predicate ;
2826
2927/**
3028 * The HeaderMutationFilter class is responsible for filtering header mutations based on a given set
3129 * of rules.
3230 */
33- public interface HeaderMutationFilter {
31+ public class HeaderMutationFilter {
32+ private final Optional <HeaderMutationRulesConfig > mutationRules ;
3433
35- /**
36- * A factory for creating {@link HeaderMutationFilter} instances.
37- */
38- @ FunctionalInterface
39- interface Factory {
40- /**
41- * Creates a new instance of {@code HeaderMutationFilter}.
42- *
43- * @param mutationRules The rules for header mutations. If an empty {@code Optional} is
44- * provided, all header mutations are allowed by default, except for certain system
45- * headers. If a {@link HeaderMutationRulesConfig} is provided, mutations will be
46- * filtered based on the specified rules.
47- */
48- HeaderMutationFilter create (Optional <HeaderMutationRulesConfig > mutationRules );
49- }
5034
51- /**
52- * The default factory for creating {@link HeaderMutationFilter} instances.
53- */
54- Factory INSTANCE = HeaderMutationFilterImpl :: new ;
35+
36+ public HeaderMutationFilter ( Optional < HeaderMutationRulesConfig > mutationRules ) { // NOPMD
37+ this . mutationRules = mutationRules ;
38+ }
5539
5640 /**
5741 * Filters the given header mutations based on the configured rules and returns the allowed
@@ -62,111 +46,73 @@ interface Factory {
6246 * @throws HeaderMutationDisallowedException if a disallowed mutation is encountered and the rules
6347 * specify that this should be an error.
6448 */
65- HeaderMutations filter (HeaderMutations mutations ) throws HeaderMutationDisallowedException ;
66-
67- /** Default implementation of {@link HeaderMutationFilter}. */
68- final class HeaderMutationFilterImpl implements HeaderMutationFilter {
69- private final Optional <HeaderMutationRulesConfig > mutationRules ;
70-
71- /**
72- * Set of HTTP/2 pseudo-headers and the host header that are critical for routing and protocol
73- * correctness. These headers cannot be mutated by user configuration.
74- */
75- private static final ImmutableSet <String > IMMUTABLE_HEADERS =
76- ImmutableSet .of ("host" , ":authority" , ":scheme" , ":method" );
77-
78- private HeaderMutationFilterImpl (Optional <HeaderMutationRulesConfig > mutationRules ) { // NOPMD
79- this .mutationRules = mutationRules ;
80- }
81-
82- @ Override
83- public HeaderMutations filter (HeaderMutations mutations )
84- throws HeaderMutationDisallowedException {
85- ImmutableList <HeaderValueOption > allowedRequestHeaders =
86- filterCollection (mutations .requestMutations ().headers (),
87- header -> isHeaderMutationAllowed (header .getHeader ().getKey ())
88- && !appendsSystemHeader (header ));
89- ImmutableList <String > allowedRequestHeadersToRemove =
90- filterCollection (mutations .requestMutations ().headersToRemove (),
91- header -> isHeaderMutationAllowed (header ) && isHeaderRemovalAllowed (header ));
92- ImmutableList <HeaderValueOption > allowedResponseHeaders =
93- filterCollection (mutations .responseMutations ().headers (),
94- header -> isHeaderMutationAllowed (header .getHeader ().getKey ())
95- && !appendsSystemHeader (header ));
96- return HeaderMutations .create (
97- RequestHeaderMutations .create (allowedRequestHeaders , allowedRequestHeadersToRemove ),
98- ResponseHeaderMutations .create (allowedResponseHeaders ));
99- }
49+ public HeaderMutations filter (HeaderMutations mutations )
50+ throws HeaderMutationDisallowedException {
51+ ImmutableList <HeaderValueOption > allowedRequestHeaders =
52+ filterCollection (mutations .requestMutations ().headers (),
53+ this ::shouldIgnore , this ::isHeaderMutationAllowed );
54+ ImmutableList <String > allowedRequestHeadersToRemove =
55+ filterCollection (mutations .requestMutations ().headersToRemove (),
56+ this ::shouldIgnore , this ::isHeaderMutationAllowed );
57+ ImmutableList <HeaderValueOption > allowedResponseHeaders =
58+ filterCollection (mutations .responseMutations ().headers (),
59+ this ::shouldIgnore , this ::isHeaderMutationAllowed );
60+ return HeaderMutations .create (
61+ RequestHeaderMutations .create (allowedRequestHeaders , allowedRequestHeadersToRemove ),
62+ ResponseHeaderMutations .create (allowedResponseHeaders ));
63+ }
10064
101- /**
102- * A generic helper to filter a collection based on a predicate.
103- *
104- * @param items The collection of items to filter.
105- * @param isAllowedPredicate The predicate to apply to each item.
106- * @param <T> The type of items in the collection.
107- * @return An immutable list of allowed items.
108- * @throws HeaderMutationDisallowedException if an item is disallowed and disallowIsError is
109- * true.
110- */
111- private <T > ImmutableList <T > filterCollection (Collection <T > items ,
112- Predicate <T > isAllowedPredicate ) throws HeaderMutationDisallowedException {
113- ImmutableList .Builder <T > allowed = ImmutableList .builder ();
114- for (T item : items ) {
115- if (isAllowedPredicate .test (item )) {
116- allowed .add (item );
117- } else if (disallowIsError ()) {
118- throw new HeaderMutationDisallowedException (
119- "Header mutation disallowed for header: " + item );
120- }
65+ /**
66+ * A generic helper to filter a collection based on a predicate.
67+ */
68+ private <T > ImmutableList <T > filterCollection (Collection <T > items ,
69+ Predicate <T > isIgnoredPredicate , Predicate <T > isAllowedPredicate )
70+ throws HeaderMutationDisallowedException {
71+ ImmutableList .Builder <T > allowed = ImmutableList .builder ();
72+ for (T item : items ) {
73+ if (isIgnoredPredicate .test (item )) {
74+ continue ;
75+ }
76+ if (isAllowedPredicate .test (item )) {
77+ allowed .add (item );
78+ } else if (disallowIsError ()) {
79+ throw new HeaderMutationDisallowedException (
80+ "Header mutation disallowed for header: " + item );
12181 }
122- return allowed .build ();
12382 }
83+ return allowed .build ();
84+ }
12485
125- private boolean isHeaderRemovalAllowed (String headerKey ) {
126- return ! isSystemHeaderKey ( headerKey );
127- }
86+ private boolean shouldIgnore (String key ) {
87+ return HeaderValueValidationUtils . shouldIgnore ( key );
88+ }
12889
129- private boolean appendsSystemHeader (HeaderValueOption headerValueOption ) {
130- String key = headerValueOption .getHeader ().getKey ();
131- boolean isAppend = headerValueOption
132- .getAppendAction () == HeaderValueOption .HeaderAppendAction .APPEND_IF_EXISTS_OR_ADD ;
133- return isAppend && isSystemHeaderKey (key );
134- }
90+ private boolean shouldIgnore (HeaderValueOption option ) {
91+ return HeaderValueValidationUtils .shouldIgnore (option .header ());
92+ }
13593
136- private boolean isSystemHeaderKey ( String key ) {
137- return key . startsWith ( ":" ) || key . toLowerCase ( Locale . ROOT ). equals ( "host" );
138- }
94+ private boolean isHeaderMutationAllowed ( HeaderValueOption option ) {
95+ return isHeaderMutationAllowed ( option . header (). key () );
96+ }
13997
140- private boolean isHeaderMutationAllowed (String headerName ) {
141- String lowerCaseHeaderName = headerName .toLowerCase (Locale .ROOT );
142- if (IMMUTABLE_HEADERS .contains (lowerCaseHeaderName )) {
143- return false ;
144- }
145- return mutationRules .map (rules -> isHeaderMutationAllowed (lowerCaseHeaderName , rules ))
146- .orElse (true );
147- }
98+ private boolean isHeaderMutationAllowed (String headerName ) {
99+ return mutationRules .map (rules -> isHeaderMutationAllowed (headerName , rules ))
100+ .orElse (true );
101+ }
148102
149- private boolean isHeaderMutationAllowed (String lowerCaseHeaderName ,
150- HeaderMutationRulesConfig rules ) {
151- // TODO(sauravzg): The priority is slightly unclear in the spec.
152- // Both `disallowAll` and `disallow_expression` take precedence over `all other
153- // settings`.
154- // `allow_expression` takes precedence over everything except `disallow_expression`.
155- // This is a conflict between ordering for `allow_expression` and `disallowAll`.
156- // Choosing to proceed with current envoy implementation which favors `allow_expression` over
157- // `disallowAll`.
158- if (rules .disallowExpression ().isPresent ()
159- && rules .disallowExpression ().get ().matcher (lowerCaseHeaderName ).matches ()) {
160- return false ;
161- }
162- if (rules .allowExpression ().isPresent ()) {
163- return rules .allowExpression ().get ().matcher (lowerCaseHeaderName ).matches ();
164- }
165- return !rules .disallowAll ();
103+ private boolean isHeaderMutationAllowed (String lowerCaseHeaderName ,
104+ HeaderMutationRulesConfig rules ) {
105+ if (rules .disallowExpression ().isPresent ()
106+ && rules .disallowExpression ().get ().matcher (lowerCaseHeaderName ).matches ()) {
107+ return false ;
166108 }
167-
168- private boolean disallowIsError () {
169- return mutationRules .map (HeaderMutationRulesConfig ::disallowIsError ).orElse (false );
109+ if (rules .allowExpression ().isPresent ()) {
110+ return rules .allowExpression ().get ().matcher (lowerCaseHeaderName ).matches ();
170111 }
112+ return !rules .disallowAll ();
113+ }
114+
115+ private boolean disallowIsError () {
116+ return mutationRules .map (HeaderMutationRulesConfig ::disallowIsError ).orElse (false );
171117 }
172118}
0 commit comments