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 .HeaderValue ;
2221import io .grpc .xds .internal .headermutations .HeaderMutations .RequestHeaderMutations ;
2322import io .grpc .xds .internal .headermutations .HeaderMutations .ResponseHeaderMutations ;
2423import java .util .Collection ;
3029 * The HeaderMutationFilter class is responsible for filtering header mutations based on a given set
3130 * of rules.
3231 */
33- public interface HeaderMutationFilter {
32+ public class HeaderMutationFilter {
33+ private final Optional <HeaderMutationRulesConfig > mutationRules ;
3434
3535 /**
36- * A factory for creating {@link HeaderMutationFilter} instances.
36+ * Set of HTTP/2 pseudo-headers and the host header that are critical for routing and protocol
37+ * correctness. These headers cannot be mutated by user configuration.
3738 */
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- }
39+ private static final int MAX_HEADER_LENGTH = 16384 ;
5040
51- /**
52- * The default factory for creating {@link HeaderMutationFilter} instances.
53- */
54- Factory INSTANCE = HeaderMutationFilterImpl ::new ;
41+ public HeaderMutationFilter (Optional <HeaderMutationRulesConfig > mutationRules ) { // NOPMD
42+ this .mutationRules = mutationRules ;
43+ }
5544
5645 /**
5746 * Filters the given header mutations based on the configured rules and returns the allowed
@@ -62,111 +51,94 @@ interface Factory {
6251 * @throws HeaderMutationDisallowedException if a disallowed mutation is encountered and the rules
6352 * specify that this should be an error.
6453 */
65- HeaderMutations filter (HeaderMutations mutations ) throws HeaderMutationDisallowedException ;
54+ public HeaderMutations filter (HeaderMutations mutations )
55+ throws HeaderMutationDisallowedException {
56+ ImmutableList <HeaderValueOption > allowedRequestHeaders =
57+ filterCollection (mutations .requestMutations ().headers (),
58+ this ::isHeaderMutationIgnored ,
59+ header -> isHeaderMutationAllowed (header .header ().key ()));
60+ ImmutableList <String > allowedRequestHeadersToRemove =
61+ filterCollection (mutations .requestMutations ().headersToRemove (),
62+ header -> header .isEmpty () || header .length () > MAX_HEADER_LENGTH ,
63+ header -> isHeaderMutationAllowed (header ) && isHeaderRemovalAllowed (header ));
64+ ImmutableList <HeaderValueOption > allowedResponseHeaders =
65+ filterCollection (mutations .responseMutations ().headers (),
66+ this ::isHeaderMutationIgnored ,
67+ header -> isHeaderMutationAllowed (header .header ().key ()));
68+ return HeaderMutations .create (
69+ RequestHeaderMutations .create (allowedRequestHeaders , allowedRequestHeadersToRemove ),
70+ ResponseHeaderMutations .create (allowedResponseHeaders ));
71+ }
6672
67- /** Default implementation of {@link HeaderMutationFilter}. */
68- final class HeaderMutationFilterImpl implements HeaderMutationFilter {
69- private final Optional <HeaderMutationRulesConfig > mutationRules ;
73+ /**
74+ * A generic helper to filter a collection based on a predicate.
75+ */
76+ private <T > ImmutableList <T > filterCollection (Collection <T > items ,
77+ Predicate <T > isIgnoredPredicate , Predicate <T > isAllowedPredicate )
78+ throws HeaderMutationDisallowedException {
79+ ImmutableList .Builder <T > allowed = ImmutableList .builder ();
80+ for (T item : items ) {
81+ if (isIgnoredPredicate .test (item )) {
82+ continue ;
83+ }
84+ if (isAllowedPredicate .test (item )) {
85+ allowed .add (item );
86+ } else if (disallowIsError ()) {
87+ throw new HeaderMutationDisallowedException (
88+ "Header mutation disallowed for header: " + item );
89+ }
90+ }
91+ return allowed .build ();
92+ }
7093
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" );
94+ private boolean isHeaderRemovalAllowed (String headerKey ) {
95+ return !isSystemHeaderKey (headerKey );
96+ }
7797
78- private HeaderMutationFilterImpl ( Optional < HeaderMutationRulesConfig > mutationRules ) { // NOPMD
79- this . mutationRules = mutationRules ;
80- }
98+ private boolean isSystemHeaderKey ( String key ) {
99+ return key . startsWith ( ":" ) || key . toLowerCase ( Locale . ROOT ). equals ( "host" ) ;
100+ }
81101
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 ));
102+ private boolean isHeaderMutationIgnored (HeaderValueOption option ) {
103+ HeaderValue header = option .header ();
104+ String key = header .key ();
105+ if (key .isEmpty () || key .length () > MAX_HEADER_LENGTH ) {
106+ return true ;
99107 }
100-
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- }
121- }
122- return allowed .build ();
108+ if (header .value ().isPresent () && header .value ().get ().length () > MAX_HEADER_LENGTH ) {
109+ return true ;
123110 }
124-
125- private boolean isHeaderRemovalAllowed (String headerKey ) {
126- return !isSystemHeaderKey (headerKey );
111+ if (header .rawValue ().isPresent () && header .rawValue ().get ().size () > MAX_HEADER_LENGTH ) {
112+ return true ;
127113 }
128-
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 );
114+ if (key .toLowerCase (Locale .ROOT ).startsWith ("grpc-" )) {
115+ return true ;
134116 }
117+ return false ;
118+ }
135119
136- private boolean isSystemHeaderKey (String key ) {
137- return key .startsWith (":" ) || key .toLowerCase (Locale .ROOT ).equals ("host" );
120+ private boolean isHeaderMutationAllowed (String headerName ) {
121+ String lowerCaseHeaderName = headerName .toLowerCase (Locale .ROOT );
122+ if (isSystemHeaderKey (lowerCaseHeaderName )) {
123+ return false ;
138124 }
125+ return mutationRules .map (rules -> isHeaderMutationAllowed (lowerCaseHeaderName , rules ))
126+ .orElse (true );
127+ }
139128
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 );
129+ private boolean isHeaderMutationAllowed (String lowerCaseHeaderName ,
130+ HeaderMutationRulesConfig rules ) {
131+ if (rules .disallowExpression ().isPresent ()
132+ && rules .disallowExpression ().get ().matcher (lowerCaseHeaderName ).matches ()) {
133+ return false ;
147134 }
148-
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 ();
135+ if (rules .allowExpression ().isPresent ()) {
136+ return rules .allowExpression ().get ().matcher (lowerCaseHeaderName ).matches ();
166137 }
138+ return !rules .disallowAll ();
139+ }
167140
168- private boolean disallowIsError () {
169- return mutationRules .map (HeaderMutationRulesConfig ::disallowIsError ).orElse (false );
170- }
141+ private boolean disallowIsError () {
142+ return mutationRules .map (HeaderMutationRulesConfig ::disallowIsError ).orElse (false );
171143 }
172144}
0 commit comments